1 // Typed wrappers around the Siti on-device chat backend (Rust/Tauri commands
2 // and events). Keeping the IPC surface in one place means components never
3 // touch raw command-name strings.
4
5 import { invoke } from "@tauri-apps/api/core";
6 import { listen, type UnlistenFn } from "@tauri-apps/api/event";
7 import { getVersion } from "@tauri-apps/api/app";
8
9 export type ChatStatus =
10 | "unloaded"
11 | "loading"
12 | "ready"
13 | "generating"
14 | "error";
15
16 export interface ModelInfo {
17 id: string;
18 name: string;
19 org: string;
20 description: string;
21 approx_memory: string;
22 size_bytes: number | null;
23 is_selected: boolean;
24 }
25
26 export interface ChatStatusResponse {
27 status: ChatStatus;
28 model_name: string | null;
29 approx_memory: string | null;
30 history_length: number;
31 }
32
33 export interface ChatStatusPayload {
34 status: ChatStatus;
35 model_name: string | null;
36 error: string | null;
37 }
38
39 export interface ChatReplyPayload {
40 reply: string | null;
41 duration: string | null;
42 error: string | null;
43 }
44
45 export interface ChatMessagePayload {
46 role: "user" | "assistant";
47 content: string;
48 }
49
50 // ── Commands ────────────────────────────────────────────────────────────────
51
52 export const listModels = () => invoke<ModelInfo[]>("chat_list_models");
53
54 export const setModel = (modelId: string) =>
55 invoke<void>("chat_set_model", { modelId });
56
57 export const sendMessage = (message: string) =>
58 invoke<void>("chat_send_message", { message });
59
60 export const getStatus = () => invoke<ChatStatusResponse>("chat_get_status");
61
62 export const getHistory = () =>
63 invoke<ChatMessagePayload[]>("chat_get_history");
64
65 export const clearHistory = () => invoke<void>("chat_clear_history");
66
67 // ── App metadata ──────────────────────────────────────────────────────────
68
69 /** Marketing version (CFBundleShortVersionString) from tauri.conf.json. */
70 export const getAppVersion = () => getVersion();
71
72 /** Native build number (Apple CFBundleVersion); `null` when unavailable. */
73 export const getBuildVersion = () =>
74 invoke<string | null>("app_build_version");
75
76 // ── Events ──────────────────────────────────────────────────────────────────
77
78 export const onStatusChanged = (cb: (p: ChatStatusPayload) => void): Promise<UnlistenFn> =>
79 listen<ChatStatusPayload>("chat_status_changed", (e) => cb(e.payload));
80
81 export const onReply = (cb: (p: ChatReplyPayload) => void): Promise<UnlistenFn> =>
82 listen<ChatReplyPayload>("chat_reply", (e) => cb(e.payload));
83
84 // ── Helpers ───────────────────────────────────────────────────────────────
85
86 /** Format a byte count as a compact "1.9 GB" / "941 MB" string. */
87 export function formatSize(bytes: number | null): string {
88 if (!bytes) return "";
89 const gb = bytes / 1e9;
90 if (gb >= 1) return `${gb.toFixed(gb >= 10 ? 0 : 1)} GB`;
91 return `${Math.round(bytes / 1e6)} MB`;
92 }