@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,763 @@
|
|
|
1
|
+
import { type ChangeEvent, type FormEvent, useEffect, useRef, useState } from 'react'
|
|
2
|
+
import {
|
|
3
|
+
clearChatHistory,
|
|
4
|
+
deleteChatSession,
|
|
5
|
+
renameChatSession,
|
|
6
|
+
getRecentInteractions,
|
|
7
|
+
getRuntimeStatus,
|
|
8
|
+
listChatSessions,
|
|
9
|
+
listSavedPictures,
|
|
10
|
+
streamChatMessage,
|
|
11
|
+
submitToolApproval,
|
|
12
|
+
listen,
|
|
13
|
+
readClipboardImage as readTauriClipboardImage,
|
|
14
|
+
type AgentProgress,
|
|
15
|
+
type ChatResponse,
|
|
16
|
+
type ChatSession,
|
|
17
|
+
type DocumentAttachment,
|
|
18
|
+
type PictureEntry,
|
|
19
|
+
type RuntimeStatus,
|
|
20
|
+
} from '../tauri'
|
|
21
|
+
import ChatPanel from './ChatPanel'
|
|
22
|
+
import DashboardSidebar, { type DashboardView } from './DashboardSidebar'
|
|
23
|
+
import PicturesLibrary from './PicturesLibrary'
|
|
24
|
+
|
|
25
|
+
const DEFAULT_CONFIG = {
|
|
26
|
+
theme: 'dark',
|
|
27
|
+
accentColor: '#4f83e6',
|
|
28
|
+
systemTextColor: '#f8fafc',
|
|
29
|
+
customBgStart: '#0f172a',
|
|
30
|
+
customBgEnd: '#1e1b4b',
|
|
31
|
+
customPanelBg: '#1e293b',
|
|
32
|
+
glassBlur: 'blur(16px)',
|
|
33
|
+
fontFamily: "'Outfit', sans-serif",
|
|
34
|
+
fontSize: '15px',
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const ACTIVE_CONVERSATION_ID_KEY = 'mint:web-active-conversation-id'
|
|
38
|
+
|
|
39
|
+
function createConversationId() {
|
|
40
|
+
const random = Math.random().toString(36).slice(2, 10)
|
|
41
|
+
return `conversation-${Date.now().toString(36)}-${random}`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function activeConversationId() {
|
|
45
|
+
const existing = window.localStorage.getItem(ACTIVE_CONVERSATION_ID_KEY)
|
|
46
|
+
if (existing === 'conversation-default') {
|
|
47
|
+
window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, 'cli')
|
|
48
|
+
return 'cli'
|
|
49
|
+
}
|
|
50
|
+
if (existing) return existing
|
|
51
|
+
const next = createConversationId()
|
|
52
|
+
window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, next)
|
|
53
|
+
return next
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const MOCK_WELCOME_INTERACTION = {
|
|
57
|
+
id: -1,
|
|
58
|
+
userText: '',
|
|
59
|
+
aiText: `มิ้นท์กำลังรอสแตนด์บายเตรียมพร้อมช่วยคุณทีมอยู่เลยค่ะ! ✨ แล้วก็แอบนั่งจัดระเบียบข้อมูลนิดๆ หน่อยๆ ให้พร้อมใช้ด้วยค่ะ 😊💖\n\nแต่พอคุณทีมทักมา มิ้นท์ก็วางมือจากทุกอย่างมาคุยกับคุณทีมก่อนเลยนะคะค้าา! ช่วงนี้มีอะไรให้มิ้นท์ช่วยดูแล หรืออยากชวนคุยเรื่องไหนเป็นพิเศษไหมคะ มิ้นท์พร้อมมว๊ากกกค่ะ! 🚀🎯`,
|
|
60
|
+
provider: 'gemini',
|
|
61
|
+
model: 'gemini-3-flash-preview',
|
|
62
|
+
createdAt: new Date().toISOString(),
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function errorMessage(reason: unknown) {
|
|
66
|
+
return reason instanceof Error ? reason.message : String(reason)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function readImage(file: File): Promise<string> {
|
|
70
|
+
return new Promise((resolve, reject) => {
|
|
71
|
+
const reader = new FileReader()
|
|
72
|
+
reader.onload = () => resolve(String(reader.result))
|
|
73
|
+
reader.onerror = () => reject(reader.error ?? new Error('Unable to read image'))
|
|
74
|
+
reader.readAsDataURL(file)
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function readDocument(file: File): Promise<string> {
|
|
79
|
+
return new Promise((resolve, reject) => {
|
|
80
|
+
const reader = new FileReader()
|
|
81
|
+
reader.onload = () => resolve(String(reader.result))
|
|
82
|
+
reader.onerror = () => reject(reader.error ?? new Error('Unable to read document'))
|
|
83
|
+
reader.readAsDataURL(file)
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function createTrimmedImagePreview(dataUri: string): Promise<string> {
|
|
88
|
+
const image = await new Promise<HTMLImageElement>((resolve, reject) => {
|
|
89
|
+
const nextImage = new Image()
|
|
90
|
+
nextImage.onload = () => resolve(nextImage)
|
|
91
|
+
nextImage.onerror = () => reject(new Error('Unable to prepare image preview'))
|
|
92
|
+
nextImage.src = dataUri
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
const canvas = document.createElement('canvas')
|
|
96
|
+
canvas.width = image.naturalWidth || image.width
|
|
97
|
+
canvas.height = image.naturalHeight || image.height
|
|
98
|
+
const context = canvas.getContext('2d', { willReadFrequently: true })
|
|
99
|
+
if (!context || canvas.width === 0 || canvas.height === 0) return dataUri
|
|
100
|
+
|
|
101
|
+
context.drawImage(image, 0, 0)
|
|
102
|
+
const pixels = context.getImageData(0, 0, canvas.width, canvas.height)
|
|
103
|
+
let minX = canvas.width
|
|
104
|
+
let minY = canvas.height
|
|
105
|
+
let maxX = -1
|
|
106
|
+
let maxY = -1
|
|
107
|
+
|
|
108
|
+
for (let y = 0; y < canvas.height; y += 1) {
|
|
109
|
+
for (let x = 0; x < canvas.width; x += 1) {
|
|
110
|
+
const alpha = pixels.data[(y * canvas.width + x) * 4 + 3]
|
|
111
|
+
if (alpha > 12) {
|
|
112
|
+
minX = Math.min(minX, x)
|
|
113
|
+
minY = Math.min(minY, y)
|
|
114
|
+
maxX = Math.max(maxX, x)
|
|
115
|
+
maxY = Math.max(maxY, y)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (maxX < minX || maxY < minY) return dataUri
|
|
121
|
+
|
|
122
|
+
const padding = 8
|
|
123
|
+
const sx = Math.max(0, minX - padding)
|
|
124
|
+
const sy = Math.max(0, minY - padding)
|
|
125
|
+
const sw = Math.min(canvas.width - sx, maxX - minX + 1 + padding * 2)
|
|
126
|
+
const sh = Math.min(canvas.height - sy, maxY - minY + 1 + padding * 2)
|
|
127
|
+
|
|
128
|
+
if (sw >= canvas.width * 0.92 && sh >= canvas.height * 0.92) return dataUri
|
|
129
|
+
|
|
130
|
+
const previewCanvas = document.createElement('canvas')
|
|
131
|
+
previewCanvas.width = sw
|
|
132
|
+
previewCanvas.height = sh
|
|
133
|
+
const previewContext = previewCanvas.getContext('2d')
|
|
134
|
+
if (!previewContext) return dataUri
|
|
135
|
+
previewContext.drawImage(canvas, sx, sy, sw, sh, 0, 0, sw, sh)
|
|
136
|
+
return previewCanvas.toDataURL('image/png')
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const lightenColor = (hex: string, amount: number) => {
|
|
140
|
+
const clean = hex.replace('#', '')
|
|
141
|
+
if (clean.length !== 6) return hex
|
|
142
|
+
const num = parseInt(clean, 16)
|
|
143
|
+
const r = Math.min(255, (num >> 16) + amount)
|
|
144
|
+
const g = Math.min(255, ((num >> 8) & 0x00FF) + amount)
|
|
145
|
+
const b = Math.min(255, (num & 0x0000FF) + amount)
|
|
146
|
+
return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, '0')}`
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const hexToRgb = (hex: string) => {
|
|
150
|
+
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
|
|
151
|
+
return result ? {
|
|
152
|
+
r: parseInt(result[1], 16),
|
|
153
|
+
g: parseInt(result[2], 16),
|
|
154
|
+
b: parseInt(result[3], 16),
|
|
155
|
+
} : { r: 15, g: 23, b: 42 }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const applyThemeStyles = (cfg: any) => {
|
|
159
|
+
const theme = cfg.theme || 'dark'
|
|
160
|
+
const accentColor = cfg.accentColor || '#4f83e6'
|
|
161
|
+
const systemTextColor = cfg.systemTextColor || '#f8fafc'
|
|
162
|
+
|
|
163
|
+
document.documentElement.setAttribute('data-theme', theme)
|
|
164
|
+
document.documentElement.style.setProperty('--accent', accentColor)
|
|
165
|
+
document.documentElement.style.setProperty('--accent-hover', lightenColor(accentColor, 20))
|
|
166
|
+
document.documentElement.style.setProperty('--text-main', systemTextColor)
|
|
167
|
+
document.documentElement.style.setProperty('--glass-blur', cfg.glassBlur || 'blur(16px)')
|
|
168
|
+
document.body.style.fontFamily = cfg.fontFamily || "'Outfit', sans-serif"
|
|
169
|
+
document.documentElement.style.fontSize = cfg.fontSize || '15px'
|
|
170
|
+
|
|
171
|
+
if (theme === 'custom') {
|
|
172
|
+
if (cfg.customBgStart && cfg.customBgEnd) {
|
|
173
|
+
document.documentElement.style.setProperty('--bg-color', cfg.customBgStart)
|
|
174
|
+
document.documentElement.style.setProperty('--bg-gradient', `linear-gradient(135deg, ${cfg.customBgStart} 0%, ${cfg.customBgEnd} 100%)`)
|
|
175
|
+
}
|
|
176
|
+
if (cfg.customPanelBg) {
|
|
177
|
+
const rgb = hexToRgb(cfg.customPanelBg)
|
|
178
|
+
document.documentElement.style.setProperty('--panel-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.75)`)
|
|
179
|
+
document.documentElement.style.setProperty('--panel-raised', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.82)`)
|
|
180
|
+
document.documentElement.style.setProperty('--panel-soft', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.46)`)
|
|
181
|
+
document.documentElement.style.setProperty('--chrome-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.88)`)
|
|
182
|
+
document.documentElement.style.setProperty('--surface-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.62)`)
|
|
183
|
+
document.documentElement.style.setProperty('--surface-strong', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.86)`)
|
|
184
|
+
document.documentElement.style.setProperty('--input-bg', `rgba(${rgb.r}, ${rgb.g}, ${rgb.b}, 0.72)`)
|
|
185
|
+
}
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
;[
|
|
190
|
+
'--bg-color',
|
|
191
|
+
'--bg-gradient',
|
|
192
|
+
'--panel-bg',
|
|
193
|
+
'--panel-raised',
|
|
194
|
+
'--panel-soft',
|
|
195
|
+
'--chrome-bg',
|
|
196
|
+
'--surface-bg',
|
|
197
|
+
'--surface-strong',
|
|
198
|
+
'--input-bg',
|
|
199
|
+
].forEach((name) => document.documentElement.style.removeProperty(name))
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
export default function MintDashboard() {
|
|
203
|
+
const [view, setView] = useState<DashboardView>('chat')
|
|
204
|
+
const [status, setStatus] = useState<RuntimeStatus | null>(null)
|
|
205
|
+
const [error, setError] = useState('')
|
|
206
|
+
const [message, setMessage] = useState('')
|
|
207
|
+
const [interactions, setInteractions] = useState<any[]>([])
|
|
208
|
+
const [pictures, setPictures] = useState<PictureEntry[]>([])
|
|
209
|
+
const [sending, setSending] = useState(false)
|
|
210
|
+
const [sendingMessage, setSendingMessage] = useState('')
|
|
211
|
+
const [sendingImageCount, setSendingImageCount] = useState(0)
|
|
212
|
+
const [streamedReply, setStreamedReply] = useState('')
|
|
213
|
+
const [streamedResponse, setStreamedResponse] = useState<ChatResponse | null>(null)
|
|
214
|
+
const [agentProgress, setAgentProgress] = useState<AgentProgress[]>([])
|
|
215
|
+
const [agentActivitySnapshots, setAgentActivitySnapshots] = useState<Record<string, AgentProgress[]>>({})
|
|
216
|
+
const [imageAttachments, setImageAttachments] = useState<Array<{ dataUri: string; name: string; previewDataUri?: string }>>([])
|
|
217
|
+
const [documentAttachment, setDocumentAttachment] = useState<DocumentAttachment | null>(null)
|
|
218
|
+
const [pendingApproval, setPendingApproval] = useState<any | null>(null)
|
|
219
|
+
const [sidebarCollapsed, setSidebarCollapsed] = useState(() => window.localStorage.getItem('mint:sidebar-collapsed') === 'true')
|
|
220
|
+
const [smartContext, setSmartContext] = useState(() => window.localStorage.getItem('mint:smart-context') !== 'false')
|
|
221
|
+
const [agentMode, setAgentMode] = useState(() => window.localStorage.getItem('mint:agent-mode') === 'true')
|
|
222
|
+
const [toastMessage, setToastMessage] = useState('')
|
|
223
|
+
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false)
|
|
224
|
+
const [dashboardDataReady, setDashboardDataReady] = useState(false)
|
|
225
|
+
const [startupTimedOut, setStartupTimedOut] = useState(false)
|
|
226
|
+
const [settingsConfig, setSettingsConfig] = useState<any>(null)
|
|
227
|
+
const [conversationId, setConversationId] = useState(activeConversationId)
|
|
228
|
+
const [chatSessions, setChatSessions] = useState<ChatSession[]>([])
|
|
229
|
+
const chatEnd = useRef<HTMLDivElement | null>(null)
|
|
230
|
+
const startupReady = dashboardDataReady || startupTimedOut
|
|
231
|
+
|
|
232
|
+
async function refreshHistory() {
|
|
233
|
+
const history = await getRecentInteractions(50, conversationId)
|
|
234
|
+
setInteractions(history.reverse())
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function refreshChatSessions(nextActiveId = conversationId) {
|
|
238
|
+
const sessions = await listChatSessions()
|
|
239
|
+
const isKnown = sessions.some((session) => session.id === nextActiveId)
|
|
240
|
+
setChatSessions(
|
|
241
|
+
isKnown || nextActiveId === 'cli'
|
|
242
|
+
? sessions
|
|
243
|
+
: [
|
|
244
|
+
{
|
|
245
|
+
id: nextActiveId,
|
|
246
|
+
title: 'New chat',
|
|
247
|
+
kind: 'conversation',
|
|
248
|
+
createdAt: new Date().toISOString(),
|
|
249
|
+
updatedAt: new Date().toISOString(),
|
|
250
|
+
},
|
|
251
|
+
...sessions,
|
|
252
|
+
],
|
|
253
|
+
)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function refreshPictures() {
|
|
257
|
+
setPictures(await listSavedPictures())
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
useEffect(() => {
|
|
261
|
+
Promise.allSettled([
|
|
262
|
+
getRuntimeStatus().then(setStatus),
|
|
263
|
+
refreshHistory(),
|
|
264
|
+
refreshChatSessions(),
|
|
265
|
+
window.settingsApi?.getSettings()
|
|
266
|
+
.then((loaded: any) => {
|
|
267
|
+
setSettingsConfig(loaded)
|
|
268
|
+
applyThemeStyles({ ...DEFAULT_CONFIG, ...loaded })
|
|
269
|
+
}),
|
|
270
|
+
]).then((results) => {
|
|
271
|
+
const failure = results.find((result) => result.status === 'rejected')
|
|
272
|
+
if (failure?.status === 'rejected') setError(errorMessage(failure.reason))
|
|
273
|
+
setDashboardDataReady(true)
|
|
274
|
+
})
|
|
275
|
+
window.api?.onSettingsChanged?.((loaded: any) => {
|
|
276
|
+
setSettingsConfig(loaded)
|
|
277
|
+
applyThemeStyles(loaded)
|
|
278
|
+
})
|
|
279
|
+
}, [])
|
|
280
|
+
|
|
281
|
+
useEffect(() => {
|
|
282
|
+
const timer = window.setTimeout(() => setStartupTimedOut(true), 10000)
|
|
283
|
+
return () => window.clearTimeout(timer)
|
|
284
|
+
}, [])
|
|
285
|
+
|
|
286
|
+
useEffect(() => {
|
|
287
|
+
if (view === 'pictures') refreshPictures().catch((reason: unknown) => setError(errorMessage(reason)))
|
|
288
|
+
}, [view])
|
|
289
|
+
|
|
290
|
+
useEffect(() => {
|
|
291
|
+
chatEnd.current?.scrollIntoView({ behavior: 'smooth' })
|
|
292
|
+
}, [interactions, sending, streamedReply, pendingApproval, agentProgress])
|
|
293
|
+
|
|
294
|
+
const showToast = (nextMessage: string) => {
|
|
295
|
+
setToastMessage(nextMessage)
|
|
296
|
+
setTimeout(() => setToastMessage((current) => current === nextMessage ? '' : current), 3000)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const changeView = (newView: DashboardView) => {
|
|
300
|
+
setView(newView)
|
|
301
|
+
setMobileSidebarOpen(false)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const toggleSidebar = () => {
|
|
305
|
+
const next = !sidebarCollapsed
|
|
306
|
+
window.localStorage.setItem('mint:sidebar-collapsed', String(next))
|
|
307
|
+
setSidebarCollapsed(next)
|
|
308
|
+
setMobileSidebarOpen(false)
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const updateSmartContext = (enabled: boolean) => {
|
|
312
|
+
window.localStorage.setItem('mint:smart-context', String(enabled))
|
|
313
|
+
setSmartContext(enabled)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const updateAgentMode = (enabled: boolean) => {
|
|
317
|
+
window.localStorage.setItem('mint:agent-mode', String(enabled))
|
|
318
|
+
setAgentMode(enabled)
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
async function handleApproval(approved: boolean, _autoApproveSession = false) {
|
|
322
|
+
if (!pendingApproval) return
|
|
323
|
+
try {
|
|
324
|
+
await submitToolApproval(pendingApproval.token, approved)
|
|
325
|
+
} catch (reason) {
|
|
326
|
+
setError(errorMessage(reason))
|
|
327
|
+
} finally {
|
|
328
|
+
setPendingApproval(null)
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
async function sendPrompt(
|
|
333
|
+
promptText: string,
|
|
334
|
+
options: {
|
|
335
|
+
imageAttachments?: Array<{ dataUri: string; name: string; previewDataUri?: string }>
|
|
336
|
+
audioDataUri?: string | null
|
|
337
|
+
documentAttachment?: DocumentAttachment | null
|
|
338
|
+
systemInstruction?: string
|
|
339
|
+
clearComposer?: boolean
|
|
340
|
+
} = {},
|
|
341
|
+
) {
|
|
342
|
+
if (sending) return
|
|
343
|
+
const outgoingImages = options.imageAttachments ?? []
|
|
344
|
+
const outgoingDocument = options.documentAttachment ?? null
|
|
345
|
+
const shouldUseAgentMode = agentMode || promptText.toLowerCase().startsWith('search web:')
|
|
346
|
+
const outgoingImage = outgoingImages.map((img) => img.dataUri).join(' ')
|
|
347
|
+
const outgoingImageCount = outgoingImages.length
|
|
348
|
+
setSending(true)
|
|
349
|
+
setSendingMessage(promptText)
|
|
350
|
+
setSendingImageCount(outgoingImageCount)
|
|
351
|
+
setError('')
|
|
352
|
+
setStreamedReply('')
|
|
353
|
+
setStreamedResponse(null)
|
|
354
|
+
setAgentProgress([])
|
|
355
|
+
const progressSnapshot: AgentProgress[] = []
|
|
356
|
+
if (options.clearComposer) {
|
|
357
|
+
setMessage('')
|
|
358
|
+
setImageAttachments([])
|
|
359
|
+
setDocumentAttachment(null)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
try {
|
|
363
|
+
const response = await streamChatMessage(
|
|
364
|
+
shouldUseAgentMode ? promptText : `/chat ${promptText}`,
|
|
365
|
+
(chunk) => setStreamedReply((current) => `${current}${chunk}`),
|
|
366
|
+
outgoingImage,
|
|
367
|
+
options.audioDataUri ?? null,
|
|
368
|
+
options.systemInstruction ?? '',
|
|
369
|
+
(progress) => {
|
|
370
|
+
progressSnapshot.push(progress)
|
|
371
|
+
setAgentProgress((current) => [...current, progress].slice(-24))
|
|
372
|
+
},
|
|
373
|
+
outgoingDocument,
|
|
374
|
+
null,
|
|
375
|
+
conversationId,
|
|
376
|
+
)
|
|
377
|
+
setStreamedResponse(response)
|
|
378
|
+
const history = (await getRecentInteractions(50, conversationId)).reverse()
|
|
379
|
+
if (progressSnapshot.length > 0) {
|
|
380
|
+
const newestInteraction = [...history]
|
|
381
|
+
.reverse()
|
|
382
|
+
.find((interaction) => interaction.aiText === response.text || interaction.userText === promptText) ?? history[history.length - 1]
|
|
383
|
+
if (newestInteraction?.id != null) {
|
|
384
|
+
setAgentActivitySnapshots((current) => ({
|
|
385
|
+
...current,
|
|
386
|
+
[String(newestInteraction.id)]: progressSnapshot.slice(),
|
|
387
|
+
}))
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
setInteractions(history)
|
|
391
|
+
await refreshChatSessions()
|
|
392
|
+
await refreshPictures()
|
|
393
|
+
setStreamedReply('')
|
|
394
|
+
setStreamedResponse(null)
|
|
395
|
+
} catch (reason) {
|
|
396
|
+
setError(errorMessage(reason))
|
|
397
|
+
} finally {
|
|
398
|
+
setSending(false)
|
|
399
|
+
setSendingMessage('')
|
|
400
|
+
setSendingImageCount(0)
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function handleSubmit(event: FormEvent<HTMLFormElement>) {
|
|
405
|
+
event.preventDefault()
|
|
406
|
+
const trimmed = message.trim()
|
|
407
|
+
const currentImages = imageAttachments
|
|
408
|
+
const currentDocument = documentAttachment
|
|
409
|
+
const hasAttachments = currentImages.length > 0 || Boolean(currentDocument)
|
|
410
|
+
if ((!trimmed && !hasAttachments) || sending) return
|
|
411
|
+
const promptText = trimmed || (currentImages.length > 1 ? 'Describe these images.' : currentImages.length === 1 ? 'Describe this image.' : 'Summarize this document.')
|
|
412
|
+
await sendPrompt(promptText, {
|
|
413
|
+
imageAttachments: currentImages,
|
|
414
|
+
documentAttachment: currentDocument,
|
|
415
|
+
clearComposer: true,
|
|
416
|
+
})
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async function sendVoiceMessage(transcript: string, audioDataUri?: string | null) {
|
|
420
|
+
const promptText = transcript.trim() || 'ข้อความเสียง'
|
|
421
|
+
if (!promptText || sending) return
|
|
422
|
+
await sendPrompt(promptText, {
|
|
423
|
+
audioDataUri,
|
|
424
|
+
systemInstruction: audioDataUri
|
|
425
|
+
? 'The user attached a voice message. Listen to the audio and reply naturally in the same language as the user. Do not mention transcription or this instruction.'
|
|
426
|
+
: '',
|
|
427
|
+
})
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async function selectImage(event: ChangeEvent<HTMLInputElement>) {
|
|
431
|
+
const file = event.target.files?.[0]
|
|
432
|
+
if (!file) return
|
|
433
|
+
try {
|
|
434
|
+
const dataUri = await readImage(file)
|
|
435
|
+
const previewDataUri = await createTrimmedImagePreview(dataUri).catch(() => dataUri)
|
|
436
|
+
setImageAttachments((current) => [...current, { dataUri, previewDataUri, name: file.name }])
|
|
437
|
+
} catch (reason) {
|
|
438
|
+
setError(errorMessage(reason))
|
|
439
|
+
} finally {
|
|
440
|
+
event.target.value = ''
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function pasteImage(clipboardData: DataTransfer) {
|
|
445
|
+
let file: File | null = null
|
|
446
|
+
|
|
447
|
+
if (clipboardData.files && clipboardData.files.length > 0) {
|
|
448
|
+
for (let i = 0; i < clipboardData.files.length; i++) {
|
|
449
|
+
const f = clipboardData.files[i]
|
|
450
|
+
if (f && f.type.startsWith('image/')) {
|
|
451
|
+
file = f
|
|
452
|
+
break
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
if (!file && clipboardData.items && clipboardData.items.length > 0) {
|
|
458
|
+
for (let i = 0; i < clipboardData.items.length; i++) {
|
|
459
|
+
const item = clipboardData.items[i]
|
|
460
|
+
if (item && item.type.startsWith('image/')) {
|
|
461
|
+
const f = item.getAsFile()
|
|
462
|
+
if (f) {
|
|
463
|
+
file = f
|
|
464
|
+
break
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
if (!file) return false
|
|
471
|
+
|
|
472
|
+
readImage(file)
|
|
473
|
+
.then((dataUri) => {
|
|
474
|
+
const name = file.name && file.name !== 'image.png' ? file.name : 'Pasted image'
|
|
475
|
+
createTrimmedImagePreview(dataUri)
|
|
476
|
+
.catch(() => dataUri)
|
|
477
|
+
.then((previewDataUri) => {
|
|
478
|
+
setImageAttachments((current) => [...current, { dataUri, previewDataUri, name }])
|
|
479
|
+
})
|
|
480
|
+
})
|
|
481
|
+
.catch((reason) => setError(errorMessage(reason)))
|
|
482
|
+
return true
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
async function readClipboardImage() {
|
|
486
|
+
try {
|
|
487
|
+
const dataUri = await readTauriClipboardImage()
|
|
488
|
+
if (dataUri) {
|
|
489
|
+
const previewDataUri = await createTrimmedImagePreview(dataUri).catch(() => dataUri)
|
|
490
|
+
setImageAttachments((current) => [...current, { dataUri, previewDataUri, name: 'Pasted image' }])
|
|
491
|
+
return true
|
|
492
|
+
}
|
|
493
|
+
} catch (err) {
|
|
494
|
+
console.warn('Tauri clipboard fallback error:', err)
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
try {
|
|
498
|
+
if (!navigator.clipboard?.read) return false
|
|
499
|
+
const items = await navigator.clipboard.read()
|
|
500
|
+
for (const item of items) {
|
|
501
|
+
const imageType = item.types.find((type) => type.startsWith('image/'))
|
|
502
|
+
if (!imageType) continue
|
|
503
|
+
const blob = await item.getType(imageType)
|
|
504
|
+
const file = new File([blob], 'Pasted image', { type: imageType })
|
|
505
|
+
const dataUri = await readImage(file)
|
|
506
|
+
const previewDataUri = await createTrimmedImagePreview(dataUri).catch(() => dataUri)
|
|
507
|
+
setImageAttachments((current) => [...current, { dataUri, previewDataUri, name: 'Pasted image' }])
|
|
508
|
+
return true
|
|
509
|
+
}
|
|
510
|
+
return false
|
|
511
|
+
} catch {
|
|
512
|
+
// Some environments expose pasted images only through ClipboardEvent.
|
|
513
|
+
return false
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
async function selectDocument(event: ChangeEvent<HTMLInputElement>) {
|
|
518
|
+
const file = event.target.files?.[0]
|
|
519
|
+
if (!file) return
|
|
520
|
+
try {
|
|
521
|
+
if (file.type !== 'application/pdf' && !file.name.toLowerCase().endsWith('.pdf')) {
|
|
522
|
+
throw new Error('Only PDF files are supported')
|
|
523
|
+
}
|
|
524
|
+
setDocumentAttachment({
|
|
525
|
+
filename: file.name,
|
|
526
|
+
dataUri: await readDocument(file),
|
|
527
|
+
})
|
|
528
|
+
} catch (reason) {
|
|
529
|
+
setError(errorMessage(reason))
|
|
530
|
+
} finally {
|
|
531
|
+
event.target.value = ''
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
function startWebSearch() {
|
|
536
|
+
updateAgentMode(true)
|
|
537
|
+
setMessage((current) => current.trim() ? `Search web: ${current.trim()}` : 'Search web: ')
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
async function captureScreen() {
|
|
541
|
+
try {
|
|
542
|
+
await window.api.startVision()
|
|
543
|
+
} catch (reason) {
|
|
544
|
+
setError(errorMessage(reason))
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
async function clearHistory(action: 'New chat' | 'Clear history') {
|
|
549
|
+
try {
|
|
550
|
+
if (action === 'New chat') {
|
|
551
|
+
const next = createConversationId()
|
|
552
|
+
window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, next)
|
|
553
|
+
setConversationId(next)
|
|
554
|
+
await refreshChatSessions(next)
|
|
555
|
+
} else {
|
|
556
|
+
if (!window.confirm(`${action} will clear the current conversation history. Continue?`)) return
|
|
557
|
+
await clearChatHistory(conversationId)
|
|
558
|
+
}
|
|
559
|
+
setInteractions([])
|
|
560
|
+
setAgentActivitySnapshots({})
|
|
561
|
+
setStreamedReply('')
|
|
562
|
+
setStreamedResponse(null)
|
|
563
|
+
setMessage('')
|
|
564
|
+
setImageAttachments([])
|
|
565
|
+
} catch (reason) {
|
|
566
|
+
setError(errorMessage(reason))
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
async function selectConversation(id: string) {
|
|
571
|
+
if (id === conversationId) {
|
|
572
|
+
changeView('chat')
|
|
573
|
+
return
|
|
574
|
+
}
|
|
575
|
+
window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, id)
|
|
576
|
+
setConversationId(id)
|
|
577
|
+
changeView('chat')
|
|
578
|
+
setStreamedReply('')
|
|
579
|
+
setStreamedResponse(null)
|
|
580
|
+
setMessage('')
|
|
581
|
+
setImageAttachments([])
|
|
582
|
+
setDocumentAttachment(null)
|
|
583
|
+
setAgentProgress([])
|
|
584
|
+
const history = await getRecentInteractions(50, id)
|
|
585
|
+
setInteractions(history.reverse())
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
async function deleteConversation(id: string) {
|
|
589
|
+
if (id === 'cli') return
|
|
590
|
+
const session = chatSessions.find((item) => item.id === id)
|
|
591
|
+
const title = session?.title || 'this chat'
|
|
592
|
+
if (!window.confirm(`Delete "${title}"? This will remove the conversation and its messages.`)) return
|
|
593
|
+
|
|
594
|
+
try {
|
|
595
|
+
await deleteChatSession(id)
|
|
596
|
+
const remaining = chatSessions.filter((item) => item.id !== id && item.kind !== 'cli' && item.id !== 'conversation-default')
|
|
597
|
+
const nextActive = id === conversationId
|
|
598
|
+
? (remaining[0]?.id ?? createConversationId())
|
|
599
|
+
: conversationId
|
|
600
|
+
|
|
601
|
+
if (nextActive !== conversationId) {
|
|
602
|
+
window.localStorage.setItem(ACTIVE_CONVERSATION_ID_KEY, nextActive)
|
|
603
|
+
setConversationId(nextActive)
|
|
604
|
+
setAgentProgress([])
|
|
605
|
+
const history = await getRecentInteractions(50, nextActive)
|
|
606
|
+
setInteractions(history.reverse())
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
await refreshChatSessions(nextActive)
|
|
610
|
+
if (id !== conversationId) return
|
|
611
|
+
setStreamedReply('')
|
|
612
|
+
setStreamedResponse(null)
|
|
613
|
+
setMessage('')
|
|
614
|
+
setImageAttachments([])
|
|
615
|
+
setDocumentAttachment(null)
|
|
616
|
+
} catch (reason) {
|
|
617
|
+
setError(errorMessage(reason))
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
async function renameConversation(id: string, newTitle: string) {
|
|
622
|
+
if (!newTitle.trim()) return
|
|
623
|
+
try {
|
|
624
|
+
await renameChatSession(id, newTitle.trim())
|
|
625
|
+
await refreshChatSessions(conversationId)
|
|
626
|
+
} catch (reason) {
|
|
627
|
+
setError(errorMessage(reason))
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
async function changeProvider(provider: string) {
|
|
632
|
+
try {
|
|
633
|
+
const config = await window.settingsApi.getSettings()
|
|
634
|
+
config.aiProvider = provider
|
|
635
|
+
await window.settingsApi.saveSettings(config)
|
|
636
|
+
setSettingsConfig(config)
|
|
637
|
+
setStatus(await getRuntimeStatus())
|
|
638
|
+
} catch (reason) {
|
|
639
|
+
setError(errorMessage(reason))
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
async function changeModel(modelName: string) {
|
|
644
|
+
try {
|
|
645
|
+
const config = await window.settingsApi.getSettings()
|
|
646
|
+
const provider = config.aiProvider
|
|
647
|
+
if (provider === 'gemini') {
|
|
648
|
+
config.geminiModel = modelName
|
|
649
|
+
} else if (provider === 'openai') {
|
|
650
|
+
config.openaiModel = modelName
|
|
651
|
+
} else if (provider === 'openrouter') {
|
|
652
|
+
config.openrouterModel = modelName
|
|
653
|
+
} else if (provider === 'deepseek') {
|
|
654
|
+
config.deepseekModel = modelName
|
|
655
|
+
} else if (provider === 'anthropic') {
|
|
656
|
+
config.anthropicModel = modelName
|
|
657
|
+
} else if (provider === 'huggingface') {
|
|
658
|
+
config.hfModel = modelName
|
|
659
|
+
} else if (provider === 'local_openai') {
|
|
660
|
+
config.localModelName = modelName
|
|
661
|
+
} else if (provider === 'ollama') {
|
|
662
|
+
config.ollamaModel = modelName
|
|
663
|
+
}
|
|
664
|
+
await window.settingsApi.saveSettings(config)
|
|
665
|
+
setSettingsConfig(config)
|
|
666
|
+
setStatus(await getRuntimeStatus())
|
|
667
|
+
} catch (reason) {
|
|
668
|
+
setError(errorMessage(reason))
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
|
|
674
|
+
return (
|
|
675
|
+
<div className={`app-container ${startupReady ? '' : 'is-loading'}`}>
|
|
676
|
+
<div className={`app-body ${sidebarCollapsed ? 'sidebar-collapsed' : ''} ${view === 'pictures' ? 'pictures-open' : ''} ${mobileSidebarOpen ? 'mobile-sidebar-open' : ''}`}>
|
|
677
|
+
{mobileSidebarOpen && (
|
|
678
|
+
<div
|
|
679
|
+
className="sidebar-backdrop"
|
|
680
|
+
onClick={() => setMobileSidebarOpen(false)}
|
|
681
|
+
style={{
|
|
682
|
+
position: 'fixed',
|
|
683
|
+
top: 0,
|
|
684
|
+
left: 0,
|
|
685
|
+
right: 0,
|
|
686
|
+
bottom: 0,
|
|
687
|
+
background: 'rgba(0, 0, 0, 0.5)',
|
|
688
|
+
backdropFilter: 'blur(4px)',
|
|
689
|
+
zIndex: 9998,
|
|
690
|
+
}}
|
|
691
|
+
/>
|
|
692
|
+
)}
|
|
693
|
+
<DashboardSidebar
|
|
694
|
+
view={view}
|
|
695
|
+
sidebarCollapsed={sidebarCollapsed}
|
|
696
|
+
sending={sending}
|
|
697
|
+
chatSessions={chatSessions}
|
|
698
|
+
activeConversationId={conversationId}
|
|
699
|
+
onToggleSidebar={toggleSidebar}
|
|
700
|
+
onClearHistory={clearHistory}
|
|
701
|
+
onSelectConversation={selectConversation}
|
|
702
|
+
onDeleteConversation={deleteConversation}
|
|
703
|
+
onRenameConversation={renameConversation}
|
|
704
|
+
onSetView={changeView}
|
|
705
|
+
/>
|
|
706
|
+
<main className="assistant-workspace model-hidden">
|
|
707
|
+
<ChatPanel
|
|
708
|
+
interactions={interactions}
|
|
709
|
+
sending={sending}
|
|
710
|
+
sendingMessage={sendingMessage}
|
|
711
|
+
sendingImageCount={sendingImageCount}
|
|
712
|
+
streamedReply={streamedReply}
|
|
713
|
+
streamedResponse={streamedResponse}
|
|
714
|
+
agentProgress={agentProgress}
|
|
715
|
+
agentActivitySnapshots={agentActivitySnapshots}
|
|
716
|
+
message={message}
|
|
717
|
+
imageAttachments={imageAttachments}
|
|
718
|
+
documentName={documentAttachment?.filename ?? ''}
|
|
719
|
+
pendingApproval={pendingApproval}
|
|
720
|
+
smartContext={smartContext}
|
|
721
|
+
agentMode={agentMode}
|
|
722
|
+
status={status}
|
|
723
|
+
chatEnd={chatEnd}
|
|
724
|
+
welcomeInteraction={MOCK_WELCOME_INTERACTION}
|
|
725
|
+
onSubmit={handleSubmit}
|
|
726
|
+
onSelectImage={selectImage}
|
|
727
|
+
onSelectDocument={selectDocument}
|
|
728
|
+
onPasteImage={pasteImage}
|
|
729
|
+
onReadClipboardImage={readClipboardImage}
|
|
730
|
+
onSetMessage={setMessage}
|
|
731
|
+
onSendVoiceMessage={sendVoiceMessage}
|
|
732
|
+
onRemoveImage={(idx: number) => {
|
|
733
|
+
setImageAttachments((current) => current.filter((_, i) => i !== idx))
|
|
734
|
+
}}
|
|
735
|
+
onRemoveDocument={() => setDocumentAttachment(null)}
|
|
736
|
+
onStartWebSearch={startWebSearch}
|
|
737
|
+
onCaptureScreen={captureScreen}
|
|
738
|
+
onSetSmartContext={updateSmartContext}
|
|
739
|
+
onSetAgentMode={updateAgentMode}
|
|
740
|
+
onSetProvider={changeProvider}
|
|
741
|
+
settingsConfig={settingsConfig}
|
|
742
|
+
onSetModel={changeModel}
|
|
743
|
+
onApproval={handleApproval}
|
|
744
|
+
onToggleMobileSidebar={() => setMobileSidebarOpen(!mobileSidebarOpen)}
|
|
745
|
+
/>
|
|
746
|
+
</main>
|
|
747
|
+
<PicturesLibrary view={view} pictures={pictures} onSetView={changeView} />
|
|
748
|
+
</div>
|
|
749
|
+
<div className={`startup-loading ${startupReady ? 'is-hidden' : ''}`} aria-live="polite" aria-busy={!startupReady}>
|
|
750
|
+
<div className="startup-loading-content">
|
|
751
|
+
<div className="startup-loading-dots" aria-hidden="true"><span /><span /><span /></div>
|
|
752
|
+
<div className="startup-loading-text">Loading Agent Mint</div>
|
|
753
|
+
</div>
|
|
754
|
+
</div>
|
|
755
|
+
{error && (
|
|
756
|
+
<div className="mint-error" style={{ position: 'absolute', bottom: '20px', right: '20px', zIndex: 100, margin: 0, boxShadow: '0 8px 24px rgba(0,0,0,0.3)' }}>
|
|
757
|
+
{error}
|
|
758
|
+
<button onClick={() => setError('')} style={{ marginLeft: '12px', background: 'transparent', border: 0, color: 'white', cursor: 'pointer' }}>✕</button>
|
|
759
|
+
</div>
|
|
760
|
+
)}
|
|
761
|
+
</div>
|
|
762
|
+
)
|
|
763
|
+
}
|