@pheem49/mint 1.5.5 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.codex +0 -0
- package/.github/FUNDING.yml +2 -0
- package/.github/workflows/ci.yml +45 -0
- package/.github/workflows/release.yml +79 -0
- package/Cargo.lock +5792 -0
- package/Cargo.toml +32 -0
- package/README.md +387 -353
- package/assets/icon.png +0 -0
- package/bin/mint +0 -0
- package/crates/mint-cli/Cargo.toml +23 -0
- package/crates/mint-cli/src/agent.rs +851 -0
- package/crates/mint-cli/src/gmail.rs +216 -0
- package/crates/mint-cli/src/image.rs +142 -0
- package/crates/mint-cli/src/main.rs +2837 -0
- package/crates/mint-cli/src/mcp.rs +63 -0
- package/crates/mint-cli/src/onboard.rs +1149 -0
- package/crates/mint-cli/src/setup.rs +390 -0
- package/crates/mint-cli/src/skills.rs +8 -0
- package/crates/mint-cli/src/updater.rs +279 -0
- package/crates/mint-core/Cargo.toml +22 -0
- package/crates/mint-core/src/agent_loop.rs +94 -0
- package/crates/mint-core/src/api_server.rs +991 -0
- package/crates/mint-core/src/channels.rs +248 -0
- package/crates/mint-core/src/chat.rs +895 -0
- package/crates/mint-core/src/code_tools.rs +729 -0
- package/crates/mint-core/src/config.rs +368 -0
- package/crates/mint-core/src/files.rs +159 -0
- package/crates/mint-core/src/knowledge.rs +541 -0
- package/crates/mint-core/src/lib.rs +84 -0
- package/crates/mint-core/src/mcp.rs +273 -0
- package/crates/mint-core/src/memory.rs +673 -0
- package/crates/mint-core/src/orchestration.rs +2157 -0
- package/crates/mint-core/src/pictures.rs +314 -0
- package/crates/mint-core/src/plugins.rs +727 -0
- package/crates/mint-core/src/safety.rs +416 -0
- package/crates/mint-core/src/semantic.rs +254 -0
- package/crates/mint-core/src/shell.rs +317 -0
- package/crates/mint-core/src/skills.rs +71 -0
- package/crates/mint-core/src/symbols.rs +157 -0
- package/crates/mint-core/src/tasks.rs +308 -0
- package/crates/mint-core/src/tts.rs +92 -0
- package/crates/mint-core/src/weather.rs +93 -0
- package/crates/mint-core/src/web_search.rs +200 -0
- package/crates/mint-core/src/workflows.rs +81 -0
- package/crates/mint-core/tests/mcp_stdio.rs +45 -0
- package/crates/mint-core/tests/memory_persistence.rs +172 -0
- package/crates/mint-core/tests/pictures_storage.rs +14 -0
- package/crates/mint-core/tests/task_lifecycle.rs +87 -0
- package/package.json +35 -99
- package/src/bin/index.js +16 -0
- package/src/renderer/index-web.html +17 -0
- package/src/renderer/index.html +17 -0
- package/src/renderer/public/Live2DCubismCore.js +9 -0
- package/src/renderer/public/assets/icon.png +0 -0
- package/src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.model3.json +36 -0
- package/src/renderer/src/App.tsx +33 -0
- package/src/renderer/src/calculator.ts +47 -0
- package/src/renderer/src/components/ChatPanel.tsx +1598 -0
- package/src/renderer/src/components/DashboardSidebar.tsx +358 -0
- package/src/renderer/src/components/Live2DStage.tsx +374 -0
- package/src/renderer/src/components/MintDashboard.tsx +950 -0
- package/src/renderer/src/components/ModelPanel.tsx +154 -0
- package/src/renderer/src/components/PicturesLibrary.tsx +46 -0
- package/src/renderer/src/components/ProactiveGlow.tsx +19 -0
- package/src/renderer/src/components/ScreenPicker.tsx +579 -0
- package/src/renderer/src/components/SettingsWindow.tsx +1467 -0
- package/src/renderer/src/components/SpotlightWindow.tsx +280 -0
- package/src/renderer/src/components/WidgetWindow.tsx +36 -0
- package/src/renderer/src/components/WorkspacePanel.tsx +268 -0
- package/src/{UI → renderer/src/css}/settings.css +69 -16
- package/src/renderer/src/css/spotlight.css +113 -0
- package/src/renderer/src/css/styles.css +3722 -0
- package/src/renderer/src/css/widget.css +185 -0
- package/src/renderer/src/env.d.ts +116 -0
- package/src/renderer/src/index.css +379 -0
- package/src/renderer/src/main.tsx +13 -0
- package/src/renderer/src/tauri.ts +996 -0
- package/src/renderer/src-web/App.tsx +25 -0
- package/src/renderer/src-web/calculator.ts +47 -0
- package/src/renderer/src-web/components/ChatPanel.tsx +1662 -0
- package/src/renderer/src-web/components/DashboardSidebar.tsx +242 -0
- package/src/renderer/src-web/components/MintDashboard.tsx +763 -0
- package/src/renderer/src-web/components/PicturesLibrary.tsx +73 -0
- package/src/renderer/src-web/components/SettingsWindow.tsx +1500 -0
- package/src/renderer/src-web/css/settings.css +1100 -0
- package/src/{UI → renderer/src-web/css}/spotlight.css +4 -4
- package/src/{UI → renderer/src-web/css}/styles.css +1055 -159
- package/src/{UI → renderer/src-web/css}/widget.css +2 -2
- package/src/renderer/src-web/env.d.ts +107 -0
- package/src/renderer/src-web/index.css +379 -0
- package/src/renderer/src-web/main.tsx +13 -0
- package/src/renderer/src-web/tauri.ts +983 -0
- package/tsconfig.json +30 -0
- package/vite.config.ts +33 -0
- package/vite.config.web.ts +51 -0
- package/GUIDE_TH.md +0 -125
- package/assets/Agent_Mint.png +0 -0
- package/assets/CLI_Screen.png +0 -0
- package/assets/Settings.png +0 -0
- package/benchmark_ai.js +0 -71
- package/install.ps1 +0 -64
- package/install.sh +0 -54
- package/main.js +0 -139
- package/mint-cli-logic.js +0 -3
- package/mint-cli.js +0 -410
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +0 -47
- package/models/Shiroko_Model/Shiroko//342/232/241/351/253/230/344/272/256/342/232/241/344/275/277/347/224/250/346/225/231/347/250/213/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.txt +0 -23
- package/preload-picker.js +0 -11
- package/preload-settings.js +0 -11
- package/preload.js +0 -41
- package/scripts/install_linux_desktop_entry.js +0 -48
- package/src/AI_Brain/Gemini_API.js +0 -813
- package/src/AI_Brain/agent_orchestrator.js +0 -73
- package/src/AI_Brain/autonomous_brain.js +0 -179
- package/src/AI_Brain/behavior_memory.js +0 -135
- package/src/AI_Brain/headless_agent.js +0 -143
- package/src/AI_Brain/knowledge_base.js +0 -349
- package/src/AI_Brain/memory_store.js +0 -662
- package/src/AI_Brain/proactive_engine.js +0 -172
- package/src/AI_Brain/provider_adapter.js +0 -365
- package/src/Automation_Layer/browser_automation.js +0 -149
- package/src/Automation_Layer/file_operations.js +0 -286
- package/src/Automation_Layer/open_app.js +0 -85
- package/src/Automation_Layer/open_website.js +0 -38
- package/src/CLI/approval_handler.js +0 -47
- package/src/CLI/chat_router.js +0 -247
- package/src/CLI/chat_ui.js +0 -1159
- package/src/CLI/cli_colors.js +0 -115
- package/src/CLI/cli_formatters.js +0 -94
- package/src/CLI/code_agent.js +0 -1667
- package/src/CLI/code_session_memory.js +0 -62
- package/src/CLI/gmail_auth.js +0 -210
- package/src/CLI/image_input.js +0 -90
- package/src/CLI/intent_detectors.js +0 -181
- package/src/CLI/interactive_chat.js +0 -658
- package/src/CLI/list_features.js +0 -64
- package/src/CLI/onboarding.js +0 -416
- package/src/CLI/repo_summarizer.js +0 -282
- package/src/CLI/semantic_code_search.js +0 -312
- package/src/CLI/skill_manager.js +0 -41
- package/src/CLI/slash_command_handler.js +0 -418
- package/src/CLI/symbol_indexer.js +0 -231
- package/src/CLI/updater.js +0 -230
- package/src/CLI/workspace_manager.js +0 -90
- package/src/Channels/brave_search_bridge.js +0 -35
- package/src/Channels/discord_bridge.js +0 -66
- package/src/Channels/google_search_bridge.js +0 -38
- package/src/Channels/line_bridge.js +0 -60
- package/src/Channels/slack_bridge.js +0 -48
- package/src/Channels/telegram_bridge.js +0 -41
- package/src/Channels/whatsapp_bridge.js +0 -57
- package/src/Command_Parser/parser.js +0 -45
- package/src/Plugins/dev_tools.js +0 -41
- package/src/Plugins/discord.js +0 -20
- package/src/Plugins/docker.js +0 -47
- package/src/Plugins/gmail.js +0 -251
- package/src/Plugins/google_calendar.js +0 -252
- package/src/Plugins/mcp_manager.js +0 -95
- package/src/Plugins/notion.js +0 -256
- package/src/Plugins/obsidian.js +0 -54
- package/src/Plugins/plugin_manager.js +0 -81
- package/src/Plugins/spotify.js +0 -173
- package/src/Plugins/system_metrics.js +0 -31
- package/src/Plugins/system_monitor.js +0 -72
- package/src/System/action_executor.js +0 -178
- package/src/System/bridge_manager.js +0 -76
- package/src/System/chat_history_manager.js +0 -83
- package/src/System/config_manager.js +0 -194
- package/src/System/custom_workflows.js +0 -163
- package/src/System/daemon_manager.js +0 -67
- package/src/System/google_tts_urls.js +0 -51
- package/src/System/granular_automation.js +0 -157
- package/src/System/ipc_handlers.js +0 -332
- package/src/System/notifications.js +0 -23
- package/src/System/optional_require.js +0 -23
- package/src/System/picture_store.js +0 -109
- package/src/System/proactive_loop.js +0 -153
- package/src/System/safety_manager.js +0 -273
- package/src/System/sandbox_runner.js +0 -182
- package/src/System/screen_capture.js +0 -175
- package/src/System/smart_context.js +0 -227
- package/src/System/system_automation.js +0 -162
- package/src/System/system_events.js +0 -79
- package/src/System/system_info.js +0 -125
- package/src/System/task_manager.js +0 -222
- package/src/System/tool_registry.js +0 -293
- package/src/System/window_manager.js +0 -220
- package/src/UI/floating.css +0 -80
- package/src/UI/floating.html +0 -17
- package/src/UI/floating.js +0 -67
- package/src/UI/live2d_manager.js +0 -600
- package/src/UI/preload-floating.js +0 -7
- package/src/UI/preload-spotlight.js +0 -11
- package/src/UI/preload-widget.js +0 -5
- package/src/UI/proactive-glow.html +0 -42
- package/src/UI/renderer.js +0 -2127
- package/src/UI/screenPicker.html +0 -214
- package/src/UI/screenPicker.js +0 -262
- package/src/UI/settings.html +0 -577
- package/src/UI/settings.js +0 -770
- package/src/UI/spotlight.html +0 -23
- package/src/UI/spotlight.js +0 -185
- package/src/UI/widget.html +0 -29
- package/src/UI/widget.js +0 -10
- /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/apron.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/catfilter.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/click.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazed.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253/347/234/274/347/217/240/346/221/207/346/231/203.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/dazedeyes.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/glasses.exp3.json} +0 -0
- /package/{models → src/renderer/public/models}/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/pen.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/photo.exp3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_00.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_01.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_02.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.4096/texture_03.png} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.cdi3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.moc3} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.physics3.json} +0 -0
- /package/{models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json" → src/renderer/public/models/Shiroko_Model/Shiroko/Shiroko_Core/shiroko.vtube.json} +0 -0
|
@@ -0,0 +1,996 @@
|
|
|
1
|
+
export interface RuntimeStatus {
|
|
2
|
+
backend: string
|
|
3
|
+
configPath: string
|
|
4
|
+
activeProvider: string
|
|
5
|
+
availableProviders: string[]
|
|
6
|
+
integrations: Record<string, unknown>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ChatResponse {
|
|
10
|
+
provider: string
|
|
11
|
+
model: string
|
|
12
|
+
text: string
|
|
13
|
+
fallbackProvider?: string | null
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface TtsUrl {
|
|
17
|
+
shortText: string
|
|
18
|
+
url: string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface DocumentAttachment {
|
|
22
|
+
filename: string
|
|
23
|
+
dataUri: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type AgentProgress =
|
|
27
|
+
| { type: 'Thinking'; data: { elapsed_secs: number } }
|
|
28
|
+
| { type: 'Thought'; data: { thought: string } }
|
|
29
|
+
| { type: 'ToolStart'; data: { action: string; input: Record<string, unknown> } }
|
|
30
|
+
| { type: 'ToolEnd'; data: { action: string; input: Record<string, unknown>; result: string } }
|
|
31
|
+
|
|
32
|
+
type DesktopStreamEvent =
|
|
33
|
+
| { type: 'chunk'; chunk: string }
|
|
34
|
+
| { type: 'progress'; progress: AgentProgress }
|
|
35
|
+
|
|
36
|
+
export interface InteractionMemory {
|
|
37
|
+
id: number
|
|
38
|
+
chatId: string
|
|
39
|
+
userText: string
|
|
40
|
+
aiText: string
|
|
41
|
+
provider: string
|
|
42
|
+
model: string
|
|
43
|
+
fallbackProvider?: string | null
|
|
44
|
+
createdAt: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface ChatSession {
|
|
48
|
+
id: string
|
|
49
|
+
title: string
|
|
50
|
+
kind: string
|
|
51
|
+
createdAt: string
|
|
52
|
+
updatedAt: string
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface PictureEntry {
|
|
56
|
+
id: string
|
|
57
|
+
filename: string
|
|
58
|
+
path: string
|
|
59
|
+
mimeType: string
|
|
60
|
+
createdAt: string
|
|
61
|
+
source: string
|
|
62
|
+
message: string
|
|
63
|
+
thumbnailPath?: string
|
|
64
|
+
url?: string
|
|
65
|
+
thumbnailUrl?: string
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface WorkspaceTreeEntry {
|
|
69
|
+
name: string
|
|
70
|
+
path: string
|
|
71
|
+
kind: 'file' | 'directory'
|
|
72
|
+
children: WorkspaceTreeEntry[]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface CodeEdit {
|
|
76
|
+
path: string
|
|
77
|
+
content: string
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface CodeEditProposal {
|
|
81
|
+
approvalRequired: boolean
|
|
82
|
+
approvalToken: string
|
|
83
|
+
edits: Array<{ path: string; existed: boolean; diff: string }>
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function getRuntimeStatus(): Promise<RuntimeStatus> {
|
|
87
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
88
|
+
const API_BASE = "http://localhost:3000/api";
|
|
89
|
+
try {
|
|
90
|
+
const res = await fetch(`${API_BASE}/status`);
|
|
91
|
+
return await res.json();
|
|
92
|
+
} catch (e) {
|
|
93
|
+
console.error("Failed to fetch runtime status from local server:", e);
|
|
94
|
+
return {
|
|
95
|
+
backend: 'browser-fallback',
|
|
96
|
+
configPath: '',
|
|
97
|
+
activeProvider: '',
|
|
98
|
+
availableProviders: [],
|
|
99
|
+
integrations: {}
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
104
|
+
return invoke<RuntimeStatus>('get_runtime_status')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function sendChatMessage(
|
|
108
|
+
message: string,
|
|
109
|
+
imageDataUri?: string | null,
|
|
110
|
+
audioDataUri?: string | null,
|
|
111
|
+
documentAttachment?: DocumentAttachment | null,
|
|
112
|
+
workspacePath?: string | null,
|
|
113
|
+
chatId?: string | null,
|
|
114
|
+
): Promise<ChatResponse> {
|
|
115
|
+
const outgoingMessage = withImagePlaceholder(message, imageDataUri)
|
|
116
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
117
|
+
const API_BASE = "http://localhost:3000/api";
|
|
118
|
+
try {
|
|
119
|
+
const res = await fetch(`${API_BASE}/chat`, {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
headers: { 'Content-Type': 'application/json' },
|
|
122
|
+
body: JSON.stringify({ message: outgoingMessage, systemInstruction: '', chatId, imageDataUri, audioDataUri, documentAttachment })
|
|
123
|
+
});
|
|
124
|
+
const data = await res.json().catch(() => null);
|
|
125
|
+
if (!res.ok) {
|
|
126
|
+
return {
|
|
127
|
+
provider: 'error',
|
|
128
|
+
model: 'error',
|
|
129
|
+
text: data?.text || data?.message || data?.status || `Local API returned HTTP ${res.status}`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (!data || typeof data.text !== 'string') {
|
|
133
|
+
return { provider: 'error', model: 'error', text: 'Local API returned an invalid chat response.' };
|
|
134
|
+
}
|
|
135
|
+
return data;
|
|
136
|
+
} catch (e) {
|
|
137
|
+
console.error("Failed to send chat message to local server:", e);
|
|
138
|
+
return { provider: 'error', model: 'error', text: `Failed to connect to Local API Server: ${e}` };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
142
|
+
const response = await invoke<ChatResponse>('send_chat_message', {
|
|
143
|
+
request: { message: outgoingMessage, systemInstruction: '', chatId, imageDataUri, audioDataUri, documentAttachment, workspacePath },
|
|
144
|
+
})
|
|
145
|
+
if (imageDataUri) {
|
|
146
|
+
await invoke('save_pictures', {
|
|
147
|
+
images: imageDataUri.split(' '),
|
|
148
|
+
source: 'chat',
|
|
149
|
+
message: outgoingMessage,
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
return response
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function streamChatMessage(
|
|
156
|
+
message: string,
|
|
157
|
+
onChunk: (chunk: string) => void,
|
|
158
|
+
imageDataUri?: string | null,
|
|
159
|
+
audioDataUri?: string | null,
|
|
160
|
+
systemInstruction = '',
|
|
161
|
+
onProgress?: (progress: AgentProgress) => void,
|
|
162
|
+
documentAttachment?: DocumentAttachment | null,
|
|
163
|
+
workspacePath?: string | null,
|
|
164
|
+
chatId?: string | null,
|
|
165
|
+
): Promise<ChatResponse> {
|
|
166
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
167
|
+
const API_BASE = "http://localhost:3000/api";
|
|
168
|
+
const outgoingMessage = withImagePlaceholder(message, imageDataUri);
|
|
169
|
+
const res = await fetch(`${API_BASE}/chat-stream`, {
|
|
170
|
+
method: 'POST',
|
|
171
|
+
headers: { 'Content-Type': 'application/json' },
|
|
172
|
+
body: JSON.stringify({ message: outgoingMessage, systemInstruction, chatId, imageDataUri, audioDataUri, documentAttachment })
|
|
173
|
+
});
|
|
174
|
+
if (!res.ok) {
|
|
175
|
+
const data = await res.json().catch(() => null);
|
|
176
|
+
throw new Error(data?.text || data?.message || `HTTP ${res.status}`);
|
|
177
|
+
}
|
|
178
|
+
const reader = res.body?.getReader();
|
|
179
|
+
if (!reader) {
|
|
180
|
+
throw new Error("No response body reader");
|
|
181
|
+
}
|
|
182
|
+
const decoder = new TextDecoder();
|
|
183
|
+
let buffer = '';
|
|
184
|
+
let finalResponse: ChatResponse | null = null;
|
|
185
|
+
while (true) {
|
|
186
|
+
const { value, done } = await reader.read();
|
|
187
|
+
if (done) break;
|
|
188
|
+
buffer += decoder.decode(value, { stream: true });
|
|
189
|
+
const lines = buffer.split('\n');
|
|
190
|
+
buffer = lines.pop() || '';
|
|
191
|
+
for (const line of lines) {
|
|
192
|
+
if (!line.trim()) continue;
|
|
193
|
+
try {
|
|
194
|
+
const event = JSON.parse(line);
|
|
195
|
+
if (event.type === 'chunk') {
|
|
196
|
+
onChunk(event.chunk);
|
|
197
|
+
} else if (event.type === 'progress') {
|
|
198
|
+
onProgress?.(event.progress);
|
|
199
|
+
} else if (event.type === 'done') {
|
|
200
|
+
finalResponse = event.response;
|
|
201
|
+
}
|
|
202
|
+
} catch (e) {
|
|
203
|
+
console.error("Failed to parse stream line:", line, e);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (finalResponse) return finalResponse;
|
|
208
|
+
throw new Error("Stream closed without a final response");
|
|
209
|
+
}
|
|
210
|
+
const { invoke, Channel } = await import('@tauri-apps/api/core')
|
|
211
|
+
const outgoingMessage = withImagePlaceholder(message, imageDataUri)
|
|
212
|
+
const onEvent = new Channel<DesktopStreamEvent>()
|
|
213
|
+
onEvent.onmessage = (event) => {
|
|
214
|
+
if (event.type === 'chunk') onChunk(event.chunk)
|
|
215
|
+
else onProgress?.(event.progress)
|
|
216
|
+
}
|
|
217
|
+
const response = await invoke<ChatResponse>('stream_chat_message', {
|
|
218
|
+
request: { message: outgoingMessage, systemInstruction, chatId, imageDataUri, audioDataUri, documentAttachment, workspacePath },
|
|
219
|
+
onEvent,
|
|
220
|
+
})
|
|
221
|
+
if (imageDataUri) {
|
|
222
|
+
await invoke('save_pictures', {
|
|
223
|
+
images: imageDataUri.split(' '),
|
|
224
|
+
source: 'chat',
|
|
225
|
+
message: outgoingMessage,
|
|
226
|
+
})
|
|
227
|
+
}
|
|
228
|
+
return response
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function withImagePlaceholder(message: string, imageDataUri?: string | null) {
|
|
232
|
+
if (!imageDataUri || message.includes('[Image #1]')) return message
|
|
233
|
+
const imageCount = imageDataUri.split(/\s+/).filter(Boolean).length
|
|
234
|
+
const markers = Array.from({ length: imageCount }, (_, index) => `[Image #${index + 1}]`).join(' ')
|
|
235
|
+
return markers ? `${message} ${markers}` : message
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export async function getTtsUrls(text: string): Promise<TtsUrl[]> {
|
|
239
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) return []
|
|
240
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
241
|
+
return invoke<TtsUrl[]>('get_tts_urls', { text })
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export async function getRecentInteractions(limit = 50, chatId?: string | null): Promise<InteractionMemory[]> {
|
|
245
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
246
|
+
const API_BASE = "http://localhost:3000/api";
|
|
247
|
+
try {
|
|
248
|
+
const params = new URLSearchParams({ limit: String(limit) });
|
|
249
|
+
if (chatId) params.set('chatId', chatId);
|
|
250
|
+
const res = await fetch(`${API_BASE}/interactions?${params.toString()}`);
|
|
251
|
+
return await res.json();
|
|
252
|
+
} catch (e) {
|
|
253
|
+
console.error("Failed to fetch chat history from local server:", e);
|
|
254
|
+
return [];
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
258
|
+
return invoke<InteractionMemory[]>('get_recent_interactions', { limit, chatId })
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
export async function listChatSessions(): Promise<ChatSession[]> {
|
|
262
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
263
|
+
const API_BASE = "http://localhost:3000/api";
|
|
264
|
+
try {
|
|
265
|
+
const res = await fetch(`${API_BASE}/chat-sessions`);
|
|
266
|
+
const data = await res.json();
|
|
267
|
+
return Array.isArray(data) ? data : [];
|
|
268
|
+
} catch (e) {
|
|
269
|
+
console.error("Failed to fetch chat sessions from local server:", e);
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
274
|
+
return invoke<ChatSession[]>('list_chat_sessions')
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export async function deleteChatSession(chatId: string): Promise<number> {
|
|
278
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
279
|
+
const API_BASE = "http://localhost:3000/api";
|
|
280
|
+
try {
|
|
281
|
+
const params = new URLSearchParams({ chatId });
|
|
282
|
+
const res = await fetch(`${API_BASE}/chat-sessions/delete?${params.toString()}`, { method: 'POST' });
|
|
283
|
+
const data = await res.json();
|
|
284
|
+
return typeof data?.deleted === 'number' ? data.deleted : 0;
|
|
285
|
+
} catch (e) {
|
|
286
|
+
console.error("Failed to delete chat session on local server:", e);
|
|
287
|
+
return 0;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
291
|
+
return invoke<number>('delete_chat_session', { chatId })
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
export async function renameChatSession(chatId: string, newTitle: string): Promise<number> {
|
|
295
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
296
|
+
const API_BASE = "http://localhost:3000/api";
|
|
297
|
+
try {
|
|
298
|
+
const res = await fetch(`${API_BASE}/chat-sessions/rename`, {
|
|
299
|
+
method: 'POST',
|
|
300
|
+
headers: { 'Content-Type': 'application/json' },
|
|
301
|
+
body: JSON.stringify({ chatId, newTitle })
|
|
302
|
+
});
|
|
303
|
+
const data = await res.json();
|
|
304
|
+
return typeof data?.updated === 'number' ? data.updated : 0;
|
|
305
|
+
} catch (e) {
|
|
306
|
+
console.error("Failed to rename chat session on local server:", e);
|
|
307
|
+
return 0;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
311
|
+
return invoke<number>('rename_chat_session', { chatId, newTitle })
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
export async function getProfileValue(key: string): Promise<string> {
|
|
315
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
316
|
+
const API_BASE = "http://localhost:3000/api";
|
|
317
|
+
try {
|
|
318
|
+
const params = new URLSearchParams({ key });
|
|
319
|
+
const res = await fetch(`${API_BASE}/profile?${params.toString()}`);
|
|
320
|
+
const data = await res.json();
|
|
321
|
+
return data.value || '';
|
|
322
|
+
} catch (e) {
|
|
323
|
+
console.error("Failed to get profile key from local server:", e);
|
|
324
|
+
return '';
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
328
|
+
return invoke<string | null>('get_profile_value', { key }).then(res => res || '')
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
export async function setProfileValue(key: string, value: string): Promise<boolean> {
|
|
332
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
333
|
+
const API_BASE = "http://localhost:3000/api";
|
|
334
|
+
try {
|
|
335
|
+
const res = await fetch(`${API_BASE}/profile`, {
|
|
336
|
+
method: 'POST',
|
|
337
|
+
headers: { 'Content-Type': 'application/json' },
|
|
338
|
+
body: JSON.stringify({ key, value })
|
|
339
|
+
});
|
|
340
|
+
const data = await res.json();
|
|
341
|
+
return data.status === 'ok';
|
|
342
|
+
} catch (e) {
|
|
343
|
+
console.error("Failed to set profile key on local server:", e);
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
348
|
+
return invoke<void>('set_profile_value', { key, value }).then(() => true).catch(() => false)
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
export async function clearChatHistory(chatId?: string | null): Promise<number> {
|
|
352
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
353
|
+
const API_BASE = "http://localhost:3000/api";
|
|
354
|
+
try {
|
|
355
|
+
const params = new URLSearchParams();
|
|
356
|
+
if (chatId) params.set('chatId', chatId);
|
|
357
|
+
const suffix = params.toString() ? `?${params.toString()}` : '';
|
|
358
|
+
const res = await fetch(`${API_BASE}/interactions/clear${suffix}`, { method: 'POST' });
|
|
359
|
+
const data = await res.json();
|
|
360
|
+
return data.status === 'ok' ? 1 : 0;
|
|
361
|
+
} catch (e) {
|
|
362
|
+
console.error("Failed to clear chat history on local server:", e);
|
|
363
|
+
return 0;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
367
|
+
return invoke<number>('clear_chat_history', { chatId })
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
export async function listSavedPictures(): Promise<PictureEntry[]> {
|
|
371
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
372
|
+
const API_BASE = "http://localhost:3000/api";
|
|
373
|
+
try {
|
|
374
|
+
const res = await fetch(`${API_BASE}/pictures`);
|
|
375
|
+
const pictures = await res.json();
|
|
376
|
+
return Array.isArray(pictures)
|
|
377
|
+
? pictures.map((picture) => {
|
|
378
|
+
const pictureUrl = picture.url ? `${API_BASE.replace('/api', '')}${picture.url}` : undefined
|
|
379
|
+
return {
|
|
380
|
+
...picture,
|
|
381
|
+
path: pictureUrl || picture.path,
|
|
382
|
+
thumbnailPath: undefined,
|
|
383
|
+
thumbnailUrl: pictureUrl,
|
|
384
|
+
url: pictureUrl || picture.url,
|
|
385
|
+
}
|
|
386
|
+
})
|
|
387
|
+
: [];
|
|
388
|
+
} catch (e) {
|
|
389
|
+
console.error("Failed to fetch saved pictures from local server:", e);
|
|
390
|
+
return [];
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
394
|
+
return invoke<PictureEntry[]>('list_pictures')
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
export async function getWorkspaceTree(path?: string | null): Promise<WorkspaceTreeEntry> {
|
|
398
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
399
|
+
return {
|
|
400
|
+
name: 'Workspace',
|
|
401
|
+
path: '.',
|
|
402
|
+
kind: 'directory',
|
|
403
|
+
children: [
|
|
404
|
+
{ name: 'src', path: 'src', kind: 'directory', children: [] },
|
|
405
|
+
{ name: 'package.json', path: 'package.json', kind: 'file', children: [] },
|
|
406
|
+
],
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
410
|
+
return invoke<WorkspaceTreeEntry>('get_workspace_tree', { path })
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
export async function selectWorkspaceDirectory(): Promise<string | null> {
|
|
414
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
415
|
+
return null
|
|
416
|
+
}
|
|
417
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
418
|
+
const selected = await invoke<string | null>('select_workspace_directory')
|
|
419
|
+
return selected?.trim() || null
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
export async function submitToolApproval(token: string, approved: boolean): Promise<void> {
|
|
423
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
427
|
+
return invoke('submit_tool_approval', { token, approved })
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
export async function proposeCodeEdits(root: string, edits: CodeEdit[]): Promise<CodeEditProposal> {
|
|
431
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
432
|
+
return { approvalRequired: false, approvalToken: '', edits: [] };
|
|
433
|
+
}
|
|
434
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
435
|
+
return invoke<CodeEditProposal>('propose_desktop_code_edits', { root, edits })
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export async function applyCodeEdits(root: string, edits: CodeEdit[], approvalToken: string) {
|
|
439
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
440
|
+
return;
|
|
441
|
+
}
|
|
442
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
443
|
+
return invoke('apply_desktop_code_edits', { root, edits, approvalToken })
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export async function listen<T>(event: string, handler: (event: { payload: T }) => void): Promise<() => void> {
|
|
447
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
448
|
+
return () => {};
|
|
449
|
+
}
|
|
450
|
+
const { listen: tauriListen } = await import('@tauri-apps/api/event');
|
|
451
|
+
return tauriListen<T>(event, handler);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export function convertFileSrc(filePath: string, protocol = 'asset'): string {
|
|
455
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
456
|
+
return filePath;
|
|
457
|
+
}
|
|
458
|
+
const internals = (window as any).__TAURI_INTERNALS__;
|
|
459
|
+
if (internals && typeof internals.convertFileSrc === 'function') {
|
|
460
|
+
return internals.convertFileSrc(filePath, protocol);
|
|
461
|
+
}
|
|
462
|
+
const path = filePath.startsWith('\\\\?\\') ? filePath.substring(4) : filePath;
|
|
463
|
+
return `https://asset.localhost/${encodeURIComponent(path)}`;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export function installTauriAdapters() {
|
|
467
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
468
|
+
console.warn("Not running inside Tauri. Connecting to local API server fallback at http://localhost:3000/api.");
|
|
469
|
+
const API_BASE = "http://localhost:3000/api";
|
|
470
|
+
|
|
471
|
+
(window as any).settingsApi = {
|
|
472
|
+
getSettings: async () => {
|
|
473
|
+
try {
|
|
474
|
+
const res = await fetch(`${API_BASE}/config`);
|
|
475
|
+
return await res.json();
|
|
476
|
+
} catch (e) {
|
|
477
|
+
console.error("Failed to fetch settings from local server:", e);
|
|
478
|
+
return {};
|
|
479
|
+
}
|
|
480
|
+
},
|
|
481
|
+
getUpdaterStatus: async () => ({}),
|
|
482
|
+
checkForUpdates: async () => ({}),
|
|
483
|
+
installAvailableUpdate: async () => {},
|
|
484
|
+
saveSettings: async (config: any) => {
|
|
485
|
+
try {
|
|
486
|
+
const res = await fetch(`${API_BASE}/config`, {
|
|
487
|
+
method: 'POST',
|
|
488
|
+
headers: { 'Content-Type': 'application/json' },
|
|
489
|
+
body: JSON.stringify(config)
|
|
490
|
+
});
|
|
491
|
+
return await res.json();
|
|
492
|
+
} catch (e) {
|
|
493
|
+
console.error("Failed to save settings to local server:", e);
|
|
494
|
+
return {};
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
closeSettings: () => {
|
|
498
|
+
window.location.hash = '#/';
|
|
499
|
+
},
|
|
500
|
+
quitApp: () => {},
|
|
501
|
+
openExternal: () => {},
|
|
502
|
+
openFolder: () => {},
|
|
503
|
+
openCustomWorkflows: () => {},
|
|
504
|
+
reloadCustomWorkflows: () => {},
|
|
505
|
+
};
|
|
506
|
+
|
|
507
|
+
(window as any).spotlightAPI = {
|
|
508
|
+
submit: () => {},
|
|
509
|
+
executeAction: async (action: any) => {
|
|
510
|
+
try {
|
|
511
|
+
const res = await fetch(`${API_BASE}/action`, {
|
|
512
|
+
method: 'POST',
|
|
513
|
+
headers: { 'Content-Type': 'application/json' },
|
|
514
|
+
body: JSON.stringify(action)
|
|
515
|
+
});
|
|
516
|
+
return await res.json();
|
|
517
|
+
} catch (e) {
|
|
518
|
+
return { success: false, message: String(e) };
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
close: () => {},
|
|
522
|
+
hide: () => {},
|
|
523
|
+
resize: () => {},
|
|
524
|
+
getSettings: async () => {
|
|
525
|
+
try {
|
|
526
|
+
const res = await fetch(`${API_BASE}/config`);
|
|
527
|
+
return await res.json();
|
|
528
|
+
} catch (e) {
|
|
529
|
+
return {};
|
|
530
|
+
}
|
|
531
|
+
},
|
|
532
|
+
onSettingsChanged: () => {},
|
|
533
|
+
};
|
|
534
|
+
|
|
535
|
+
(window as any).widgetAPI = {
|
|
536
|
+
onStateChange: () => {},
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
(window as any).screenPickerApi = {
|
|
540
|
+
onScreenshot: () => {},
|
|
541
|
+
sendSelection: () => {},
|
|
542
|
+
startContinuousTranslation: () => {},
|
|
543
|
+
stopContinuousTranslation: () => {},
|
|
544
|
+
onTranslationResult: () => {},
|
|
545
|
+
closePicker: () => {},
|
|
546
|
+
setOverlayInteractable: () => {},
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
(window as any).api = {
|
|
550
|
+
sendMessage: async (message: string, imageDataUri?: string | null, audioDataUri?: string | null, documentAttachment?: DocumentAttachment | null) => {
|
|
551
|
+
try {
|
|
552
|
+
const res = await fetch(`${API_BASE}/chat`, {
|
|
553
|
+
method: 'POST',
|
|
554
|
+
headers: { 'Content-Type': 'application/json' },
|
|
555
|
+
body: JSON.stringify({ message, imageDataUri, audioDataUri, documentAttachment })
|
|
556
|
+
});
|
|
557
|
+
const data = await res.json().catch(() => null);
|
|
558
|
+
if (!res.ok) {
|
|
559
|
+
return {
|
|
560
|
+
provider: 'error',
|
|
561
|
+
model: 'error',
|
|
562
|
+
text: data?.text || data?.message || data?.status || `Local API returned HTTP ${res.status}`,
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
if (!data || typeof data.text !== 'string') {
|
|
566
|
+
return { provider: 'error', model: 'error', text: 'Local API returned an invalid chat response.' };
|
|
567
|
+
}
|
|
568
|
+
return data;
|
|
569
|
+
} catch (e) {
|
|
570
|
+
console.error("Failed to send message to local server:", e);
|
|
571
|
+
return { provider: 'error', model: 'error', text: `Failed to connect to Local API Server: ${e}` };
|
|
572
|
+
}
|
|
573
|
+
},
|
|
574
|
+
closeWindow: () => {},
|
|
575
|
+
minimizeWindow: () => {},
|
|
576
|
+
quitApp: () => {},
|
|
577
|
+
maximizeWindow: () => {},
|
|
578
|
+
resetChat: async () => {
|
|
579
|
+
try {
|
|
580
|
+
const res = await fetch(`${API_BASE}/interactions/clear`, { method: 'POST' });
|
|
581
|
+
const data = await res.json();
|
|
582
|
+
return data.status === 'ok' ? 1 : 0;
|
|
583
|
+
} catch (e) {
|
|
584
|
+
return 0;
|
|
585
|
+
}
|
|
586
|
+
},
|
|
587
|
+
getChatHistory: async () => {
|
|
588
|
+
try {
|
|
589
|
+
const res = await fetch(`${API_BASE}/interactions`);
|
|
590
|
+
return await res.json();
|
|
591
|
+
} catch (e) {
|
|
592
|
+
console.error("Failed to fetch chat history from local server:", e);
|
|
593
|
+
return [];
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
listSavedPictures,
|
|
597
|
+
openSettings: () => {
|
|
598
|
+
window.location.hash = '#/settings';
|
|
599
|
+
},
|
|
600
|
+
readClipboard: async () => '',
|
|
601
|
+
writeClipboard: async () => {},
|
|
602
|
+
getSystemInfo: async () => {
|
|
603
|
+
try {
|
|
604
|
+
const res = await fetch(`${API_BASE}/status`);
|
|
605
|
+
return await res.json();
|
|
606
|
+
} catch (e) {
|
|
607
|
+
return { backend: 'browser-fallback' };
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
getWeather: async (city: string) => {
|
|
611
|
+
try {
|
|
612
|
+
const res = await fetch(`${API_BASE}/weather?city=${encodeURIComponent(city)}`);
|
|
613
|
+
return await res.json();
|
|
614
|
+
} catch (e) {
|
|
615
|
+
return { error: String(e) };
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
getSettings: async () => {
|
|
619
|
+
try {
|
|
620
|
+
const res = await fetch(`${API_BASE}/config`);
|
|
621
|
+
return await res.json();
|
|
622
|
+
} catch (e) {
|
|
623
|
+
return {};
|
|
624
|
+
}
|
|
625
|
+
},
|
|
626
|
+
saveSettings: async (config: any) => {
|
|
627
|
+
try {
|
|
628
|
+
const res = await fetch(`${API_BASE}/config`, {
|
|
629
|
+
method: 'POST',
|
|
630
|
+
headers: { 'Content-Type': 'application/json' },
|
|
631
|
+
body: JSON.stringify(config)
|
|
632
|
+
});
|
|
633
|
+
return await res.json();
|
|
634
|
+
} catch (e) {
|
|
635
|
+
return {};
|
|
636
|
+
}
|
|
637
|
+
},
|
|
638
|
+
onSettingsChanged: () => {},
|
|
639
|
+
startVision: () => {},
|
|
640
|
+
onVisionReady: async () => () => {},
|
|
641
|
+
captureSilentScreen: async () => '',
|
|
642
|
+
getSmartContext: async () => {
|
|
643
|
+
try {
|
|
644
|
+
const res = await fetch(`${API_BASE}/smart-context`);
|
|
645
|
+
return await res.json();
|
|
646
|
+
} catch (e) {
|
|
647
|
+
return {};
|
|
648
|
+
}
|
|
649
|
+
},
|
|
650
|
+
onProactiveSuggestion: async () => () => {},
|
|
651
|
+
onProactiveNotification: async () => () => {},
|
|
652
|
+
toggleProactive: () => {},
|
|
653
|
+
recordBehavior: () => {},
|
|
654
|
+
executeProactiveAction: async (action: any) => {
|
|
655
|
+
try {
|
|
656
|
+
const res = await fetch(`${API_BASE}/action`, {
|
|
657
|
+
method: 'POST',
|
|
658
|
+
headers: { 'Content-Type': 'application/json' },
|
|
659
|
+
body: JSON.stringify(action)
|
|
660
|
+
});
|
|
661
|
+
return await res.json();
|
|
662
|
+
} catch (e) {
|
|
663
|
+
return { success: false, message: String(e) };
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
executeApprovedAction: async (action: any) => {
|
|
667
|
+
try {
|
|
668
|
+
const res = await fetch(`${API_BASE}/action`, {
|
|
669
|
+
method: 'POST',
|
|
670
|
+
headers: { 'Content-Type': 'application/json' },
|
|
671
|
+
body: JSON.stringify(action)
|
|
672
|
+
});
|
|
673
|
+
return await res.json();
|
|
674
|
+
} catch (e) {
|
|
675
|
+
return { success: false, message: String(e) };
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
onSpotlightToChat: async () => () => {},
|
|
679
|
+
notifyAiResponse: () => {},
|
|
680
|
+
clearAiNotifications: () => {},
|
|
681
|
+
getTtsUrls: async () => [],
|
|
682
|
+
setAiState: () => {},
|
|
683
|
+
};
|
|
684
|
+
return;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const settingsChanged = async (callback: (config: any) => void) => {
|
|
688
|
+
const { listen } = await import('@tauri-apps/api/event')
|
|
689
|
+
void listen<any>('settings-changed', (event) => callback(event.payload))
|
|
690
|
+
}
|
|
691
|
+
const executeAction = async (action: any, approved = false) => {
|
|
692
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
693
|
+
return action.type === 'plugin'
|
|
694
|
+
? invoke('run_native_plugin', { name: action.pluginName, instruction: action.target || '' })
|
|
695
|
+
: invoke('run_desktop_action', { action: { ...action, approved } })
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
window.settingsApi = {
|
|
699
|
+
getSettings: async () => {
|
|
700
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
701
|
+
return invoke('get_config')
|
|
702
|
+
},
|
|
703
|
+
getUpdaterStatus: async () => {
|
|
704
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
705
|
+
return invoke('get_updater_status')
|
|
706
|
+
},
|
|
707
|
+
checkForUpdates: async () => {
|
|
708
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
709
|
+
return invoke('check_for_updates')
|
|
710
|
+
},
|
|
711
|
+
installAvailableUpdate: async () => {
|
|
712
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
713
|
+
return invoke('install_available_update', { approved: true })
|
|
714
|
+
},
|
|
715
|
+
saveSettings: async (config) => {
|
|
716
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
717
|
+
return invoke('update_config', { config })
|
|
718
|
+
},
|
|
719
|
+
closeSettings: async () => {
|
|
720
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
721
|
+
const { getCurrentWindow } = await import('@tauri-apps/api/window')
|
|
722
|
+
return invoke('close_desktop_window', { label: getCurrentWindow().label })
|
|
723
|
+
},
|
|
724
|
+
quitApp: async () => {
|
|
725
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
726
|
+
return void invoke('exit_app')
|
|
727
|
+
},
|
|
728
|
+
openExternal: async (url) => {
|
|
729
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
730
|
+
return invoke('run_desktop_action', { action: { type: 'open_url', target: url } })
|
|
731
|
+
},
|
|
732
|
+
openFolder: async (path) => {
|
|
733
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
734
|
+
return invoke('open_folder', { path })
|
|
735
|
+
},
|
|
736
|
+
openCustomWorkflows: async () => {
|
|
737
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
738
|
+
return invoke('open_workflows_file')
|
|
739
|
+
},
|
|
740
|
+
reloadCustomWorkflows: async () => {
|
|
741
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
742
|
+
return invoke('reload_custom_workflows')
|
|
743
|
+
},
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
window.spotlightAPI = {
|
|
747
|
+
submit: async (query) => {
|
|
748
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
749
|
+
return void invoke('submit_spotlight', { query })
|
|
750
|
+
},
|
|
751
|
+
executeAction: async (action) => {
|
|
752
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
753
|
+
if (action.type === 'clipboard_write') {
|
|
754
|
+
await navigator.clipboard.writeText(action.target)
|
|
755
|
+
return { success: true }
|
|
756
|
+
}
|
|
757
|
+
return invoke('run_desktop_action', { action })
|
|
758
|
+
},
|
|
759
|
+
close: async () => {
|
|
760
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
761
|
+
const { getCurrentWindow } = await import('@tauri-apps/api/window')
|
|
762
|
+
return invoke('close_desktop_window', { label: getCurrentWindow().label })
|
|
763
|
+
},
|
|
764
|
+
hide: async () => {
|
|
765
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
766
|
+
const { getCurrentWindow } = await import('@tauri-apps/api/window')
|
|
767
|
+
return invoke('hide_desktop_window', { label: getCurrentWindow().label })
|
|
768
|
+
},
|
|
769
|
+
resize: async (width, height) => {
|
|
770
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
771
|
+
const { getCurrentWindow } = await import('@tauri-apps/api/window')
|
|
772
|
+
return void invoke('resize_desktop_window', {
|
|
773
|
+
label: getCurrentWindow().label,
|
|
774
|
+
width,
|
|
775
|
+
height,
|
|
776
|
+
})
|
|
777
|
+
},
|
|
778
|
+
getSettings: async () => {
|
|
779
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
780
|
+
return invoke('get_config')
|
|
781
|
+
},
|
|
782
|
+
onSettingsChanged: settingsChanged,
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
window.widgetAPI = {
|
|
786
|
+
onStateChange: async (callback) => {
|
|
787
|
+
const { listen } = await import('@tauri-apps/api/event')
|
|
788
|
+
void listen<string>('widget-state', (event) => callback(event.payload))
|
|
789
|
+
},
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
window.screenPickerApi = {
|
|
793
|
+
onScreenshot: async (callback) => {
|
|
794
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
795
|
+
try {
|
|
796
|
+
const image = await captureSharedScreen()
|
|
797
|
+
callback(image)
|
|
798
|
+
} catch (reason) {
|
|
799
|
+
console.warn('Screen share capture failed, falling back to native capture:', reason)
|
|
800
|
+
void invoke<string>('capture_silent_screen').then(callback)
|
|
801
|
+
}
|
|
802
|
+
},
|
|
803
|
+
sendSelection: async (image) => {
|
|
804
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
805
|
+
return void invoke('submit_screen_selection', { image })
|
|
806
|
+
},
|
|
807
|
+
startContinuousTranslation: (rect) => {
|
|
808
|
+
let translationTimer: ReturnType<typeof setInterval> | null = null
|
|
809
|
+
const translate = async () => {
|
|
810
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
811
|
+
void invoke<string>('translate_capture_region', { rect })
|
|
812
|
+
.then((text) => window.dispatchEvent(new CustomEvent('mint-translation', { detail: text })))
|
|
813
|
+
.catch((reason) => {
|
|
814
|
+
window.dispatchEvent(new CustomEvent('mint-translation', { detail: String(reason) }))
|
|
815
|
+
})
|
|
816
|
+
}
|
|
817
|
+
translate()
|
|
818
|
+
translationTimer = setInterval(translate, 3000)
|
|
819
|
+
|
|
820
|
+
// Clean up helper attached to window if needed
|
|
821
|
+
if ((window as any)._stopTranslate) (window as any)._stopTranslate()
|
|
822
|
+
;(window as any)._stopTranslate = () => {
|
|
823
|
+
if (translationTimer) clearInterval(translationTimer)
|
|
824
|
+
}
|
|
825
|
+
},
|
|
826
|
+
stopContinuousTranslation: () => {
|
|
827
|
+
if ((window as any)._stopTranslate) {
|
|
828
|
+
(window as any)._stopTranslate()
|
|
829
|
+
}
|
|
830
|
+
},
|
|
831
|
+
onTranslationResult: (callback) => {
|
|
832
|
+
window.addEventListener('mint-translation', ((event: CustomEvent<string>) => {
|
|
833
|
+
callback(event.detail)
|
|
834
|
+
}) as EventListener)
|
|
835
|
+
},
|
|
836
|
+
closePicker: async () => {
|
|
837
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
838
|
+
const { getCurrentWindow } = await import('@tauri-apps/api/window')
|
|
839
|
+
return invoke('close_desktop_window', { label: getCurrentWindow().label })
|
|
840
|
+
},
|
|
841
|
+
setOverlayInteractable: () => {},
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
window.api = {
|
|
845
|
+
sendMessage: (message, imageDataUri, audioDataUri, documentAttachment) => sendChatMessage(message, imageDataUri, audioDataUri, documentAttachment),
|
|
846
|
+
closeWindow: async () => {
|
|
847
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
848
|
+
const { getCurrentWindow } = await import('@tauri-apps/api/window')
|
|
849
|
+
return invoke('hide_desktop_window', { label: getCurrentWindow().label })
|
|
850
|
+
},
|
|
851
|
+
minimizeWindow: async () => {
|
|
852
|
+
const { getCurrentWindow } = await import('@tauri-apps/api/window')
|
|
853
|
+
return void getCurrentWindow().minimize()
|
|
854
|
+
},
|
|
855
|
+
quitApp: async () => {
|
|
856
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
857
|
+
return void invoke('exit_app')
|
|
858
|
+
},
|
|
859
|
+
maximizeWindow: async () => {
|
|
860
|
+
const { getCurrentWindow } = await import('@tauri-apps/api/window')
|
|
861
|
+
return void getCurrentWindow().toggleMaximize()
|
|
862
|
+
},
|
|
863
|
+
resetChat: clearChatHistory,
|
|
864
|
+
getChatHistory: () => getRecentInteractions(50),
|
|
865
|
+
listSavedPictures,
|
|
866
|
+
openSettings: async () => {
|
|
867
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
868
|
+
return invoke('open_window', { kind: 'settings' })
|
|
869
|
+
},
|
|
870
|
+
readClipboard: () => navigator.clipboard.readText(),
|
|
871
|
+
writeClipboard: (text) => navigator.clipboard.writeText(text),
|
|
872
|
+
getSystemInfo: async () => ({ backend: 'rust' }),
|
|
873
|
+
getWeather: async (city) => {
|
|
874
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
875
|
+
return invoke('get_weather', { city })
|
|
876
|
+
},
|
|
877
|
+
getSettings: async () => {
|
|
878
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
879
|
+
return invoke('get_config')
|
|
880
|
+
},
|
|
881
|
+
saveSettings: async (config) => {
|
|
882
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
883
|
+
return invoke('update_config', { config })
|
|
884
|
+
},
|
|
885
|
+
onSettingsChanged: settingsChanged,
|
|
886
|
+
startVision: async () => {
|
|
887
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
888
|
+
try {
|
|
889
|
+
const image = await captureSharedScreen()
|
|
890
|
+
window.localStorage.setItem('mint:pending-screen-capture', image)
|
|
891
|
+
} catch (reason) {
|
|
892
|
+
console.warn('Screen share capture failed before opening picker:', reason)
|
|
893
|
+
const image = await invoke<string>('capture_silent_screen')
|
|
894
|
+
window.localStorage.setItem('mint:pending-screen-capture', image)
|
|
895
|
+
}
|
|
896
|
+
return invoke('start_screen_capture')
|
|
897
|
+
},
|
|
898
|
+
onVisionReady: async (callback) => {
|
|
899
|
+
const { listen } = await import('@tauri-apps/api/event')
|
|
900
|
+
return listen<string>('vision-ready', (event) => callback(event.payload))
|
|
901
|
+
},
|
|
902
|
+
captureSilentScreen: async () => {
|
|
903
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
904
|
+
return invoke('capture_silent_screen')
|
|
905
|
+
},
|
|
906
|
+
getSmartContext: async () => {
|
|
907
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
908
|
+
return invoke('get_smart_context')
|
|
909
|
+
},
|
|
910
|
+
onProactiveSuggestion: async (callback) => {
|
|
911
|
+
const { listen } = await import('@tauri-apps/api/event')
|
|
912
|
+
return listen<any>('proactive-suggestion', (event) => callback(event.payload))
|
|
913
|
+
},
|
|
914
|
+
onProactiveNotification: async (callback) => {
|
|
915
|
+
const { listen } = await import('@tauri-apps/api/event')
|
|
916
|
+
return listen<any>('proactive-notification', (event) => callback(event.payload))
|
|
917
|
+
},
|
|
918
|
+
toggleProactive: async (enabled) => {
|
|
919
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
920
|
+
return void invoke('toggle_proactive', { enabled })
|
|
921
|
+
},
|
|
922
|
+
recordBehavior: async (context) => {
|
|
923
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
924
|
+
return void invoke('save_behavior_context', { context })
|
|
925
|
+
},
|
|
926
|
+
executeProactiveAction: (action) => executeAction(action),
|
|
927
|
+
executeApprovedAction: (action) => executeAction(action, true),
|
|
928
|
+
onSpotlightToChat: async (callback) => {
|
|
929
|
+
const { listen } = await import('@tauri-apps/api/event')
|
|
930
|
+
return listen<string>('spotlight-to-chat', (event) => callback(event.payload))
|
|
931
|
+
},
|
|
932
|
+
notifyAiResponse: () => {},
|
|
933
|
+
clearAiNotifications: () => {},
|
|
934
|
+
getTtsUrls: async (text) => {
|
|
935
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
936
|
+
return invoke('get_tts_urls', { text })
|
|
937
|
+
},
|
|
938
|
+
setAiState: async (state) => {
|
|
939
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
940
|
+
return void invoke('set_ai_state', { state })
|
|
941
|
+
},
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
async function captureSharedScreen(): Promise<string> {
|
|
946
|
+
// On Linux (especially under Wayland/WebKitGTK), getDisplayMedia often returns a black screen
|
|
947
|
+
// or fails silently. Bypass it to force fallback to native screenshot commands.
|
|
948
|
+
if (navigator.userAgent.toLowerCase().includes('linux')) {
|
|
949
|
+
throw new Error('Linux detected, bypassing getDisplayMedia to use native screenshot tools')
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
if (!navigator.mediaDevices?.getDisplayMedia) {
|
|
953
|
+
throw new Error('getDisplayMedia is not available')
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
const stream = await navigator.mediaDevices.getDisplayMedia({
|
|
957
|
+
video: true,
|
|
958
|
+
audio: false,
|
|
959
|
+
})
|
|
960
|
+
try {
|
|
961
|
+
const video = document.createElement('video')
|
|
962
|
+
video.srcObject = stream
|
|
963
|
+
video.muted = true
|
|
964
|
+
await video.play()
|
|
965
|
+
await new Promise<void>((resolve) => {
|
|
966
|
+
if (video.videoWidth > 0 && video.videoHeight > 0) {
|
|
967
|
+
resolve()
|
|
968
|
+
} else {
|
|
969
|
+
video.onloadedmetadata = () => resolve()
|
|
970
|
+
}
|
|
971
|
+
})
|
|
972
|
+
|
|
973
|
+
const canvas = document.createElement('canvas')
|
|
974
|
+
canvas.width = video.videoWidth || window.screen.width
|
|
975
|
+
canvas.height = video.videoHeight || window.screen.height
|
|
976
|
+
const context = canvas.getContext('2d')
|
|
977
|
+
if (!context) throw new Error('Unable to create screen capture canvas')
|
|
978
|
+
context.drawImage(video, 0, 0, canvas.width, canvas.height)
|
|
979
|
+
return canvas.toDataURL('image/png')
|
|
980
|
+
} finally {
|
|
981
|
+
stream.getTracks().forEach((track) => track.stop())
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
export async function readClipboardImage(): Promise<string | null> {
|
|
986
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
987
|
+
return null
|
|
988
|
+
}
|
|
989
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
990
|
+
try {
|
|
991
|
+
return await invoke<string>('read_clipboard_image')
|
|
992
|
+
} catch (err) {
|
|
993
|
+
console.warn('Failed to read clipboard image via Tauri command:', err)
|
|
994
|
+
return null
|
|
995
|
+
}
|
|
996
|
+
}
|