Frontend counterpart of the new Settings field: the one-time "data leaves your device" acknowledgment for hosted (non-local) AI providers.
555 lines
20 KiB
TypeScript
555 lines
20 KiB
TypeScript
// Typed Tauri client — the frontend's ONLY way to reach the core.
|
||
// Mirrors docs/04-api-contracts.md. Keep these signatures in sync with
|
||
// src-tauri/src/commands.rs (a contract test enforces this — see docs/06).
|
||
|
||
import { invoke } from "@tauri-apps/api/core";
|
||
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
|
||
|
||
export type MeetingId = string;
|
||
export type BackendId = "npu" | "nvidia" | "amd" | "intel" | "cpu";
|
||
|
||
// Every backend command rejects an `Err` as this shape, not a native Error
|
||
// (see src-tauri/src/error.rs) — `e instanceof Error` is always false for it.
|
||
export interface WaError {
|
||
kind: string;
|
||
message: string;
|
||
}
|
||
|
||
export function errorMessage(e: unknown): string {
|
||
if (typeof e === "string") return e;
|
||
if (e instanceof Error) return e.message;
|
||
if (e && typeof e === "object" && typeof (e as WaError).message === "string") {
|
||
return (e as WaError).message;
|
||
}
|
||
return String(e);
|
||
}
|
||
|
||
export interface BackendInfo {
|
||
id: BackendId;
|
||
name: string;
|
||
available: boolean;
|
||
rank: number;
|
||
vram_mb: number | null;
|
||
}
|
||
|
||
export interface AppInfo {
|
||
version: string;
|
||
commit: string;
|
||
}
|
||
|
||
export interface HardwareStatus {
|
||
backends: BackendInfo[];
|
||
active: BackendId;
|
||
modelSize: string;
|
||
estRtf: number;
|
||
/** NPU package state (T3.4): chip detected, runtime staged, model fetched. */
|
||
npu?: { present: boolean; runtimeReady: boolean; modelInstalled: boolean };
|
||
/** DirectML GPU package (P2): applicable = staging would help this build. */
|
||
directml?: { applicable: boolean; runtimeReady: boolean; modelInstalled: boolean };
|
||
}
|
||
|
||
// One enumerated audio device — a render (playback) device for the loopback
|
||
// picker (FR-CAP-1) or a capture (microphone) device for the mic picker
|
||
// (FR-CAP-7). `id` is the persisted `Device::get_id()`; `name` is display-only.
|
||
export interface AudioDeviceInfo {
|
||
id: string;
|
||
name: string;
|
||
}
|
||
|
||
export interface LlmStatus {
|
||
provider: string; // ollama|custom|anthropic|off (ADR-0011; "openai" not yet wired)
|
||
reachable: boolean;
|
||
isLocal: boolean;
|
||
models: string[];
|
||
}
|
||
|
||
export interface ModelInfo {
|
||
id: string;
|
||
label: string;
|
||
size_mb: number;
|
||
installed: boolean;
|
||
active: boolean;
|
||
}
|
||
|
||
export type MeetingStatus = "recording" | "transcribing" | "ready" | "recovering" | "error";
|
||
|
||
export interface MeetingListItem {
|
||
id: MeetingId;
|
||
title: string;
|
||
started_at: number;
|
||
duration_secs: number | null;
|
||
status: MeetingStatus;
|
||
tags: string[];
|
||
}
|
||
|
||
// Full-text search hit (Phase 8, FR-SEARCH-1) — same shape as MeetingListItem
|
||
// plus a highlighted excerpt of what matched.
|
||
export interface SearchHit {
|
||
id: MeetingId;
|
||
title: string;
|
||
started_at: number;
|
||
duration_secs: number | null;
|
||
status: MeetingStatus;
|
||
tags: string[];
|
||
snippet: string;
|
||
}
|
||
|
||
// list_meetings filters (Phase 8, FR-SEARCH-2). All fields optional/ANDed.
|
||
export interface MeetingFilter {
|
||
query?: string;
|
||
tag?: string;
|
||
participantId?: string;
|
||
from?: number;
|
||
to?: number;
|
||
}
|
||
|
||
export interface TranscriptSegment {
|
||
id: number;
|
||
start_ms: number;
|
||
end_ms: number;
|
||
speaker: string;
|
||
text: string;
|
||
confidence: number | null;
|
||
interim: boolean;
|
||
}
|
||
|
||
export interface SpeakerInfo {
|
||
label: string;
|
||
display_name: string | null;
|
||
participant_id: string | null;
|
||
}
|
||
|
||
// Drafted action item from a summary (Phase 5, FR-LLM-3). `id` is only set
|
||
// once `confirmActionItems` has persisted it as a row.
|
||
export interface ActionItem {
|
||
id: string | null;
|
||
text: string;
|
||
owner: string | null;
|
||
due_at: number | null;
|
||
confirmed: boolean;
|
||
// Schedule a local OS reminder for due_at (Phase 8, T8.6, FR-CAL-5).
|
||
reminder_set: boolean;
|
||
}
|
||
|
||
// On-disk shape of summary.json (FR-LLM-2/4) — `null` until generate_summary
|
||
// has run at least once for a meeting.
|
||
export interface SummaryFile {
|
||
schema: number;
|
||
generated_at: number;
|
||
provider: string;
|
||
model: string;
|
||
summary_md: string;
|
||
decisions: string[];
|
||
action_items: ActionItem[];
|
||
}
|
||
|
||
// Full meeting detail (get_meeting) — DB row + transcript + speakers + notes.
|
||
export interface Meeting {
|
||
id: MeetingId;
|
||
title: string;
|
||
started_at: number;
|
||
ended_at: number | null;
|
||
duration_secs: number | null;
|
||
status: MeetingStatus;
|
||
recorded: boolean;
|
||
language: string | null;
|
||
backend_used: string | null;
|
||
model_used: string | null;
|
||
segments: TranscriptSegment[];
|
||
speakers: SpeakerInfo[];
|
||
notes_markdown: string;
|
||
summary: SummaryFile | null;
|
||
calendar_event_id: string | null;
|
||
tags: string[];
|
||
template_id: string | null;
|
||
}
|
||
|
||
// Note template (Phase 8, T8.1, FR-NOTE-5) — section headers applied to
|
||
// notes.md at recording-start time.
|
||
export interface NoteTemplate {
|
||
id: string;
|
||
name: string;
|
||
sections: string[];
|
||
}
|
||
|
||
// Calendar / .pst (Phase 6, FR-CAL-*).
|
||
export interface CalendarEvent {
|
||
id: string;
|
||
source: string; // pst|graph|ics
|
||
subject: string | null;
|
||
organizer: string | null;
|
||
starts_at: number | null;
|
||
ends_at: number | null;
|
||
description: string | null;
|
||
raw_uid: string | null;
|
||
}
|
||
|
||
export interface Participant {
|
||
id: string;
|
||
name: string;
|
||
email: string | null;
|
||
role: string | null; // organizer|required|optional
|
||
}
|
||
|
||
export interface CalendarEventDetail {
|
||
event: CalendarEvent;
|
||
participants: Participant[];
|
||
}
|
||
|
||
export type SyncKind = "webdav" | "onedrive" | "dropbox" | "box";
|
||
|
||
// What the UI sees about a target — NEVER includes the secret (FR-SYNC-6).
|
||
export interface SyncTargetInfo {
|
||
id: string;
|
||
name: string;
|
||
kind: SyncKind;
|
||
provider_hint: string | null; // nextcloud|owncloud|cloudreve|seafile|synology|generic
|
||
base_url: string | null;
|
||
remote_base_path: string;
|
||
username: string | null;
|
||
enabled: boolean;
|
||
third_party: boolean;
|
||
host: string | null;
|
||
upload_transcript: boolean;
|
||
upload_notes: boolean;
|
||
upload_summary: boolean;
|
||
upload_recording: boolean;
|
||
trigger_on_finalize: boolean;
|
||
allow_plaintext_lan: boolean;
|
||
encrypt_before_upload: boolean;
|
||
}
|
||
|
||
// privacy_self_check() response (FR-SEC-2) — proves local-only handling.
|
||
export interface PrivacySyncTarget {
|
||
name: string;
|
||
host: string;
|
||
thirdParty: boolean;
|
||
tls: boolean;
|
||
}
|
||
|
||
export interface PrivacySelfCheck {
|
||
llmEndpoint: string;
|
||
llmIsLocal: boolean;
|
||
syncEnabled: boolean;
|
||
syncTargets: PrivacySyncTarget[];
|
||
allowlistedHosts: string[];
|
||
}
|
||
|
||
// Payload for add/update. `secret` is write-only (stored in the OS credential store).
|
||
export interface SyncTargetConfig {
|
||
id?: string;
|
||
name: string;
|
||
kind: SyncKind;
|
||
provider_hint?: string;
|
||
base_url?: string;
|
||
remote_base_path?: string;
|
||
username?: string;
|
||
secret?: string;
|
||
enabled?: boolean;
|
||
upload_transcript?: boolean;
|
||
upload_notes?: boolean;
|
||
upload_summary?: boolean;
|
||
upload_recording?: boolean;
|
||
trigger_on_finalize?: boolean;
|
||
allow_plaintext_lan?: boolean;
|
||
encrypt_before_upload?: boolean;
|
||
}
|
||
|
||
// A durable upload job for one artifact × target (sync_status). Snake_case,
|
||
// matching the Rust SyncJobInfo; the sync://job *event* is camelCase (onSyncJob).
|
||
export interface SyncJobInfo {
|
||
id: string;
|
||
target_id: string;
|
||
meeting_id: MeetingId;
|
||
artifact: string;
|
||
status: string; // pending|uploading|done|failed|skipped
|
||
attempts: number;
|
||
bytes_sent: number;
|
||
bytes_total: number | null;
|
||
last_error: string | null;
|
||
}
|
||
|
||
export interface AppSettings {
|
||
theme: string;
|
||
storage_root: string;
|
||
llm_provider: string;
|
||
llm_endpoint: string;
|
||
llm_model: string;
|
||
/** Advanced Ollama tuning; sparse (only non-default overrides). */
|
||
llm_advanced?: {
|
||
system?: string;
|
||
think?: string;
|
||
keep_alive?: string;
|
||
options?: Record<string, number | boolean | string[]>;
|
||
} | null;
|
||
preferred_backend: string;
|
||
whisper_model: string;
|
||
low_overhead: boolean;
|
||
default_record: boolean;
|
||
consent_acknowledged: boolean;
|
||
/** One-time "data leaves your device" ack for hosted (non-local) AI
|
||
* providers — Anthropic today (ADR-0011, T10.3). Independent of
|
||
* consent_acknowledged (that one's about recording law). */
|
||
hosted_ai_acknowledged: boolean;
|
||
sync_enabled: boolean;
|
||
mcp_enabled: boolean;
|
||
retention_max_age_days: number | null;
|
||
retention_max_size_gb: number | null;
|
||
pst_last_path: string | null;
|
||
pst_auto_sync: boolean;
|
||
audio_output_device: string | null;
|
||
microphone_enabled: boolean;
|
||
audio_input_device: string | null;
|
||
}
|
||
|
||
// Feature brief — agent-ready spec distilled from a meeting (ADR-0011).
|
||
export interface FeatureBrief {
|
||
id: string;
|
||
meeting_id: MeetingId;
|
||
title: string;
|
||
problem: string;
|
||
desired_outcome: string;
|
||
acceptance_criteria: string[];
|
||
target_repo: string | null;
|
||
context_excerpts: { speaker: string; text: string }[];
|
||
}
|
||
|
||
export interface FeatureBriefInfo {
|
||
id: string;
|
||
meeting_id: MeetingId;
|
||
title: string;
|
||
target_repo: string | null;
|
||
exposed: boolean;
|
||
}
|
||
|
||
export interface McpAccessEntry {
|
||
at: number;
|
||
tool: string;
|
||
meeting_id: MeetingId | null;
|
||
client: string | null;
|
||
}
|
||
|
||
// ---- Commands ----
|
||
export const api = {
|
||
// `record` controls audio RETENTION (default false / off — ADR-0009).
|
||
startRecording: (
|
||
meetingTitle?: string,
|
||
calendarEventId?: string,
|
||
record = false,
|
||
templateId?: string,
|
||
) =>
|
||
invoke<MeetingId>("start_recording", {
|
||
args: { meetingTitle, calendarEventId, record, templateId },
|
||
}),
|
||
listNoteTemplates: () => invoke<NoteTemplate[]>("list_note_templates"),
|
||
stopRecording: (meetingId: MeetingId) => invoke<void>("stop_recording", { meetingId }),
|
||
// Abandon an accidental recording: stop + delete files + drop the DB row.
|
||
cancelRecording: (meetingId: MeetingId) => invoke<void>("cancel_recording", { meetingId }),
|
||
// Absolute path to a playable audio.wav (decrypted if sealed), for convertFileSrc.
|
||
recordingPlaybackPath: (meetingId: MeetingId) =>
|
||
invoke<string>("recording_playback_path", { meetingId }),
|
||
pauseRecording: (meetingId: MeetingId) => invoke<void>("pause_recording", { meetingId }),
|
||
resumeRecording: (meetingId: MeetingId) => invoke<void>("resume_recording", { meetingId }),
|
||
setRecordingRetention: (meetingId: MeetingId, record: boolean) =>
|
||
invoke<void>("set_recording_retention", { meetingId, record }),
|
||
acknowledgeRecordingConsent: () => invoke<void>("acknowledge_recording_consent"),
|
||
|
||
appInfo: () => invoke<AppInfo>("app_info"),
|
||
openUrl: (url: string) => invoke<void>("open_url", { url }),
|
||
hardwareStatus: () => invoke<HardwareStatus>("hardware_status"),
|
||
listAudioDevices: () => invoke<AudioDeviceInfo[]>("list_audio_devices"),
|
||
listInputDevices: () => invoke<AudioDeviceInfo[]>("list_input_devices"),
|
||
setPreferredBackend: (backend: BackendId | "auto") =>
|
||
invoke<void>("set_preferred_backend", { args: { backend } }),
|
||
downloadNpuPackage: () => invoke<void>("download_npu_package"),
|
||
downloadDirectmlPackage: () => invoke<void>("download_directml_package"),
|
||
listModels: () => invoke<ModelInfo[]>("list_models"),
|
||
downloadModel: (id: string, kind: "whisper" = "whisper") =>
|
||
invoke<void>("download_model", { args: { kind, id } }),
|
||
removeModel: (id: string) => invoke<void>("remove_model", { id }),
|
||
reprocessTranscript: (meetingId: MeetingId, model: string) =>
|
||
invoke<void>("reprocess_transcript", { meetingId, model }),
|
||
resumeTranscription: (meetingId: MeetingId) =>
|
||
invoke<void>("resume_transcription", { meetingId }),
|
||
listMeetings: (filter?: MeetingFilter) =>
|
||
invoke<MeetingListItem[]>("list_meetings", {
|
||
query: filter?.query,
|
||
tag: filter?.tag,
|
||
participantId: filter?.participantId,
|
||
from: filter?.from,
|
||
to: filter?.to,
|
||
}),
|
||
// Full-text search across transcripts + notes — distinct from listMeetings'
|
||
// `query`, which only substring-matches the title.
|
||
search: (query: string) => invoke<SearchHit[]>("search", { query }),
|
||
setTags: (meetingId: MeetingId, tags: string[]) => invoke<void>("set_tags", { meetingId, tags }),
|
||
listTags: () => invoke<string[]>("list_tags"),
|
||
getMeeting: (meetingId: MeetingId) => invoke<Meeting>("get_meeting", { meetingId }),
|
||
deleteMeeting: (meetingId: MeetingId) => invoke<void>("delete_meeting", { meetingId }),
|
||
updateNotes: (meetingId: MeetingId, markdown: string) =>
|
||
invoke<void>("update_notes", { meetingId, markdown }),
|
||
// dest is a file path for md/pdf/docx, a folder for bundle.
|
||
exportMeeting: (meetingId: MeetingId, dest: string, format: "md" | "pdf" | "docx" | "bundle") =>
|
||
invoke<string>("export_meeting", { meetingId, dest, format }),
|
||
// Every meeting matching tag/date filters, one file (or bundle folder) per
|
||
// meeting under destDir. Returns the count actually exported (T8.5, FR-STORE-4).
|
||
bulkExportMeetings: (
|
||
destDir: string,
|
||
format: "md" | "pdf" | "docx" | "bundle",
|
||
filter?: Pick<MeetingFilter, "tag" | "from" | "to">,
|
||
) =>
|
||
invoke<number>("bulk_export_meetings", {
|
||
destDir,
|
||
format,
|
||
tag: filter?.tag,
|
||
from: filter?.from,
|
||
to: filter?.to,
|
||
}),
|
||
|
||
llmStatus: () => invoke<LlmStatus>("llm_status"),
|
||
// provider ∈ ollama|custom|anthropic|openai|off; apiKey (hosted) → OS credential store (ADR-0011).
|
||
setLlmProvider: (config: {
|
||
provider: string;
|
||
endpoint?: string;
|
||
model?: string;
|
||
apiKey?: string;
|
||
}) => invoke<void>("set_llm_provider", { args: config }),
|
||
generateSummary: (meetingId: MeetingId, templateId?: string) =>
|
||
invoke<void>("generate_summary", { meetingId, templateId }),
|
||
confirmActionItems: (meetingId: MeetingId, items: ActionItem[]) =>
|
||
invoke<void>("confirm_action_items", { meetingId, items }),
|
||
generateTags: (meetingId: MeetingId) => invoke<string[]>("generate_tags", { meetingId }),
|
||
|
||
importPst: (path: string, password?: string) => invoke<number>("import_pst", { path, password }),
|
||
listCalendarEvents: (from?: number, to?: number) =>
|
||
invoke<CalendarEvent[]>("list_calendar_events", { from, to }),
|
||
getCalendarEvent: (eventId: string) =>
|
||
invoke<CalendarEventDetail>("get_calendar_event", { eventId }),
|
||
attachMeetingToEvent: (meetingId: MeetingId, eventId: string) =>
|
||
invoke<void>("attach_meeting_to_event", { meetingId, eventId }),
|
||
renameMeeting: (meetingId: MeetingId, title: string) =>
|
||
invoke<void>("rename_meeting", { meetingId, title }),
|
||
renameSpeaker: (meetingId: MeetingId, label: string, name: string) =>
|
||
invoke<void>("rename_speaker", { meetingId, label, name }),
|
||
mapSpeakerToParticipant: (meetingId: MeetingId, label: string, participantId: string) =>
|
||
invoke<void>("map_speaker_to_participant", { meetingId, label, participantId }),
|
||
|
||
// Sync (ADR-0010) — off by default; secrets never returned by listSyncTargets.
|
||
listSyncTargets: () => invoke<SyncTargetInfo[]>("list_sync_targets"),
|
||
addSyncTarget: (config: SyncTargetConfig) =>
|
||
invoke<SyncTargetInfo>("add_sync_target", { config }),
|
||
updateSyncTarget: (config: SyncTargetConfig & { id: string }) =>
|
||
invoke<SyncTargetInfo>("update_sync_target", { config }),
|
||
removeSyncTarget: (id: string) => invoke<void>("remove_sync_target", { id }),
|
||
testSyncTarget: (configOrId: SyncTargetConfig | { id: string }) =>
|
||
invoke<{ ok: boolean; message: string }>("test_sync_target", { config: configOrId }),
|
||
setSyncEnabled: (enabled: boolean) => invoke<void>("set_sync_enabled", { enabled }),
|
||
beginOauthLink: (kind: "onedrive" | "dropbox" | "box") =>
|
||
invoke<{ authUrl: string }>("begin_oauth_link", { kind }),
|
||
syncMeeting: (meetingId: MeetingId, targetId?: string) =>
|
||
invoke<void>("sync_meeting", { meetingId, targetId }),
|
||
syncStatus: (meetingId?: MeetingId) => invoke<SyncJobInfo[]>("sync_status", { meetingId }),
|
||
retrySyncJob: (jobId: string) => invoke<void>("retry_sync_job", { jobId }),
|
||
|
||
// Feature briefs + MCP server (ADR-0011). MCP server is loopback-only and off by default.
|
||
createFeatureBrief: (meetingId: MeetingId, targetRepo?: string) =>
|
||
invoke<FeatureBrief>("create_feature_brief", { meetingId, targetRepo }),
|
||
listFeatureBriefs: (meetingId?: MeetingId) =>
|
||
invoke<FeatureBriefInfo[]>("list_feature_briefs", { meetingId }),
|
||
getFeatureBrief: (id: string) => invoke<FeatureBrief>("get_feature_brief", { id }),
|
||
setBriefExposed: (id: string, exposed: boolean) =>
|
||
invoke<void>("set_brief_exposed", { id, exposed }),
|
||
mcpStatus: () => invoke("mcp_status"),
|
||
setMcpEnabled: (enabled: boolean, transport?: "http" | "stdio", port?: number) =>
|
||
invoke<{ endpoint: string; token: string }>("set_mcp_enabled", { enabled, transport, port }),
|
||
setMcpScope: (expose: "none" | "selected" | "all", exposeRecordings?: boolean) =>
|
||
invoke<void>("set_mcp_scope", { expose, exposeRecordings }),
|
||
mcpAccessLog: (limit?: number) => invoke<McpAccessEntry[]>("mcp_access_log", { limit }),
|
||
// Layer 3 (later) push handoff.
|
||
runAgent: (
|
||
briefId: string,
|
||
tool: "claude" | "codex" | "opencode" | "copilot",
|
||
repoPath: string,
|
||
) => invoke<{ ok: boolean }>("run_agent", { briefId, tool, repoPath }),
|
||
createIssueFromBrief: (briefId: string, tracker: "github", assignCopilot?: boolean) =>
|
||
invoke<{ url: string }>("create_issue_from_brief", { briefId, tracker, assignCopilot }),
|
||
|
||
getSettings: () => invoke<AppSettings>("get_settings"),
|
||
updateSettings: (patch: Partial<AppSettings>) =>
|
||
invoke<AppSettings>("update_settings", { patch }),
|
||
privacySelfCheck: () => invoke<PrivacySelfCheck>("privacy_self_check"),
|
||
|
||
// At-rest encryption vault (Phase 8, T8.8, FR-SEC-3).
|
||
vaultStatus: () => invoke<{ enabled: boolean; unlocked: boolean }>("vault_status"),
|
||
enableVault: (password: string) => invoke<void>("enable_vault", { args: { password } }),
|
||
unlockVault: (password: string) => invoke<void>("unlock_vault", { args: { password } }),
|
||
lockVault: () => invoke<void>("lock_vault"),
|
||
changeVaultPassword: (oldPassword: string, newPassword: string) =>
|
||
invoke<void>("change_vault_password", { args: { oldPassword, newPassword } }),
|
||
};
|
||
|
||
// ---- Events (Rust → UI) ----
|
||
export const events = {
|
||
onRecordingState: (cb: (p: unknown) => void): Promise<UnlistenFn> =>
|
||
listen("recording://state", (e) => cb(e.payload)),
|
||
onRetention: (cb: (p: { meetingId: string; record: boolean }) => void): Promise<UnlistenFn> =>
|
||
listen("recording://retention", (e) => cb(e.payload as never)),
|
||
onLevel: (
|
||
cb: (p: { meetingId: string; rms: number; peak: number }) => void,
|
||
): Promise<UnlistenFn> => listen("recording://level", (e) => cb(e.payload as never)),
|
||
onDeviceChanged: (
|
||
cb: (p: { meetingId: string; recovered: boolean; message: string }) => void,
|
||
): Promise<UnlistenFn> => listen("recording://device", (e) => cb(e.payload as never)),
|
||
onSegment: (
|
||
cb: (p: { meetingId: string; segment: TranscriptSegment }) => void,
|
||
): Promise<UnlistenFn> => listen("transcript://segment", (e) => cb(e.payload as never)),
|
||
onFinalized: (
|
||
cb: (p: { meetingId: string; segmentCount: number }) => void,
|
||
): Promise<UnlistenFn> => listen("transcript://finalized", (e) => cb(e.payload as never)),
|
||
onLlmToken: (cb: (p: { meetingId: string; text: string }) => void): Promise<UnlistenFn> =>
|
||
listen("llm://token", (e) => cb(e.payload as never)),
|
||
onLlmDone: (cb: (p: { meetingId: string; summary: SummaryFile }) => void): Promise<UnlistenFn> =>
|
||
listen("llm://done", (e) => cb(e.payload as never)),
|
||
onModelProgress: (
|
||
cb: (p: { id: string; receivedBytes: number; totalBytes: number | null }) => void,
|
||
): Promise<UnlistenFn> => listen("model://progress", (e) => cb(e.payload as never)),
|
||
onHardwareChanged: (
|
||
cb: (p: { active: BackendId; reason: string }) => void,
|
||
): Promise<UnlistenFn> => listen("hardware://changed", (e) => cb(e.payload as never)),
|
||
onNpuDownload: (
|
||
cb: (p: {
|
||
stage: "model" | "runtime" | "done";
|
||
file?: number;
|
||
received?: number;
|
||
total?: number | null;
|
||
copied?: number;
|
||
ready?: boolean;
|
||
}) => void,
|
||
): Promise<UnlistenFn> => listen("npu://download", (e) => cb(e.payload as never)),
|
||
onSyncJob: (
|
||
cb: (p: {
|
||
jobId: string;
|
||
meetingId: string;
|
||
targetId: string;
|
||
artifact: string;
|
||
status: string;
|
||
bytesSent: number;
|
||
bytesTotal: number | null;
|
||
attempts?: number;
|
||
error?: string | null;
|
||
}) => void,
|
||
): Promise<UnlistenFn> => listen("sync://job", (e) => cb(e.payload as never)),
|
||
onSyncLinked: (
|
||
cb: (p: { ok: boolean; kind: string; error?: string }) => void,
|
||
): Promise<UnlistenFn> => listen("sync://linked", (e) => cb(e.payload as never)),
|
||
onMcpAccess: (cb: (p: McpAccessEntry & { client?: string }) => void): Promise<UnlistenFn> =>
|
||
listen("mcp://access", (e) => cb(e.payload as never)),
|
||
onAgentProgress: (
|
||
cb: (p: { briefId: string; tool: string; line: string }) => void,
|
||
): Promise<UnlistenFn> => listen("agent://progress", (e) => cb(e.payload as never)),
|
||
onPstProgress: (cb: (p: { processed: number; total: number }) => void): Promise<UnlistenFn> =>
|
||
listen("pst://progress", (e) => cb(e.payload as never)),
|
||
onError: (cb: (p: { kind: string; message: string }) => void): Promise<UnlistenFn> =>
|
||
listen("error", (e) => cb(e.payload as never)),
|
||
};
|