@pheem49/mint 1.7.1 → 1.8.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/.github/workflows/release.yml +1 -1
- package/Cargo.lock +34 -3
- package/Cargo.toml +2 -2
- package/README.md +40 -0
- package/Release_Note.md +95 -0
- package/assets/Mint_CLI.png +0 -0
- package/crates/mint-cli/Cargo.toml +1 -0
- package/crates/mint-cli/src/agent.rs +142 -48
- package/crates/mint-cli/src/main.rs +231 -22
- package/crates/mint-core/src/api_server.rs +71 -4
- package/crates/mint-core/src/browser.rs +401 -0
- package/crates/mint-core/src/chat.rs +79 -0
- package/crates/mint-core/src/config.rs +69 -1
- package/crates/mint-core/src/lib.rs +6 -1
- package/crates/mint-core/src/orchestration.rs +137 -1
- package/crates/mint-core/src/skills.rs +113 -4
- package/crates/mint-core/src/workflows.rs +9 -22
- package/package.json +2 -1
- package/src/renderer/src/App.tsx +1 -1
- package/src/renderer/src/components/ChatPanel.tsx +197 -47
- package/src/renderer/src/components/DashboardSidebar.tsx +71 -12
- package/src/renderer/src/components/ImageStudioPanel.tsx +18 -4
- package/src/renderer/src/components/MintDashboard.tsx +239 -7
- package/src/renderer/src/components/PicturesLibrary.tsx +1 -3
- package/src/renderer/src/components/Settings/AudioTab.tsx +97 -0
- package/src/renderer/src/components/Settings/AutomationTab.tsx +195 -0
- package/src/renderer/src/components/Settings/GeneralTab.tsx +950 -0
- package/src/renderer/src/components/Settings/MemoryTab.tsx +60 -0
- package/src/renderer/src/components/Settings/PluginsTab.tsx +632 -0
- package/src/renderer/src/components/Settings/ShortcutsTab.tsx +30 -0
- package/src/renderer/src/components/Settings/ThemeTab.tsx +129 -0
- package/src/renderer/src/components/SettingsWindow.tsx +175 -970
- package/src/renderer/src/components/WorkflowBuilderPanel.tsx +536 -0
- package/src/renderer/src/components/WorkflowsWindow.tsx +46 -0
- package/src/renderer/src/components/WorkspacePanel.tsx +66 -9
- package/src/renderer/src/css/image-studio.css +15 -4
- package/src/renderer/src/css/settings.css +75 -6
- package/src/renderer/src/css/styles.css +419 -49
- package/src/renderer/src/css/workflows.css +542 -0
- package/src/renderer/src/env.d.ts +2 -0
- package/src/renderer/src/index.css +13 -1
- package/src/renderer/src/tauri.ts +140 -2
- package/src/renderer/src-web/components/ChatPanel.tsx +190 -46
- package/src/renderer/src-web/components/DashboardSidebar.tsx +17 -1
- package/src/renderer/src-web/components/ImageStudioPanel.tsx +18 -4
- package/src/renderer/src-web/components/MintDashboard.tsx +197 -3
- package/src/renderer/src-web/components/PicturesLibrary.tsx +1 -3
- package/src/renderer/src-web/components/Settings/AudioTab.tsx +97 -0
- package/src/renderer/src-web/components/Settings/AutomationTab.tsx +196 -0
- package/src/renderer/src-web/components/Settings/GeneralTab.tsx +948 -0
- package/src/renderer/src-web/components/Settings/MemoryTab.tsx +60 -0
- package/src/renderer/src-web/components/Settings/PluginsTab.tsx +632 -0
- package/src/renderer/src-web/components/Settings/ShortcutsTab.tsx +30 -0
- package/src/renderer/src-web/components/Settings/ThemeTab.tsx +129 -0
- package/src/renderer/src-web/components/SettingsWindow.tsx +187 -976
- package/src/renderer/src-web/css/image-studio.css +15 -4
- package/src/renderer/src-web/css/settings.css +75 -6
- package/src/renderer/src-web/css/styles.css +394 -50
- package/src/renderer/src-web/env.d.ts +2 -0
- package/src/renderer/src-web/index.css +12 -1
- package/src/renderer/src-web/tauri.ts +60 -2
- package/src-tauri/src/browser.rs +1 -141
- package/src-tauri/src/headless.rs +3 -2
- package/src-tauri/src/lib.rs +195 -111
- package/src-tauri/src/workflows.rs +56 -4
- package/src-tauri/tauri.conf.json +1 -1
|
@@ -128,6 +128,28 @@ export async function getRuntimeStatus(): Promise<RuntimeStatus> {
|
|
|
128
128
|
return invoke<RuntimeStatus>('get_runtime_status')
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
export interface DetectedTools {
|
|
132
|
+
docker: boolean
|
|
133
|
+
git: boolean
|
|
134
|
+
gh: boolean
|
|
135
|
+
node: boolean
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export async function detectSystemTools(): Promise<DetectedTools> {
|
|
139
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
140
|
+
const API_BASE = "http://localhost:3000/api";
|
|
141
|
+
try {
|
|
142
|
+
const res = await fetch(`${API_BASE}/detect-tools`);
|
|
143
|
+
return await res.json();
|
|
144
|
+
} catch (e) {
|
|
145
|
+
console.error("Failed to detect tools from local server:", e);
|
|
146
|
+
return { docker: false, git: false, gh: false, node: false };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
150
|
+
return invoke<DetectedTools>('detect_system_tools')
|
|
151
|
+
}
|
|
152
|
+
|
|
131
153
|
export async function sendChatMessage(
|
|
132
154
|
message: string,
|
|
133
155
|
imageDataUri?: string | null,
|
|
@@ -282,6 +304,30 @@ export async function getRecentInteractions(limit = 50, chatId?: string | null):
|
|
|
282
304
|
return invoke<InteractionMemory[]>('get_recent_interactions', { limit, chatId })
|
|
283
305
|
}
|
|
284
306
|
|
|
307
|
+
export async function saveSystemInteraction(
|
|
308
|
+
chatId: string,
|
|
309
|
+
userText: string,
|
|
310
|
+
provider: string,
|
|
311
|
+
model: string,
|
|
312
|
+
): Promise<any> {
|
|
313
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
314
|
+
const API_BASE = "http://localhost:3000/api";
|
|
315
|
+
try {
|
|
316
|
+
const res = await fetch(`${API_BASE}/interactions`, {
|
|
317
|
+
method: 'POST',
|
|
318
|
+
headers: { 'Content-Type': 'application/json' },
|
|
319
|
+
body: JSON.stringify({ chatId, userText, provider, model }),
|
|
320
|
+
});
|
|
321
|
+
return await res.json();
|
|
322
|
+
} catch (e) {
|
|
323
|
+
console.error("Failed to save system interaction on local server:", e);
|
|
324
|
+
return { success: false };
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
328
|
+
return invoke('save_system_interaction', { chatId, userText, provider, model })
|
|
329
|
+
}
|
|
330
|
+
|
|
285
331
|
export async function saveInteractionAgentActivity(
|
|
286
332
|
interactionId: number,
|
|
287
333
|
activity: AgentProgress[],
|
|
@@ -396,6 +442,72 @@ export async function setProfileValue(key: string, value: string): Promise<boole
|
|
|
396
442
|
return invoke<void>('set_profile_value', { key, value }).then(() => true).catch(() => false)
|
|
397
443
|
}
|
|
398
444
|
|
|
445
|
+
export interface LearnedSkill {
|
|
446
|
+
id: number
|
|
447
|
+
name: string
|
|
448
|
+
sourcePath: string
|
|
449
|
+
content: string
|
|
450
|
+
updatedAt: string
|
|
451
|
+
location?: string
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export async function listLearnedSkills(workspacePath?: string): Promise<LearnedSkill[]> {
|
|
455
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
456
|
+
try {
|
|
457
|
+
const val = await getProfileValue('learned-skills-web-mock')
|
|
458
|
+
if (val) {
|
|
459
|
+
return JSON.parse(val)
|
|
460
|
+
}
|
|
461
|
+
return []
|
|
462
|
+
} catch (e) {
|
|
463
|
+
console.error("Failed to load web mock skills:", e)
|
|
464
|
+
return []
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
468
|
+
return invoke<LearnedSkill[]>('list_learned_skills', { workspacePath })
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
export async function addLearnedSkill(name: string, content: string): Promise<LearnedSkill> {
|
|
472
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
473
|
+
try {
|
|
474
|
+
const list = await listLearnedSkills()
|
|
475
|
+
const newSkill: LearnedSkill = {
|
|
476
|
+
id: Date.now(),
|
|
477
|
+
name,
|
|
478
|
+
sourcePath: 'ui_manual',
|
|
479
|
+
content,
|
|
480
|
+
updatedAt: new Date().toISOString()
|
|
481
|
+
}
|
|
482
|
+
list.push(newSkill)
|
|
483
|
+
await setProfileValue('learned-skills-web-mock', JSON.stringify(list))
|
|
484
|
+
return newSkill
|
|
485
|
+
} catch (e) {
|
|
486
|
+
console.error("Failed to add web mock skill:", e)
|
|
487
|
+
throw e
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
491
|
+
return invoke<LearnedSkill>('add_learned_skill', { name, content })
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export async function deleteLearnedSkill(name: string): Promise<number> {
|
|
495
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
496
|
+
try {
|
|
497
|
+
const list = await listLearnedSkills()
|
|
498
|
+
const filtered = list.filter(s => s.name !== name)
|
|
499
|
+
const deletedCount = list.length - filtered.length
|
|
500
|
+
await setProfileValue('learned-skills-web-mock', JSON.stringify(filtered))
|
|
501
|
+
return deletedCount
|
|
502
|
+
} catch (e) {
|
|
503
|
+
console.error("Failed to delete web mock skill:", e)
|
|
504
|
+
return 0
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
508
|
+
return invoke<number>('delete_learned_skill', { name })
|
|
509
|
+
}
|
|
510
|
+
|
|
399
511
|
export async function clearChatHistory(chatId?: string | null): Promise<number> {
|
|
400
512
|
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
401
513
|
const API_BASE = "http://localhost:3000/api";
|
|
@@ -558,6 +670,24 @@ export async function getWorkspaceTree(path?: string | null): Promise<WorkspaceT
|
|
|
558
670
|
return invoke<WorkspaceTreeEntry>('get_workspace_tree', { path })
|
|
559
671
|
}
|
|
560
672
|
|
|
673
|
+
export async function createWorkspaceFile(path: string): Promise<void> {
|
|
674
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) return
|
|
675
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
676
|
+
return invoke('create_workspace_file', { path })
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
export async function createWorkspaceFolder(path: string): Promise<void> {
|
|
680
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) return
|
|
681
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
682
|
+
return invoke('create_workspace_folder', { path })
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
export async function deleteWorkspaceItem(path: string): Promise<void> {
|
|
686
|
+
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) return
|
|
687
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
688
|
+
return invoke('delete_workspace_item', { path })
|
|
689
|
+
}
|
|
690
|
+
|
|
561
691
|
export async function selectWorkspaceDirectory(): Promise<string | null> {
|
|
562
692
|
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
563
693
|
return null
|
|
@@ -567,12 +697,12 @@ export async function selectWorkspaceDirectory(): Promise<string | null> {
|
|
|
567
697
|
return selected?.trim() || null
|
|
568
698
|
}
|
|
569
699
|
|
|
570
|
-
export async function submitToolApproval(token: string, approved: boolean): Promise<void> {
|
|
700
|
+
export async function submitToolApproval(token: string, approved: boolean, answer?: string): Promise<void> {
|
|
571
701
|
if (typeof window === 'undefined' || !(window as any).__TAURI_INTERNALS__) {
|
|
572
702
|
return;
|
|
573
703
|
}
|
|
574
704
|
const { invoke } = await import('@tauri-apps/api/core')
|
|
575
|
-
return invoke('submit_tool_approval', { token, approved })
|
|
705
|
+
return invoke('submit_tool_approval', { token, approved, answer })
|
|
576
706
|
}
|
|
577
707
|
|
|
578
708
|
export async function proposeCodeEdits(root: string, edits: CodeEdit[]): Promise<CodeEditProposal> {
|
|
@@ -889,6 +1019,10 @@ export function installTauriAdapters() {
|
|
|
889
1019
|
const { invoke } = await import('@tauri-apps/api/core')
|
|
890
1020
|
return invoke('reload_custom_workflows')
|
|
891
1021
|
},
|
|
1022
|
+
saveCustomWorkflows: async (workflows) => {
|
|
1023
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
1024
|
+
return invoke('save_custom_workflows', { workflows })
|
|
1025
|
+
},
|
|
892
1026
|
}
|
|
893
1027
|
|
|
894
1028
|
window.spotlightAPI = {
|
|
@@ -1015,6 +1149,10 @@ export function installTauriAdapters() {
|
|
|
1015
1149
|
const { invoke } = await import('@tauri-apps/api/core')
|
|
1016
1150
|
return invoke('open_window', { kind: 'settings' })
|
|
1017
1151
|
},
|
|
1152
|
+
openWorkflows: async () => {
|
|
1153
|
+
const { invoke } = await import('@tauri-apps/api/core')
|
|
1154
|
+
return invoke('open_window', { kind: 'workflows' })
|
|
1155
|
+
},
|
|
1018
1156
|
readClipboard: () => navigator.clipboard.readText(),
|
|
1019
1157
|
writeClipboard: (text) => navigator.clipboard.writeText(text),
|
|
1020
1158
|
getSystemInfo: async () => ({ backend: 'rust' }),
|
|
@@ -68,15 +68,7 @@ const LOCAL_MODELS = [
|
|
|
68
68
|
'lmstudio-community/gemma-2-9b-it-GGUF'
|
|
69
69
|
]
|
|
70
70
|
|
|
71
|
-
const OLLAMA_MODELS = [
|
|
72
|
-
'llama3:latest',
|
|
73
|
-
'llama3.1:latest',
|
|
74
|
-
'llama3.2:latest',
|
|
75
|
-
'gemma2:latest',
|
|
76
|
-
'mistral:latest',
|
|
77
|
-
'phi3:latest',
|
|
78
|
-
'qwen2.5:latest'
|
|
79
|
-
]
|
|
71
|
+
const OLLAMA_MODELS: string[] = []
|
|
80
72
|
|
|
81
73
|
interface ApprovalDetails {
|
|
82
74
|
title: string
|
|
@@ -122,6 +114,7 @@ interface AgentActivity {
|
|
|
122
114
|
target: string
|
|
123
115
|
kind: 'file' | 'folder' | 'search' | 'terminal' | 'tool'
|
|
124
116
|
state: 'active' | 'done' | 'error'
|
|
117
|
+
action?: string
|
|
125
118
|
}
|
|
126
119
|
|
|
127
120
|
interface AgentActivityView {
|
|
@@ -153,7 +146,20 @@ function describeTool(action: string, input: Record<string, unknown>): AgentActi
|
|
|
153
146
|
const command = activityDetail(input, 'command')
|
|
154
147
|
const name = activityDetail(input, 'name')
|
|
155
148
|
const tool = activityDetail(input, 'tool')
|
|
156
|
-
const
|
|
149
|
+
const rawTarget = path || query || command || name || tool || action.replaceAll('_', ' ')
|
|
150
|
+
|
|
151
|
+
// Append line range for read_file when startLine / endLine are available
|
|
152
|
+
let target = rawTarget
|
|
153
|
+
if (action === 'read_file' && path) {
|
|
154
|
+
const startLine = typeof input.startLine === 'number' ? input.startLine : undefined
|
|
155
|
+
const endLine = typeof input.endLine === 'number' ? input.endLine : undefined
|
|
156
|
+
if (startLine !== undefined && endLine !== undefined) {
|
|
157
|
+
target = `${rawTarget} #L${startLine}-${endLine}`
|
|
158
|
+
} else if (startLine !== undefined) {
|
|
159
|
+
target = `${rawTarget} #L${startLine}`
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
157
163
|
const labels: Record<string, string> = {
|
|
158
164
|
apply_patch: 'Applying patch',
|
|
159
165
|
ask_user: 'Asking user',
|
|
@@ -189,6 +195,7 @@ function describeTool(action: string, input: Record<string, unknown>): AgentActi
|
|
|
189
195
|
target: formatActivityTarget(target),
|
|
190
196
|
kind: activityKind(action, target),
|
|
191
197
|
state: 'active',
|
|
198
|
+
action,
|
|
192
199
|
}
|
|
193
200
|
}
|
|
194
201
|
|
|
@@ -215,7 +222,13 @@ function activitiesFrom(progress: AgentProgress[]): AgentActivityView {
|
|
|
215
222
|
for (let index = activities.length - 1; index >= 0; index -= 1) {
|
|
216
223
|
if (activities[index].state !== 'active') continue
|
|
217
224
|
activities[index].state = event.data.result.startsWith('Error:') ? 'error' : 'done'
|
|
218
|
-
if (activities[index].state === 'error')
|
|
225
|
+
if (activities[index].state === 'error') {
|
|
226
|
+
activities[index].label = 'Failed'
|
|
227
|
+
}
|
|
228
|
+
if (activities[index].action === 'ask_user' && event.data.result.startsWith('User answered:')) {
|
|
229
|
+
const answer = event.data.result.replace('User answered:', '').trim()
|
|
230
|
+
activities[index].target = `(Answered: "${answer}") ${activities[index].target}`
|
|
231
|
+
}
|
|
219
232
|
break
|
|
220
233
|
}
|
|
221
234
|
}
|
|
@@ -224,7 +237,16 @@ function activitiesFrom(progress: AgentProgress[]): AgentActivityView {
|
|
|
224
237
|
return { summary: activitySummary(activities), items }
|
|
225
238
|
}
|
|
226
239
|
|
|
227
|
-
function
|
|
240
|
+
function AgentActivityTable({ activityView }: { activityView: AgentActivityView }) {
|
|
241
|
+
const [expandedIndices, setExpandedIndices] = useState<Record<number, boolean>>({})
|
|
242
|
+
|
|
243
|
+
const toggleExpand = (index: number) => {
|
|
244
|
+
setExpandedIndices(prev => ({
|
|
245
|
+
...prev,
|
|
246
|
+
[index]: !prev[index]
|
|
247
|
+
}))
|
|
248
|
+
}
|
|
249
|
+
|
|
228
250
|
return (
|
|
229
251
|
<div className="agent-activity-list">
|
|
230
252
|
<div className="agent-activity-table-head" aria-hidden="true">
|
|
@@ -233,14 +255,39 @@ function renderAgentActivityTable(activityView: AgentActivityView) {
|
|
|
233
255
|
<span>Target</span>
|
|
234
256
|
<span />
|
|
235
257
|
</div>
|
|
236
|
-
{activityView.items.map((activity, index) =>
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
<
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
258
|
+
{activityView.items.map((activity, index) => {
|
|
259
|
+
const isExpanded = !!expandedIndices[index]
|
|
260
|
+
return (
|
|
261
|
+
<div
|
|
262
|
+
className="agent-activity-item"
|
|
263
|
+
data-kind={activity.kind}
|
|
264
|
+
data-state={activity.state}
|
|
265
|
+
key={`${index}-${activity.label}-${activity.target}`}
|
|
266
|
+
style={{ cursor: 'pointer' }}
|
|
267
|
+
onClick={() => toggleExpand(index)}
|
|
268
|
+
>
|
|
269
|
+
<span className="agent-activity-label">{activity.label}</span>
|
|
270
|
+
<span className="agent-activity-icon" aria-hidden="true" />
|
|
271
|
+
<span
|
|
272
|
+
className="agent-activity-text"
|
|
273
|
+
style={isExpanded ? { whiteSpace: 'normal', wordBreak: 'break-all', overflow: 'visible', textOverflow: 'clip' } : undefined}
|
|
274
|
+
>
|
|
275
|
+
{activity.target}
|
|
276
|
+
</span>
|
|
277
|
+
<span
|
|
278
|
+
className="agent-activity-chevron"
|
|
279
|
+
style={{
|
|
280
|
+
transform: isExpanded ? 'rotate(90deg)' : 'none',
|
|
281
|
+
transition: 'transform 0.2s ease',
|
|
282
|
+
display: 'inline-block'
|
|
283
|
+
}}
|
|
284
|
+
aria-hidden="true"
|
|
285
|
+
>
|
|
286
|
+
>
|
|
287
|
+
</span>
|
|
288
|
+
</div>
|
|
289
|
+
)
|
|
290
|
+
})}
|
|
244
291
|
</div>
|
|
245
292
|
)
|
|
246
293
|
}
|
|
@@ -257,7 +304,7 @@ function renderApprovalDetails(approval: any): ApprovalDetails {
|
|
|
257
304
|
return { title: `Run MCP Tool: ${server}/${tool}`, body: typeof args === 'string' ? args : JSON.stringify(args, null, 2), reason: 'Running external MCP tool.', isDangerous: false }
|
|
258
305
|
}
|
|
259
306
|
if (approval.UserApproval) return { title: approval.UserApproval.title, body: approval.UserApproval.prompt, reason: 'The agent requested explicit approval.', isDangerous: false }
|
|
260
|
-
if (approval.AskUser) return { title: 'Question From Agent', body: approval.AskUser.question, reason: '
|
|
307
|
+
if (approval.AskUser) return { title: 'Question From Agent', body: approval.AskUser.question, reason: 'Type your answer below and submit to respond to the agent.', isDangerous: false }
|
|
261
308
|
return { title: 'Unknown Action', body: JSON.stringify(approval, null, 2), reason: 'Requires approval to proceed.', isDangerous: false }
|
|
262
309
|
}
|
|
263
310
|
|
|
@@ -353,7 +400,7 @@ interface ChatPanelProps {
|
|
|
353
400
|
onSetSmartContext: (enabled: boolean) => void
|
|
354
401
|
onSetAgentMode: (enabled: boolean) => void
|
|
355
402
|
onSetProvider: (provider: string) => void
|
|
356
|
-
onApproval: (approved: boolean, autoApproveSession?: boolean) => void
|
|
403
|
+
onApproval: (approved: boolean, autoApproveSession?: boolean, answer?: string) => void
|
|
357
404
|
onToggleMobileSidebar: () => void
|
|
358
405
|
settingsConfig: any
|
|
359
406
|
onSetModel: (model: string) => void
|
|
@@ -449,7 +496,17 @@ function renderFormattedMessage(text: string) {
|
|
|
449
496
|
</strong>
|
|
450
497
|
)
|
|
451
498
|
}
|
|
452
|
-
|
|
499
|
+
const mentionParts = boldPart.split(/(@[\w\-\.\/]+)/g)
|
|
500
|
+
return mentionParts.map((mentionPart, mentionIndex) => {
|
|
501
|
+
if (mentionIndex % 2 === 1) {
|
|
502
|
+
return (
|
|
503
|
+
<span key={`mention-${mentionIndex}`} className="chat-mention">
|
|
504
|
+
{mentionPart}
|
|
505
|
+
</span>
|
|
506
|
+
)
|
|
507
|
+
}
|
|
508
|
+
return mentionPart
|
|
509
|
+
})
|
|
453
510
|
})
|
|
454
511
|
})
|
|
455
512
|
}
|
|
@@ -975,6 +1032,34 @@ export default function ChatPanel({
|
|
|
975
1032
|
const [openFileDiffs, setOpenFileDiffs] = useState<Record<string, boolean>>({})
|
|
976
1033
|
const [toolMenuOpen, setToolMenuOpen] = useState(false)
|
|
977
1034
|
const toolMenuRef = useRef<HTMLDivElement | null>(null)
|
|
1035
|
+
const [dynamicOllamaModels, setDynamicOllamaModels] = useState<string[]>(OLLAMA_MODELS)
|
|
1036
|
+
const [askAnswer, setAskAnswer] = useState('')
|
|
1037
|
+
|
|
1038
|
+
useEffect(() => {
|
|
1039
|
+
setAskAnswer('')
|
|
1040
|
+
}, [pendingApproval])
|
|
1041
|
+
|
|
1042
|
+
useEffect(() => {
|
|
1043
|
+
const fetchOllamaModels = async () => {
|
|
1044
|
+
if (status?.activeProvider !== 'ollama') return;
|
|
1045
|
+
const host = settingsConfig?.ollamaHost || 'http://localhost:11434';
|
|
1046
|
+
const cleanHost = host.endsWith('/') ? host.slice(0, -1) : host;
|
|
1047
|
+
try {
|
|
1048
|
+
const res = await fetch(`${cleanHost}/api/tags`);
|
|
1049
|
+
if (res.ok) {
|
|
1050
|
+
const data = await res.json();
|
|
1051
|
+
if (data && Array.isArray(data.models)) {
|
|
1052
|
+
setDynamicOllamaModels(data.models.map((m: any) => m.name));
|
|
1053
|
+
return;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
} catch (err) {
|
|
1057
|
+
// fallback to default if fetch fails
|
|
1058
|
+
}
|
|
1059
|
+
setDynamicOllamaModels(OLLAMA_MODELS);
|
|
1060
|
+
}
|
|
1061
|
+
fetchOllamaModels();
|
|
1062
|
+
}, [status?.activeProvider, settingsConfig?.ollamaHost])
|
|
978
1063
|
const canSubmit = Boolean(message.trim() || imageAttachments.length > 0 || documentName)
|
|
979
1064
|
const sendingImageMarkers = Array.from({ length: sendingImageCount }, (_, index) => `[Image #${index + 1}]`).join(' ')
|
|
980
1065
|
|
|
@@ -995,7 +1080,7 @@ export default function ChatPanel({
|
|
|
995
1080
|
case 'local_openai':
|
|
996
1081
|
return LOCAL_MODELS
|
|
997
1082
|
case 'ollama':
|
|
998
|
-
return
|
|
1083
|
+
return dynamicOllamaModels
|
|
999
1084
|
default:
|
|
1000
1085
|
return []
|
|
1001
1086
|
}
|
|
@@ -1543,7 +1628,7 @@ export default function ChatPanel({
|
|
|
1543
1628
|
</button>
|
|
1544
1629
|
{isOpen && (
|
|
1545
1630
|
<div className="agent-activity-card agent-activity-card-history">
|
|
1546
|
-
{
|
|
1631
|
+
<AgentActivityTable activityView={activityView} />
|
|
1547
1632
|
</div>
|
|
1548
1633
|
)}
|
|
1549
1634
|
</div>
|
|
@@ -1798,21 +1883,33 @@ export default function ChatPanel({
|
|
|
1798
1883
|
)}
|
|
1799
1884
|
|
|
1800
1885
|
<div className="chat-container">
|
|
1801
|
-
{interactions.map((interaction) =>
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
<
|
|
1886
|
+
{interactions.map((interaction) => {
|
|
1887
|
+
const isSystemEvent = interaction.provider === 'system' && interaction.model === 'provider_change';
|
|
1888
|
+
if (isSystemEvent) {
|
|
1889
|
+
return (
|
|
1890
|
+
<div key={interaction.id} className="system-event-divider">
|
|
1891
|
+
<div className="system-event-line" />
|
|
1892
|
+
<div className="system-event-pill">
|
|
1893
|
+
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5">
|
|
1894
|
+
<path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z" />
|
|
1895
|
+
</svg>
|
|
1896
|
+
<span>{interaction.userText}</span>
|
|
1812
1897
|
</div>
|
|
1898
|
+
<div className="system-event-line" />
|
|
1813
1899
|
</div>
|
|
1814
|
-
)
|
|
1815
|
-
|
|
1900
|
+
);
|
|
1901
|
+
}
|
|
1902
|
+
return (
|
|
1903
|
+
<div key={interaction.id} style={{ display: 'flex', flexDirection: 'column', gap: '8px', marginBottom: '16px' }}>
|
|
1904
|
+
{interaction.userText && (
|
|
1905
|
+
<div className="message user-message">
|
|
1906
|
+
<div className="bubble-wrapper">
|
|
1907
|
+
<div className="message-bubble">{renderFormattedMessage(interaction.userText)}</div>
|
|
1908
|
+
<div className="message-time"><span>{new Date(interaction.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}</span></div>
|
|
1909
|
+
</div>
|
|
1910
|
+
</div>
|
|
1911
|
+
)}
|
|
1912
|
+
<div className="message ai-message">
|
|
1816
1913
|
<div className="bubble-wrapper">
|
|
1817
1914
|
{renderCompletedActivity(interaction)}
|
|
1818
1915
|
{renderFileChanges(interaction)}
|
|
@@ -1858,7 +1955,8 @@ export default function ChatPanel({
|
|
|
1858
1955
|
</div>
|
|
1859
1956
|
</div>
|
|
1860
1957
|
</div>
|
|
1861
|
-
)
|
|
1958
|
+
);
|
|
1959
|
+
})}
|
|
1862
1960
|
|
|
1863
1961
|
{sending && (
|
|
1864
1962
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px', marginBottom: '16px' }}>
|
|
@@ -1872,7 +1970,7 @@ export default function ChatPanel({
|
|
|
1872
1970
|
{pendingApproval ? 'Waiting for approval' : 'Working'}
|
|
1873
1971
|
</span>
|
|
1874
1972
|
</div>
|
|
1875
|
-
{
|
|
1973
|
+
<AgentActivityTable activityView={agentActivities} />
|
|
1876
1974
|
</div>
|
|
1877
1975
|
</div>
|
|
1878
1976
|
)}
|
|
@@ -1954,6 +2052,7 @@ export default function ChatPanel({
|
|
|
1954
2052
|
const writeFile = pendingApproval.approval?.WriteFile
|
|
1955
2053
|
const applyPatch = pendingApproval.approval?.ApplyPatch
|
|
1956
2054
|
const diffText = writeFile?.diff || applyPatch?.diff
|
|
2055
|
+
const isAskUser = !!pendingApproval.approval?.AskUser
|
|
1957
2056
|
|
|
1958
2057
|
return (
|
|
1959
2058
|
<div className="message ai-message" style={{ width: '100%' }}>
|
|
@@ -1970,12 +2069,54 @@ export default function ChatPanel({
|
|
|
1970
2069
|
) : (
|
|
1971
2070
|
details.reason && <div className="approval-card-reason" style={{ whiteSpace: 'pre-wrap', fontFamily: 'monospace' }}>{details.reason}</div>
|
|
1972
2071
|
)}
|
|
2072
|
+
{isAskUser && (
|
|
2073
|
+
<div className="approval-card-input-container" style={{ marginTop: '12px', width: '100%' }}>
|
|
2074
|
+
<textarea
|
|
2075
|
+
style={{
|
|
2076
|
+
width: '100%',
|
|
2077
|
+
minHeight: '70px',
|
|
2078
|
+
padding: '10px',
|
|
2079
|
+
background: 'rgba(0, 0, 0, 0.2)',
|
|
2080
|
+
border: '1px solid rgba(255, 255, 255, 0.1)',
|
|
2081
|
+
borderRadius: '6px',
|
|
2082
|
+
color: 'var(--text-main, #f8fafc)',
|
|
2083
|
+
fontSize: '0.9rem',
|
|
2084
|
+
fontFamily: 'inherit',
|
|
2085
|
+
resize: 'vertical',
|
|
2086
|
+
outline: 'none',
|
|
2087
|
+
}}
|
|
2088
|
+
placeholder="Type your answer here..."
|
|
2089
|
+
value={askAnswer}
|
|
2090
|
+
onChange={(e) => setAskAnswer(e.target.value)}
|
|
2091
|
+
/>
|
|
2092
|
+
</div>
|
|
2093
|
+
)}
|
|
1973
2094
|
</div>
|
|
1974
|
-
|
|
1975
|
-
<
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
2095
|
+
{isAskUser ? (
|
|
2096
|
+
<div className="approval-card-actions">
|
|
2097
|
+
<button
|
|
2098
|
+
type="button"
|
|
2099
|
+
className="approval-btn approval-btn-approve"
|
|
2100
|
+
disabled={!askAnswer.trim()}
|
|
2101
|
+
onClick={() => onApproval(true, false, askAnswer)}
|
|
2102
|
+
>
|
|
2103
|
+
Submit Answer
|
|
2104
|
+
</button>
|
|
2105
|
+
<button
|
|
2106
|
+
type="button"
|
|
2107
|
+
className="approval-btn approval-btn-cancel"
|
|
2108
|
+
onClick={() => onApproval(false)}
|
|
2109
|
+
>
|
|
2110
|
+
Decline
|
|
2111
|
+
</button>
|
|
2112
|
+
</div>
|
|
2113
|
+
) : (
|
|
2114
|
+
<div className="approval-card-actions">
|
|
2115
|
+
<button type="button" className="approval-btn approval-btn-approve" onClick={() => onApproval(true)}>Approve</button>
|
|
2116
|
+
<button type="button" className="approval-btn" style={{ backgroundColor: 'rgba(16, 185, 129, 0.22)', borderColor: 'rgba(16, 185, 129, 0.4)', color: '#a7f3d0' }} onClick={() => onApproval(true, true)}>Approve this session</button>
|
|
2117
|
+
<button type="button" className="approval-btn approval-btn-cancel" onClick={() => onApproval(false)}>Cancel</button>
|
|
2118
|
+
</div>
|
|
2119
|
+
)}
|
|
1979
2120
|
</div>
|
|
1980
2121
|
</div>
|
|
1981
2122
|
</div>
|
|
@@ -2121,7 +2262,7 @@ export default function ChatPanel({
|
|
|
2121
2262
|
return <option key={provider} value={provider}>{displayName}</option>
|
|
2122
2263
|
})}
|
|
2123
2264
|
</select>
|
|
2124
|
-
{availableModels.length > 0 && (
|
|
2265
|
+
{(availableModels.length > 0 || activeModel) && (
|
|
2125
2266
|
<select
|
|
2126
2267
|
value={activeModel}
|
|
2127
2268
|
onChange={(event) => onSetModel(event.target.value)}
|
|
@@ -2179,6 +2320,9 @@ export default function ChatPanel({
|
|
|
2179
2320
|
</button>
|
|
2180
2321
|
</form>
|
|
2181
2322
|
</div>
|
|
2323
|
+
<p className="input-disclaimer">
|
|
2324
|
+
Mint Agent is an AI gateway. Responses via third-party APIs. Verify critical info.
|
|
2325
|
+
</p>
|
|
2182
2326
|
</section>
|
|
2183
2327
|
)
|
|
2184
2328
|
}
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { useState } from 'react'
|
|
2
2
|
|
|
3
|
-
export type DashboardView = 'chat' | 'pictures' | 'model' | 'imagine'
|
|
3
|
+
export type DashboardView = 'chat' | 'pictures' | 'model' | 'imagine' | 'workflows'
|
|
4
4
|
|
|
5
5
|
interface ChatSessionItem {
|
|
6
6
|
id: string
|
|
7
7
|
title: string
|
|
8
8
|
kind: string
|
|
9
|
+
createdAt?: string
|
|
10
|
+
updatedAt?: string
|
|
9
11
|
}
|
|
10
12
|
|
|
11
13
|
interface DashboardSidebarProps {
|
|
@@ -20,6 +22,8 @@ interface DashboardSidebarProps {
|
|
|
20
22
|
onDeleteConversation: (id: string) => void
|
|
21
23
|
onRenameConversation?: (id: string, newTitle: string) => void
|
|
22
24
|
onSetView: (view: DashboardView) => void
|
|
25
|
+
isSearchOpen: boolean
|
|
26
|
+
onSetSearchOpen: (open: boolean) => void
|
|
23
27
|
}
|
|
24
28
|
|
|
25
29
|
export default function DashboardSidebar({
|
|
@@ -34,6 +38,8 @@ export default function DashboardSidebar({
|
|
|
34
38
|
onDeleteConversation,
|
|
35
39
|
onRenameConversation,
|
|
36
40
|
onSetView,
|
|
41
|
+
isSearchOpen,
|
|
42
|
+
onSetSearchOpen,
|
|
37
43
|
}: DashboardSidebarProps) {
|
|
38
44
|
const [editingSessionId, setEditingSessionId] = useState<string | null>(null)
|
|
39
45
|
const [editTitleValue, setEditTitleValue] = useState('')
|
|
@@ -77,6 +83,16 @@ export default function DashboardSidebar({
|
|
|
77
83
|
<span>New Chat</span>
|
|
78
84
|
</button>
|
|
79
85
|
|
|
86
|
+
<button className="sidebar-top-action sidebar-search-btn" onClick={() => onSetSearchOpen(true)}>
|
|
87
|
+
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
88
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
89
|
+
<circle cx="11" cy="11" r="8"></circle>
|
|
90
|
+
<line x1="21" y1="21" x2="16.65" y2="16.65"></line>
|
|
91
|
+
</svg>
|
|
92
|
+
</span>
|
|
93
|
+
<span>Search Chats</span>
|
|
94
|
+
</button>
|
|
95
|
+
|
|
80
96
|
<button className={`sidebar-top-action ${view === 'chat' ? 'is-active' : ''}`} onClick={() => onSetView('chat')}>
|
|
81
97
|
<span aria-hidden="true" style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
82
98
|
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
|
|
@@ -16,6 +16,7 @@ interface ImageStudioPanelProps {
|
|
|
16
16
|
view: string
|
|
17
17
|
onRefreshPictures?: () => void
|
|
18
18
|
onSendToChat?: (dataUri: string, prompt: string) => void
|
|
19
|
+
onToggleMobileSidebar?: () => void
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
const ASPECT_OPTIONS: { value: AspectRatio; label: string; icon: string }[] = [
|
|
@@ -85,7 +86,7 @@ function SkeletonCard() {
|
|
|
85
86
|
)
|
|
86
87
|
}
|
|
87
88
|
|
|
88
|
-
export default function ImageStudioPanel({ view, onRefreshPictures, onSendToChat }: ImageStudioPanelProps) {
|
|
89
|
+
export default function ImageStudioPanel({ view, onRefreshPictures, onSendToChat, onToggleMobileSidebar }: ImageStudioPanelProps) {
|
|
89
90
|
const [prompt, setPrompt] = useState('')
|
|
90
91
|
const [negativePrompt, setNegativePrompt] = useState('')
|
|
91
92
|
const [showNegative, setShowNegative] = useState(false)
|
|
@@ -193,15 +194,28 @@ export default function ImageStudioPanel({ view, onRefreshPictures, onSendToChat
|
|
|
193
194
|
document.body.removeChild(a)
|
|
194
195
|
}
|
|
195
196
|
|
|
196
|
-
if (view !== 'imagine') return null
|
|
197
|
-
|
|
198
197
|
const modelOptions = PROVIDER_MODELS[selectedProvider] ?? []
|
|
199
198
|
|
|
200
199
|
return (
|
|
201
|
-
<div
|
|
200
|
+
<div
|
|
201
|
+
className={`img-studio ${view === 'imagine' ? 'is-visible' : ''}`}
|
|
202
|
+
id="image-studio-panel"
|
|
203
|
+
role="main"
|
|
204
|
+
aria-label="Image Studio"
|
|
205
|
+
aria-hidden={view !== 'imagine'}
|
|
206
|
+
>
|
|
202
207
|
{/* Header */}
|
|
203
208
|
<header className="img-studio-header">
|
|
204
209
|
<div className="img-studio-header-title">
|
|
210
|
+
<button
|
|
211
|
+
className="mobile-menu-btn"
|
|
212
|
+
type="button"
|
|
213
|
+
onClick={onToggleMobileSidebar}
|
|
214
|
+
aria-label="Toggle menu"
|
|
215
|
+
style={{ marginRight: '8px' }}
|
|
216
|
+
>
|
|
217
|
+
☰
|
|
218
|
+
</button>
|
|
205
219
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
|
|
206
220
|
<polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/>
|
|
207
221
|
</svg>
|