@pheem49/mint 1.8.0 → 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 +31 -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 +9 -0
- package/crates/mint-core/src/browser.rs +401 -0
- package/crates/mint-core/src/config.rs +69 -1
- package/crates/mint-core/src/lib.rs +5 -0
- 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 +1 -22
- package/package.json +1 -1
- package/src/renderer/src/components/ChatPanel.tsx +170 -33
- package/src/renderer/src/components/ImageStudioPanel.tsx +11 -1
- package/src/renderer/src/components/MintDashboard.tsx +40 -2
- package/src/renderer/src/components/Settings/PluginsTab.tsx +221 -41
- package/src/renderer/src/components/SettingsWindow.tsx +1 -9
- package/src/renderer/src/components/WorkspacePanel.tsx +66 -9
- package/src/renderer/src/css/image-studio.css +6 -0
- package/src/renderer/src/css/styles.css +64 -11
- package/src/renderer/src/index.css +12 -1
- package/src/renderer/src/tauri.ts +45 -4
- package/src/renderer/src-web/components/ChatPanel.tsx +163 -32
- package/src/renderer/src-web/components/ImageStudioPanel.tsx +11 -1
- package/src/renderer/src-web/components/MintDashboard.tsx +8 -3
- package/src/renderer/src-web/components/Settings/PluginsTab.tsx +221 -41
- package/src/renderer/src-web/components/SettingsWindow.tsx +1 -9
- package/src/renderer/src-web/css/image-studio.css +6 -0
- package/src/renderer/src-web/css/styles.css +100 -12
- package/src/renderer/src-web/index.css +12 -1
- package/src/renderer/src-web/tauri.ts +24 -2
- package/src-tauri/src/browser.rs +1 -141
- package/src-tauri/src/headless.rs +3 -2
- package/src-tauri/src/lib.rs +117 -26
- package/src-tauri/src/workflows.rs +56 -4
- package/src-tauri/tauri.conf.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState, useEffect } from 'react'
|
|
2
2
|
import { DEFAULT_CONFIG } from '../SettingsWindow'
|
|
3
|
-
import { listLearnedSkills, addLearnedSkill, deleteLearnedSkill, LearnedSkill } from '../../tauri'
|
|
3
|
+
import { listLearnedSkills, addLearnedSkill, deleteLearnedSkill, LearnedSkill, detectSystemTools, DetectedTools } from '../../tauri'
|
|
4
4
|
|
|
5
5
|
interface PluginsTabProps {
|
|
6
6
|
config: typeof DEFAULT_CONFIG
|
|
@@ -40,14 +40,49 @@ export default function PluginsTab({
|
|
|
40
40
|
const [skillsLoading, setSkillsLoading] = useState(false)
|
|
41
41
|
const [skillsError, setSkillsError] = useState('')
|
|
42
42
|
|
|
43
|
-
//
|
|
43
|
+
// Local state for auto-detected tools
|
|
44
|
+
const [detectedTools, setDetectedTools] = useState<DetectedTools>({
|
|
45
|
+
docker: false,
|
|
46
|
+
git: false,
|
|
47
|
+
gh: false,
|
|
48
|
+
node: false
|
|
49
|
+
})
|
|
50
|
+
const [detecting, setDetecting] = useState(false)
|
|
51
|
+
|
|
52
|
+
// Load skills and detect tools on mount
|
|
44
53
|
useEffect(() => {
|
|
45
54
|
fetchSkills()
|
|
55
|
+
detectTools()
|
|
46
56
|
}, [])
|
|
47
57
|
|
|
58
|
+
const detectTools = async () => {
|
|
59
|
+
setDetecting(true)
|
|
60
|
+
try {
|
|
61
|
+
const tools = await detectSystemTools()
|
|
62
|
+
setDetectedTools(tools)
|
|
63
|
+
} catch (e) {
|
|
64
|
+
console.error("Failed to detect tools:", e)
|
|
65
|
+
} finally {
|
|
66
|
+
setDetecting(false)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const handleEnableTool = (name: string, command: string, args: string[]) => {
|
|
71
|
+
const updatedMcp = {
|
|
72
|
+
...(config.mcpServers || {}),
|
|
73
|
+
[name]: {
|
|
74
|
+
command,
|
|
75
|
+
args,
|
|
76
|
+
env: {}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
updateField('mcpServers', updatedMcp)
|
|
80
|
+
}
|
|
81
|
+
|
|
48
82
|
const fetchSkills = async () => {
|
|
49
83
|
try {
|
|
50
|
-
const
|
|
84
|
+
const activeWorkspace = window.localStorage.getItem('mint:last-workspace-path') || undefined
|
|
85
|
+
const list = await listLearnedSkills(activeWorkspace)
|
|
51
86
|
setSkills(list)
|
|
52
87
|
} catch (e) {
|
|
53
88
|
console.error("Failed to load skills:", e)
|
|
@@ -122,6 +157,79 @@ export default function PluginsTab({
|
|
|
122
157
|
}
|
|
123
158
|
]
|
|
124
159
|
|
|
160
|
+
const mcpListItems: Array<{
|
|
161
|
+
name: string
|
|
162
|
+
command: string
|
|
163
|
+
args: string[]
|
|
164
|
+
icon: string
|
|
165
|
+
isEnabled: boolean
|
|
166
|
+
isConfigured: boolean
|
|
167
|
+
description: string
|
|
168
|
+
}> = []
|
|
169
|
+
|
|
170
|
+
Object.entries(config.mcpServers || {}).forEach(([name, srv]: [string, any]) => {
|
|
171
|
+
let icon = '⚙';
|
|
172
|
+
if (name === 'docker') icon = '🐳';
|
|
173
|
+
else if (name === 'git' || name === 'gitkraken') icon = '🐙';
|
|
174
|
+
else if (name === 'github') icon = '🐱';
|
|
175
|
+
else if (name === 'node') icon = '🟢';
|
|
176
|
+
|
|
177
|
+
mcpListItems.push({
|
|
178
|
+
name,
|
|
179
|
+
command: srv.command,
|
|
180
|
+
args: srv.args || [],
|
|
181
|
+
icon,
|
|
182
|
+
isEnabled: true,
|
|
183
|
+
isConfigured: true,
|
|
184
|
+
description: `Command: ${srv.command} ${(srv.args || []).join(' ')}`
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
if (detectedTools.docker && !config.mcpServers?.docker) {
|
|
189
|
+
mcpListItems.push({
|
|
190
|
+
name: 'docker',
|
|
191
|
+
command: 'npx',
|
|
192
|
+
args: ['-y', '@proxeus/mcp-docker-server'],
|
|
193
|
+
icon: '🐳',
|
|
194
|
+
isEnabled: false,
|
|
195
|
+
isConfigured: false,
|
|
196
|
+
description: 'Docker MCP Server (Discovered)'
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
if (detectedTools.git && !config.mcpServers?.git) {
|
|
200
|
+
mcpListItems.push({
|
|
201
|
+
name: 'git',
|
|
202
|
+
command: 'npx',
|
|
203
|
+
args: ['-y', '@modelcontextprotocol/server-git'],
|
|
204
|
+
icon: '🐙',
|
|
205
|
+
isEnabled: false,
|
|
206
|
+
isConfigured: false,
|
|
207
|
+
description: 'Git MCP Server (Discovered)'
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
if (detectedTools.gh && !config.mcpServers?.github) {
|
|
211
|
+
mcpListItems.push({
|
|
212
|
+
name: 'github',
|
|
213
|
+
command: 'npx',
|
|
214
|
+
args: ['-y', '@modelcontextprotocol/server-github'],
|
|
215
|
+
icon: '🐱',
|
|
216
|
+
isEnabled: false,
|
|
217
|
+
isConfigured: false,
|
|
218
|
+
description: 'GitHub MCP Server (Discovered)'
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
if (detectedTools.node && !config.mcpServers?.node) {
|
|
222
|
+
mcpListItems.push({
|
|
223
|
+
name: 'node',
|
|
224
|
+
command: 'npx',
|
|
225
|
+
args: ['-y', '@modelcontextprotocol/server-node'],
|
|
226
|
+
icon: '🟢',
|
|
227
|
+
isEnabled: false,
|
|
228
|
+
isConfigured: false,
|
|
229
|
+
description: 'NodeJS Runtime MCP Server (Discovered)'
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
|
|
125
233
|
return (
|
|
126
234
|
<div className="tab-pane active" style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
|
|
127
235
|
{/* ── Learned AI Skills ── */}
|
|
@@ -154,7 +262,7 @@ export default function PluginsTab({
|
|
|
154
262
|
{skills.map((s) => (
|
|
155
263
|
<div className="skill-card" key={s.id} style={{
|
|
156
264
|
display: 'flex',
|
|
157
|
-
alignItems: '
|
|
265
|
+
alignItems: 'center',
|
|
158
266
|
justifyContent: 'space-between',
|
|
159
267
|
padding: '16px 20px',
|
|
160
268
|
border: '1px solid var(--border)',
|
|
@@ -163,7 +271,7 @@ export default function PluginsTab({
|
|
|
163
271
|
gap: '16px'
|
|
164
272
|
}}>
|
|
165
273
|
<div style={{ flex: 1, minWidth: 0 }}>
|
|
166
|
-
<div style={{ display: 'flex', alignItems: 'center', gap: '8px'
|
|
274
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
167
275
|
<span className="badge" style={{
|
|
168
276
|
background: 'var(--accent)',
|
|
169
277
|
color: '#fff',
|
|
@@ -172,24 +280,31 @@ export default function PluginsTab({
|
|
|
172
280
|
padding: '2px 8px',
|
|
173
281
|
borderRadius: '4px'
|
|
174
282
|
}}>{s.name}</span>
|
|
283
|
+
{s.location && (
|
|
284
|
+
<span className="location-badge" style={{
|
|
285
|
+
background: s.location === 'workspace'
|
|
286
|
+
? 'rgba(16, 185, 129, 0.15)'
|
|
287
|
+
: s.location === 'global'
|
|
288
|
+
? 'rgba(59, 130, 246, 0.15)'
|
|
289
|
+
: 'rgba(139, 92, 246, 0.15)',
|
|
290
|
+
color: s.location === 'workspace'
|
|
291
|
+
? '#10b981'
|
|
292
|
+
: s.location === 'global'
|
|
293
|
+
? '#3b82f6'
|
|
294
|
+
: '#8b5cf6',
|
|
295
|
+
fontSize: '0.7rem',
|
|
296
|
+
fontWeight: '600',
|
|
297
|
+
padding: '2px 6px',
|
|
298
|
+
borderRadius: '4px',
|
|
299
|
+
textTransform: 'capitalize'
|
|
300
|
+
}}>
|
|
301
|
+
{s.location === 'database' ? 'Taught' : s.location}
|
|
302
|
+
</span>
|
|
303
|
+
)}
|
|
175
304
|
<span style={{ fontSize: '0.8rem', color: 'var(--text-muted)', textOverflow: 'ellipsis', overflow: 'hidden', whiteSpace: 'nowrap' }}>
|
|
176
305
|
Source: {s.sourcePath}
|
|
177
306
|
</span>
|
|
178
307
|
</div>
|
|
179
|
-
<div style={{
|
|
180
|
-
fontSize: '0.85rem',
|
|
181
|
-
color: 'var(--text-main)',
|
|
182
|
-
whiteSpace: 'pre-wrap',
|
|
183
|
-
maxHeight: '120px',
|
|
184
|
-
overflowY: 'auto',
|
|
185
|
-
background: 'rgba(0,0,0,0.12)',
|
|
186
|
-
padding: '10px 14px',
|
|
187
|
-
borderRadius: '8px',
|
|
188
|
-
fontFamily: 'monospace',
|
|
189
|
-
lineHeight: '1.4'
|
|
190
|
-
}}>
|
|
191
|
-
{s.content}
|
|
192
|
-
</div>
|
|
193
308
|
</div>
|
|
194
309
|
<button
|
|
195
310
|
className="btn btn-danger"
|
|
@@ -255,33 +370,98 @@ export default function PluginsTab({
|
|
|
255
370
|
</div>
|
|
256
371
|
|
|
257
372
|
<div className="mcp-list">
|
|
258
|
-
{
|
|
259
|
-
<div
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
373
|
+
{mcpListItems.length === 0 ? (
|
|
374
|
+
<div style={{ padding: '20px', textAlign: 'center', color: 'var(--text-muted)' }}>
|
|
375
|
+
No MCP servers configured or discovered.
|
|
376
|
+
</div>
|
|
377
|
+
) : (
|
|
378
|
+
mcpListItems.map((item) => (
|
|
379
|
+
<div
|
|
380
|
+
className="plugin-card"
|
|
381
|
+
key={item.name}
|
|
382
|
+
style={{
|
|
383
|
+
display: 'flex',
|
|
384
|
+
alignItems: 'center',
|
|
385
|
+
justifyContent: 'space-between',
|
|
386
|
+
padding: '16px 20px',
|
|
387
|
+
border: '1px solid var(--border)',
|
|
388
|
+
borderRadius: '12px',
|
|
389
|
+
background: item.isConfigured ? 'var(--surface-bg)' : 'rgba(16, 185, 129, 0.03)',
|
|
390
|
+
opacity: item.isConfigured ? 1 : 0.65,
|
|
391
|
+
marginBottom: '10px',
|
|
392
|
+
transition: 'all 0.2s'
|
|
393
|
+
}}
|
|
394
|
+
>
|
|
395
|
+
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
|
396
|
+
<div className="plugin-icon" style={{ fontSize: '1.5rem', width: '40px', height: '40px', display: 'grid', placeItems: 'center', background: 'var(--surface-strong)', borderRadius: '10px' }}>
|
|
397
|
+
{item.icon}
|
|
398
|
+
</div>
|
|
399
|
+
<div className="plugin-info">
|
|
400
|
+
<div className="plugin-name" style={{ fontWeight: '600', color: 'var(--text-main)' }}>
|
|
401
|
+
{item.name} {!item.isConfigured && <span style={{ fontSize: '0.75rem', padding: '2px 6px', background: 'rgba(16, 185, 129, 0.15)', color: '#10b981', borderRadius: '4px', marginLeft: '6px' }}>Discovered</span>}
|
|
402
|
+
</div>
|
|
403
|
+
<div className="plugin-desc" style={{ fontFamily: 'monospace', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
|
|
404
|
+
{item.description}
|
|
405
|
+
</div>
|
|
275
406
|
</div>
|
|
276
407
|
</div>
|
|
408
|
+
<div className="plugin-actions" style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
|
|
409
|
+
{item.isConfigured && (
|
|
410
|
+
<button
|
|
411
|
+
className="btn-icon-danger"
|
|
412
|
+
onClick={() => handleRemoveMcpServer(item.name)}
|
|
413
|
+
style={{
|
|
414
|
+
background: 'transparent',
|
|
415
|
+
border: 'none',
|
|
416
|
+
color: '#ef4444',
|
|
417
|
+
cursor: 'pointer',
|
|
418
|
+
padding: '6px',
|
|
419
|
+
display: 'flex',
|
|
420
|
+
alignItems: 'center',
|
|
421
|
+
justifyContent: 'center',
|
|
422
|
+
borderRadius: '6px',
|
|
423
|
+
transition: 'background 0.2s'
|
|
424
|
+
}}
|
|
425
|
+
title="Delete MCP Server"
|
|
426
|
+
>
|
|
427
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
428
|
+
<polyline points="3 6 5 6 21 6"></polyline>
|
|
429
|
+
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
|
|
430
|
+
<line x1="10" y1="11" x2="10" y2="17"></line>
|
|
431
|
+
<line x1="14" y1="11" x2="14" y2="17"></line>
|
|
432
|
+
</svg>
|
|
433
|
+
</button>
|
|
434
|
+
)}
|
|
435
|
+
<label className="settings-toggle-switch">
|
|
436
|
+
<input
|
|
437
|
+
type="checkbox"
|
|
438
|
+
checked={item.isEnabled}
|
|
439
|
+
onChange={(e) => {
|
|
440
|
+
if (e.target.checked) {
|
|
441
|
+
handleEnableTool(item.name, item.command, item.args);
|
|
442
|
+
} else {
|
|
443
|
+
handleRemoveMcpServer(item.name);
|
|
444
|
+
}
|
|
445
|
+
}}
|
|
446
|
+
/>
|
|
447
|
+
<span className="settings-toggle-slider" />
|
|
448
|
+
</label>
|
|
449
|
+
</div>
|
|
277
450
|
</div>
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
</div>
|
|
281
|
-
</div>
|
|
282
|
-
))}
|
|
451
|
+
))
|
|
452
|
+
)}
|
|
283
453
|
</div>
|
|
284
454
|
|
|
455
|
+
{/* Save settings alert */}
|
|
456
|
+
{((detectedTools.docker && !config.mcpServers?.docker) ||
|
|
457
|
+
(detectedTools.git && !config.mcpServers?.git) ||
|
|
458
|
+
(detectedTools.gh && !config.mcpServers?.github) ||
|
|
459
|
+
(detectedTools.node && !config.mcpServers?.node)) && (
|
|
460
|
+
<div style={{ marginTop: '12px', padding: '12px 16px', background: 'rgba(255,193,7,0.1)', border: '1px solid rgba(255,193,7,0.3)', borderRadius: '8px', fontSize: '0.8rem', color: '#ffb300', fontWeight: '500', display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
461
|
+
<span>⚠️ After toggling a plugin, please click the "Save Settings" button at the bottom of the window to persist changes.</span>
|
|
462
|
+
</div>
|
|
463
|
+
)}
|
|
464
|
+
|
|
285
465
|
<div className="add-mcp-box" style={{
|
|
286
466
|
marginTop: '20px',
|
|
287
467
|
padding: '20px',
|
|
@@ -157,15 +157,7 @@ export const LOCAL_MODELS = [
|
|
|
157
157
|
'lmstudio-community/gemma-2-9b-it-GGUF'
|
|
158
158
|
]
|
|
159
159
|
|
|
160
|
-
export const OLLAMA_MODELS = [
|
|
161
|
-
'llama3:latest',
|
|
162
|
-
'llama3.1:latest',
|
|
163
|
-
'llama3.2:latest',
|
|
164
|
-
'gemma2:latest',
|
|
165
|
-
'mistral:latest',
|
|
166
|
-
'phi3:latest',
|
|
167
|
-
'qwen2.5:latest'
|
|
168
|
-
]
|
|
160
|
+
export const OLLAMA_MODELS: string[] = []
|
|
169
161
|
|
|
170
162
|
export default function SettingsWindow() {
|
|
171
163
|
const [activeTab, setActiveTab] = useState<TabType>('sect-general')
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useEffect, useState } from 'react'
|
|
2
|
-
import { getWorkspaceTree, type WorkspaceTreeEntry } from '../tauri'
|
|
1
|
+
import React, { useEffect, useState } from 'react'
|
|
2
|
+
import { getWorkspaceTree, type WorkspaceTreeEntry, createWorkspaceFile, createWorkspaceFolder, deleteWorkspaceItem } from '../tauri'
|
|
3
3
|
import folderIcon from 'material-icon-theme/icons/folder.svg?url'
|
|
4
4
|
import folderOpenIcon from 'material-icon-theme/icons/folder-open.svg?url'
|
|
5
5
|
import folderComponentsIcon from 'material-icon-theme/icons/folder-components.svg?url'
|
|
@@ -147,7 +147,7 @@ function materialFileIcon(name: string, fileExtension: string) {
|
|
|
147
147
|
return FILE_ICONS_BY_NAME[name.toLowerCase()] || FILE_ICONS_BY_EXTENSION[fileExtension] || documentIcon
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
function TreeNode({ entry, level }: { entry: WorkspaceTreeEntry; level: number; key?: string }) {
|
|
150
|
+
function TreeNode({ entry, level, onRefresh, workspacePath }: { entry: WorkspaceTreeEntry; level: number; onRefresh: () => void; workspacePath: string; key?: string }) {
|
|
151
151
|
const [open, setOpen] = useState(level < 1)
|
|
152
152
|
const isDirectory = entry.kind === 'directory'
|
|
153
153
|
const hasChildren = entry.children.length > 0
|
|
@@ -156,6 +156,20 @@ function TreeNode({ entry, level }: { entry: WorkspaceTreeEntry; level: number;
|
|
|
156
156
|
const materialIcon = isDirectory ? materialFolderIcon(entry.name, open) : materialFileIcon(entry.name, fileExtension)
|
|
157
157
|
const dragText = `@${entry.path}`
|
|
158
158
|
|
|
159
|
+
const handleContextMenu = async (event: React.MouseEvent) => {
|
|
160
|
+
event.preventDefault()
|
|
161
|
+
const confirmed = confirm(`Are you sure you want to delete "${entry.name}"?`)
|
|
162
|
+
if (!confirmed) return
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const absolutePath = `${workspacePath}/${entry.path}`
|
|
166
|
+
await deleteWorkspaceItem(absolutePath)
|
|
167
|
+
onRefresh()
|
|
168
|
+
} catch (err) {
|
|
169
|
+
alert(`Failed to delete item: ${err instanceof Error ? err.message : String(err)}`)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
159
173
|
return (
|
|
160
174
|
<div className="workspace-tree-node">
|
|
161
175
|
<button
|
|
@@ -163,6 +177,7 @@ function TreeNode({ entry, level }: { entry: WorkspaceTreeEntry; level: number;
|
|
|
163
177
|
className={`workspace-tree-row ${isDirectory ? 'is-directory' : 'is-file'}`}
|
|
164
178
|
style={{ paddingLeft: `${level * 16 + 8}px` }}
|
|
165
179
|
onClick={() => isDirectory && hasChildren && setOpen((current) => !current)}
|
|
180
|
+
onContextMenu={handleContextMenu}
|
|
166
181
|
title={entry.path}
|
|
167
182
|
draggable
|
|
168
183
|
onDragStart={(event) => {
|
|
@@ -186,7 +201,7 @@ function TreeNode({ entry, level }: { entry: WorkspaceTreeEntry; level: number;
|
|
|
186
201
|
{isDirectory && open && hasChildren && (
|
|
187
202
|
<div className="workspace-tree-children">
|
|
188
203
|
{entry.children.map((child) => (
|
|
189
|
-
<TreeNode key={child.path} entry={child} level={level + 1} />
|
|
204
|
+
<TreeNode key={child.path} entry={child} level={level + 1} onRefresh={onRefresh} workspacePath={workspacePath} />
|
|
190
205
|
))}
|
|
191
206
|
</div>
|
|
192
207
|
)}
|
|
@@ -217,11 +232,52 @@ export default function WorkspacePanel({ agentMode, sending, workspacePath, onEn
|
|
|
217
232
|
|
|
218
233
|
useEffect(() => {
|
|
219
234
|
refresh()
|
|
235
|
+
|
|
236
|
+
// Refresh when the window gains focus (e.g., switching back from VS Code)
|
|
237
|
+
const handleFocus = () => {
|
|
238
|
+
refresh()
|
|
239
|
+
}
|
|
240
|
+
window.addEventListener('focus', handleFocus)
|
|
241
|
+
|
|
242
|
+
// Poll every 15 seconds to catch edits/updates in real-time
|
|
243
|
+
const interval = setInterval(() => {
|
|
244
|
+
refresh()
|
|
245
|
+
}, 15000)
|
|
246
|
+
|
|
247
|
+
return () => {
|
|
248
|
+
window.removeEventListener('focus', handleFocus)
|
|
249
|
+
clearInterval(interval)
|
|
250
|
+
}
|
|
220
251
|
}, [workspacePath])
|
|
221
252
|
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
253
|
+
const handleCreateFile = async () => {
|
|
254
|
+
if (!workspacePath.trim()) return
|
|
255
|
+
const name = prompt('Enter name of new file:')
|
|
256
|
+
if (!name || !name.trim()) return
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
setError('')
|
|
260
|
+
const fullPath = `${workspacePath}/${name.trim()}`
|
|
261
|
+
await createWorkspaceFile(fullPath)
|
|
262
|
+
await refresh()
|
|
263
|
+
} catch (err) {
|
|
264
|
+
setError(err instanceof Error ? err.message : String(err))
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const handleCreateFolder = async () => {
|
|
269
|
+
if (!workspacePath.trim()) return
|
|
270
|
+
const name = prompt('Enter name of new folder:')
|
|
271
|
+
if (!name || !name.trim()) return
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
setError('')
|
|
275
|
+
const fullPath = `${workspacePath}/${name.trim()}`
|
|
276
|
+
await createWorkspaceFolder(fullPath)
|
|
277
|
+
await refresh()
|
|
278
|
+
} catch (err) {
|
|
279
|
+
setError(err instanceof Error ? err.message : String(err))
|
|
280
|
+
}
|
|
225
281
|
}
|
|
226
282
|
|
|
227
283
|
return (
|
|
@@ -237,7 +293,8 @@ export default function WorkspacePanel({ agentMode, sending, workspacePath, onEn
|
|
|
237
293
|
</header>
|
|
238
294
|
|
|
239
295
|
<div className="workspace-panel-actions">
|
|
240
|
-
<button type="button" onClick={
|
|
296
|
+
<button type="button" onClick={handleCreateFile} disabled={!workspacePath.trim()}>New File</button>
|
|
297
|
+
<button type="button" onClick={handleCreateFolder} disabled={!workspacePath.trim()}>New Folder</button>
|
|
241
298
|
<button type="button" onClick={refresh} disabled={!workspacePath.trim()}>Refresh</button>
|
|
242
299
|
</div>
|
|
243
300
|
|
|
@@ -255,7 +312,7 @@ export default function WorkspacePanel({ agentMode, sending, workspacePath, onEn
|
|
|
255
312
|
</div>
|
|
256
313
|
<div className="workspace-tree">
|
|
257
314
|
{tree.children.map((entry) => (
|
|
258
|
-
<TreeNode key={entry.path} entry={entry} level={0} />
|
|
315
|
+
<TreeNode key={entry.path} entry={entry} level={0} onRefresh={refresh} workspacePath={workspacePath} />
|
|
259
316
|
))}
|
|
260
317
|
</div>
|
|
261
318
|
</>
|
|
@@ -873,7 +873,7 @@ h1 {
|
|
|
873
873
|
|
|
874
874
|
.assistant-workspace.model-hidden .conversation-panel {
|
|
875
875
|
flex: 1;
|
|
876
|
-
max-width:
|
|
876
|
+
max-width: 1100px;
|
|
877
877
|
margin: 0 auto;
|
|
878
878
|
}
|
|
879
879
|
|
|
@@ -1167,6 +1167,51 @@ h1 {
|
|
|
1167
1167
|
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
|
|
1168
1168
|
}
|
|
1169
1169
|
|
|
1170
|
+
.input-disclaimer {
|
|
1171
|
+
text-align: center;
|
|
1172
|
+
font-size: 0.7rem;
|
|
1173
|
+
color: var(--text-muted);
|
|
1174
|
+
opacity: 0.55;
|
|
1175
|
+
padding: 4px 20px 10px;
|
|
1176
|
+
margin: 0;
|
|
1177
|
+
flex-shrink: 0;
|
|
1178
|
+
line-height: 1.5;
|
|
1179
|
+
user-select: none;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
|
|
1183
|
+
/* Chat Header (Mint Agent title bar inside panel) */
|
|
1184
|
+
.chat-header {
|
|
1185
|
+
display: flex;
|
|
1186
|
+
align-items: center;
|
|
1187
|
+
gap: 12px;
|
|
1188
|
+
padding: 10px 18px;
|
|
1189
|
+
background: var(--chrome-bg);
|
|
1190
|
+
backdrop-filter: var(--glass-blur);
|
|
1191
|
+
-webkit-backdrop-filter: var(--glass-blur);
|
|
1192
|
+
border-bottom: 1px solid var(--border);
|
|
1193
|
+
position: sticky;
|
|
1194
|
+
top: 0;
|
|
1195
|
+
z-index: 50;
|
|
1196
|
+
height: 52px;
|
|
1197
|
+
flex-shrink: 0;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
.chat-header-title {
|
|
1201
|
+
display: flex;
|
|
1202
|
+
align-items: center;
|
|
1203
|
+
gap: 8px;
|
|
1204
|
+
font-weight: 600;
|
|
1205
|
+
color: var(--text-main);
|
|
1206
|
+
font-size: 0.92rem;
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
.chat-header-logo {
|
|
1210
|
+
width: 20px;
|
|
1211
|
+
height: 20px;
|
|
1212
|
+
object-fit: contain;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1170
1215
|
#chat-form {
|
|
1171
1216
|
display: flex;
|
|
1172
1217
|
gap: 12px;
|
|
@@ -1428,7 +1473,7 @@ h1 {
|
|
|
1428
1473
|
gap: 8px;
|
|
1429
1474
|
margin-bottom: 8px;
|
|
1430
1475
|
margin-top: 0;
|
|
1431
|
-
max-width:
|
|
1476
|
+
max-width: 100%;
|
|
1432
1477
|
}
|
|
1433
1478
|
|
|
1434
1479
|
.agent-activity-toggle {
|
|
@@ -1463,7 +1508,7 @@ h1 {
|
|
|
1463
1508
|
border: 0;
|
|
1464
1509
|
border-radius: 0;
|
|
1465
1510
|
color: var(--text-main);
|
|
1466
|
-
width:
|
|
1511
|
+
width: 100%;
|
|
1467
1512
|
max-width: 100%;
|
|
1468
1513
|
padding: 4px 0;
|
|
1469
1514
|
box-shadow: none;
|
|
@@ -2705,7 +2750,7 @@ input:checked + .slider:before {
|
|
|
2705
2750
|
|
|
2706
2751
|
.workspace-panel-actions {
|
|
2707
2752
|
display: grid;
|
|
2708
|
-
grid-template-columns:
|
|
2753
|
+
grid-template-columns: repeat(3, 1fr);
|
|
2709
2754
|
gap: 8px;
|
|
2710
2755
|
padding: 0 10px 12px 0;
|
|
2711
2756
|
}
|
|
@@ -3321,12 +3366,12 @@ input:checked + .slider:before {
|
|
|
3321
3366
|
.assistant-workspace.model-hidden .conversation-panel {
|
|
3322
3367
|
position: relative;
|
|
3323
3368
|
display: grid;
|
|
3324
|
-
grid-template-rows: minmax(0, 1fr) auto;
|
|
3325
|
-
width: min(
|
|
3369
|
+
grid-template-rows: auto minmax(0, 1fr) auto auto;
|
|
3370
|
+
width: min(1100px, calc(100% - 64px));
|
|
3326
3371
|
height: 100%;
|
|
3327
3372
|
max-width: none;
|
|
3328
3373
|
margin: 0 auto;
|
|
3329
|
-
padding:
|
|
3374
|
+
padding: 0 0 8px;
|
|
3330
3375
|
grid-column: 1 / -1;
|
|
3331
3376
|
z-index: 1;
|
|
3332
3377
|
}
|
|
@@ -3349,13 +3394,13 @@ input:checked + .slider:before {
|
|
|
3349
3394
|
}
|
|
3350
3395
|
|
|
3351
3396
|
.assistant-workspace.model-hidden .conversation-panel.is-empty {
|
|
3352
|
-
grid-template-rows: minmax(0, 1fr);
|
|
3353
|
-
align-content:
|
|
3397
|
+
grid-template-rows: auto minmax(0, 1fr) auto auto;
|
|
3398
|
+
align-content: stretch;
|
|
3354
3399
|
justify-content: stretch;
|
|
3355
|
-
width: min(
|
|
3400
|
+
width: min(1100px, calc(100% - 64px));
|
|
3356
3401
|
margin: 0 auto;
|
|
3357
3402
|
padding-top: 0;
|
|
3358
|
-
padding-bottom:
|
|
3403
|
+
padding-bottom: 8px;
|
|
3359
3404
|
}
|
|
3360
3405
|
|
|
3361
3406
|
.conversation-panel.is-empty .chat-container {
|
|
@@ -4239,3 +4284,11 @@ input:checked + .slider:before {
|
|
|
4239
4284
|
font-size: 0.9rem;
|
|
4240
4285
|
}
|
|
4241
4286
|
|
|
4287
|
+
/* Allow global text selection everywhere */
|
|
4288
|
+
* {
|
|
4289
|
+
user-select: text !important;
|
|
4290
|
+
-webkit-user-select: text !important;
|
|
4291
|
+
-moz-user-select: text !important;
|
|
4292
|
+
-ms-user-select: text !important;
|
|
4293
|
+
}
|
|
4294
|
+
|
|
@@ -380,7 +380,6 @@ button {
|
|
|
380
380
|
}
|
|
381
381
|
}
|
|
382
382
|
|
|
383
|
-
/* Premium Markdown Styles for Chat Messages */
|
|
384
383
|
.chat-inline-code {
|
|
385
384
|
background: rgba(16, 185, 129, 0.15);
|
|
386
385
|
border: 1px solid rgba(16, 185, 129, 0.25);
|
|
@@ -391,6 +390,18 @@ button {
|
|
|
391
390
|
font-size: 0.88em;
|
|
392
391
|
}
|
|
393
392
|
|
|
393
|
+
.chat-mention {
|
|
394
|
+
background: color-mix(in srgb, var(--accent, #10b981) 12%, transparent);
|
|
395
|
+
border: 1px solid color-mix(in srgb, var(--accent, #10b981) 25%, transparent);
|
|
396
|
+
color: var(--accent, #34d399);
|
|
397
|
+
padding: 2px 6px;
|
|
398
|
+
border-radius: 6px;
|
|
399
|
+
font-weight: 500;
|
|
400
|
+
font-size: 0.9em;
|
|
401
|
+
display: inline-block;
|
|
402
|
+
margin: 0 2px;
|
|
403
|
+
}
|
|
404
|
+
|
|
394
405
|
.chat-code-block-container {
|
|
395
406
|
display: flex;
|
|
396
407
|
flex-direction: column;
|