@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.
Files changed (41) hide show
  1. package/.github/workflows/release.yml +1 -1
  2. package/Cargo.lock +34 -3
  3. package/Cargo.toml +2 -2
  4. package/README.md +31 -0
  5. package/Release_Note.md +95 -0
  6. package/assets/Mint_CLI.png +0 -0
  7. package/crates/mint-cli/Cargo.toml +1 -0
  8. package/crates/mint-cli/src/agent.rs +142 -48
  9. package/crates/mint-cli/src/main.rs +231 -22
  10. package/crates/mint-core/src/api_server.rs +9 -0
  11. package/crates/mint-core/src/browser.rs +401 -0
  12. package/crates/mint-core/src/config.rs +69 -1
  13. package/crates/mint-core/src/lib.rs +5 -0
  14. package/crates/mint-core/src/orchestration.rs +137 -1
  15. package/crates/mint-core/src/skills.rs +113 -4
  16. package/crates/mint-core/src/workflows.rs +1 -22
  17. package/package.json +1 -1
  18. package/src/renderer/src/components/ChatPanel.tsx +170 -33
  19. package/src/renderer/src/components/ImageStudioPanel.tsx +11 -1
  20. package/src/renderer/src/components/MintDashboard.tsx +40 -2
  21. package/src/renderer/src/components/Settings/PluginsTab.tsx +221 -41
  22. package/src/renderer/src/components/SettingsWindow.tsx +1 -9
  23. package/src/renderer/src/components/WorkspacePanel.tsx +66 -9
  24. package/src/renderer/src/css/image-studio.css +6 -0
  25. package/src/renderer/src/css/styles.css +64 -11
  26. package/src/renderer/src/index.css +12 -1
  27. package/src/renderer/src/tauri.ts +45 -4
  28. package/src/renderer/src-web/components/ChatPanel.tsx +163 -32
  29. package/src/renderer/src-web/components/ImageStudioPanel.tsx +11 -1
  30. package/src/renderer/src-web/components/MintDashboard.tsx +8 -3
  31. package/src/renderer/src-web/components/Settings/PluginsTab.tsx +221 -41
  32. package/src/renderer/src-web/components/SettingsWindow.tsx +1 -9
  33. package/src/renderer/src-web/css/image-studio.css +6 -0
  34. package/src/renderer/src-web/css/styles.css +100 -12
  35. package/src/renderer/src-web/index.css +12 -1
  36. package/src/renderer/src-web/tauri.ts +24 -2
  37. package/src-tauri/src/browser.rs +1 -141
  38. package/src-tauri/src/headless.rs +3 -2
  39. package/src-tauri/src/lib.rs +117 -26
  40. package/src-tauri/src/workflows.rs +56 -4
  41. 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 '../../components/SettingsWindow'
3
- import { listLearnedSkills, addLearnedSkill, deleteLearnedSkill, LearnedSkill } from '../../../src/tauri'
3
+ import { listLearnedSkills, addLearnedSkill, deleteLearnedSkill, LearnedSkill, detectSystemTools, DetectedTools } from '../../../src/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
- // Load skills on mount
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 list = await listLearnedSkills()
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)
@@ -121,6 +156,79 @@ export default function PluginsTab({
121
156
  }
122
157
  ]
123
158
 
159
+ const mcpListItems: Array<{
160
+ name: string
161
+ command: string
162
+ args: string[]
163
+ icon: string
164
+ isEnabled: boolean
165
+ isConfigured: boolean
166
+ description: string
167
+ }> = []
168
+
169
+ Object.entries(config.mcpServers || {}).forEach(([name, srv]: [string, any]) => {
170
+ let icon = '⚙';
171
+ if (name === 'docker') icon = '🐳';
172
+ else if (name === 'git' || name === 'gitkraken') icon = '🐙';
173
+ else if (name === 'github') icon = '🐱';
174
+ else if (name === 'node') icon = '🟢';
175
+
176
+ mcpListItems.push({
177
+ name,
178
+ command: srv.command,
179
+ args: srv.args || [],
180
+ icon,
181
+ isEnabled: true,
182
+ isConfigured: true,
183
+ description: `Command: ${srv.command} ${(srv.args || []).join(' ')}`
184
+ });
185
+ });
186
+
187
+ if (detectedTools.docker && !config.mcpServers?.docker) {
188
+ mcpListItems.push({
189
+ name: 'docker',
190
+ command: 'npx',
191
+ args: ['-y', '@proxeus/mcp-docker-server'],
192
+ icon: '🐳',
193
+ isEnabled: false,
194
+ isConfigured: false,
195
+ description: 'Docker MCP Server (Discovered)'
196
+ });
197
+ }
198
+ if (detectedTools.git && !config.mcpServers?.git) {
199
+ mcpListItems.push({
200
+ name: 'git',
201
+ command: 'npx',
202
+ args: ['-y', '@modelcontextprotocol/server-git'],
203
+ icon: '🐙',
204
+ isEnabled: false,
205
+ isConfigured: false,
206
+ description: 'Git MCP Server (Discovered)'
207
+ });
208
+ }
209
+ if (detectedTools.gh && !config.mcpServers?.github) {
210
+ mcpListItems.push({
211
+ name: 'github',
212
+ command: 'npx',
213
+ args: ['-y', '@modelcontextprotocol/server-github'],
214
+ icon: '🐱',
215
+ isEnabled: false,
216
+ isConfigured: false,
217
+ description: 'GitHub MCP Server (Discovered)'
218
+ });
219
+ }
220
+ if (detectedTools.node && !config.mcpServers?.node) {
221
+ mcpListItems.push({
222
+ name: 'node',
223
+ command: 'npx',
224
+ args: ['-y', '@modelcontextprotocol/server-node'],
225
+ icon: '🟢',
226
+ isEnabled: false,
227
+ isConfigured: false,
228
+ description: 'NodeJS Runtime MCP Server (Discovered)'
229
+ });
230
+ }
231
+
124
232
  return (
125
233
  <div className="tab-pane active" style={{ display: 'flex', flexDirection: 'column', gap: '24px' }}>
126
234
  {/* ── Learned AI Skills ── */}
@@ -153,7 +261,7 @@ export default function PluginsTab({
153
261
  {skills.map((s) => (
154
262
  <div className="skill-card" key={s.id} style={{
155
263
  display: 'flex',
156
- alignItems: 'flex-start',
264
+ alignItems: 'center',
157
265
  justifyContent: 'space-between',
158
266
  padding: '16px 20px',
159
267
  border: '1px solid var(--border)',
@@ -162,7 +270,7 @@ export default function PluginsTab({
162
270
  gap: '16px'
163
271
  }}>
164
272
  <div style={{ flex: 1, minWidth: 0 }}>
165
- <div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '6px' }}>
273
+ <div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
166
274
  <span className="badge" style={{
167
275
  background: 'var(--accent)',
168
276
  color: '#fff',
@@ -171,24 +279,31 @@ export default function PluginsTab({
171
279
  padding: '2px 8px',
172
280
  borderRadius: '4px'
173
281
  }}>{s.name}</span>
282
+ {s.location && (
283
+ <span className="location-badge" style={{
284
+ background: s.location === 'workspace'
285
+ ? 'rgba(16, 185, 129, 0.15)'
286
+ : s.location === 'global'
287
+ ? 'rgba(59, 130, 246, 0.15)'
288
+ : 'rgba(139, 92, 246, 0.15)',
289
+ color: s.location === 'workspace'
290
+ ? '#10b981'
291
+ : s.location === 'global'
292
+ ? '#3b82f6'
293
+ : '#8b5cf6',
294
+ fontSize: '0.7rem',
295
+ fontWeight: '600',
296
+ padding: '2px 6px',
297
+ borderRadius: '4px',
298
+ textTransform: 'capitalize'
299
+ }}>
300
+ {s.location === 'database' ? 'Taught' : s.location}
301
+ </span>
302
+ )}
174
303
  <span style={{ fontSize: '0.8rem', color: 'var(--text-muted)', textOverflow: 'ellipsis', overflow: 'hidden', whiteSpace: 'nowrap' }}>
175
304
  Source: {s.sourcePath}
176
305
  </span>
177
306
  </div>
178
- <div style={{
179
- fontSize: '0.85rem',
180
- color: 'var(--text-main)',
181
- whiteSpace: 'pre-wrap',
182
- maxHeight: '120px',
183
- overflowY: 'auto',
184
- background: 'rgba(0,0,0,0.12)',
185
- padding: '10px 14px',
186
- borderRadius: '8px',
187
- fontFamily: 'monospace',
188
- lineHeight: '1.4'
189
- }}>
190
- {s.content}
191
- </div>
192
307
  </div>
193
308
  <button
194
309
  className="btn btn-danger"
@@ -254,33 +369,98 @@ export default function PluginsTab({
254
369
  </div>
255
370
 
256
371
  <div className="mcp-list">
257
- {Object.entries(config.mcpServers || {}).map(([name, srv]: [string, any]) => (
258
- <div className="plugin-card" key={name} style={{
259
- display: 'flex',
260
- alignItems: 'center',
261
- justifyContent: 'space-between',
262
- padding: '16px 20px',
263
- border: '1px solid var(--border)',
264
- borderRadius: '12px',
265
- background: 'var(--surface-bg)',
266
- marginBottom: '10px'
267
- }}>
268
- <div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
269
- <div className="plugin-icon" style={{ fontSize: '1.5rem', width: '40px', height: '40px', display: 'grid', placeItems: 'center', background: 'var(--surface-strong)', borderRadius: '10px' }}>⚙</div>
270
- <div className="plugin-info">
271
- <div className="plugin-name" style={{ fontWeight: '600', color: 'var(--text-main)' }}>{name}</div>
272
- <div className="plugin-desc" style={{ fontFamily: 'monospace', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
273
- Command: {srv.command} {srv.args?.join(' ')}
372
+ {mcpListItems.length === 0 ? (
373
+ <div style={{ padding: '20px', textAlign: 'center', color: 'var(--text-muted)' }}>
374
+ No MCP servers configured or discovered.
375
+ </div>
376
+ ) : (
377
+ mcpListItems.map((item) => (
378
+ <div
379
+ className="plugin-card"
380
+ key={item.name}
381
+ style={{
382
+ display: 'flex',
383
+ alignItems: 'center',
384
+ justifyContent: 'space-between',
385
+ padding: '16px 20px',
386
+ border: '1px solid var(--border)',
387
+ borderRadius: '12px',
388
+ background: item.isConfigured ? 'var(--surface-bg)' : 'rgba(16, 185, 129, 0.03)',
389
+ opacity: item.isConfigured ? 1 : 0.65,
390
+ marginBottom: '10px',
391
+ transition: 'all 0.2s'
392
+ }}
393
+ >
394
+ <div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
395
+ <div className="plugin-icon" style={{ fontSize: '1.5rem', width: '40px', height: '40px', display: 'grid', placeItems: 'center', background: 'var(--surface-strong)', borderRadius: '10px' }}>
396
+ {item.icon}
397
+ </div>
398
+ <div className="plugin-info">
399
+ <div className="plugin-name" style={{ fontWeight: '600', color: 'var(--text-main)' }}>
400
+ {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>}
401
+ </div>
402
+ <div className="plugin-desc" style={{ fontFamily: 'monospace', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
403
+ {item.description}
404
+ </div>
274
405
  </div>
275
406
  </div>
407
+ <div className="plugin-actions" style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
408
+ {item.isConfigured && (
409
+ <button
410
+ className="btn-icon-danger"
411
+ onClick={() => handleRemoveMcpServer(item.name)}
412
+ style={{
413
+ background: 'transparent',
414
+ border: 'none',
415
+ color: '#ef4444',
416
+ cursor: 'pointer',
417
+ padding: '6px',
418
+ display: 'flex',
419
+ alignItems: 'center',
420
+ justifyContent: 'center',
421
+ borderRadius: '6px',
422
+ transition: 'background 0.2s'
423
+ }}
424
+ title="Delete MCP Server"
425
+ >
426
+ <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">
427
+ <polyline points="3 6 5 6 21 6"></polyline>
428
+ <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>
429
+ <line x1="10" y1="11" x2="10" y2="17"></line>
430
+ <line x1="14" y1="11" x2="14" y2="17"></line>
431
+ </svg>
432
+ </button>
433
+ )}
434
+ <label className="settings-toggle-switch">
435
+ <input
436
+ type="checkbox"
437
+ checked={item.isEnabled}
438
+ onChange={(e) => {
439
+ if (e.target.checked) {
440
+ handleEnableTool(item.name, item.command, item.args);
441
+ } else {
442
+ handleRemoveMcpServer(item.name);
443
+ }
444
+ }}
445
+ />
446
+ <span className="settings-toggle-slider" />
447
+ </label>
448
+ </div>
276
449
  </div>
277
- <div className="plugin-actions">
278
- <button className="btn btn-danger" onClick={() => handleRemoveMcpServer(name)} style={{ padding: '6px 12px', fontSize: '0.8rem', height: '34px' }}>Remove</button>
279
- </div>
280
- </div>
281
- ))}
450
+ ))
451
+ )}
282
452
  </div>
283
453
 
454
+ {/* Save settings alert */}
455
+ {((detectedTools.docker && !config.mcpServers?.docker) ||
456
+ (detectedTools.git && !config.mcpServers?.git) ||
457
+ (detectedTools.gh && !config.mcpServers?.github) ||
458
+ (detectedTools.node && !config.mcpServers?.node)) && (
459
+ <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' }}>
460
+ <span>⚠️ After toggling a plugin, please click the "Save Settings" button at the bottom of the window to persist changes.</span>
461
+ </div>
462
+ )}
463
+
284
464
  <div className="add-mcp-box" style={{
285
465
  marginTop: '20px',
286
466
  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')
@@ -725,3 +725,9 @@
725
725
  overflow-y: auto;
726
726
  }
727
727
  }
728
+
729
+ @media (max-width: 760px) {
730
+ .img-studio {
731
+ left: 0 !important;
732
+ }
733
+ }
@@ -916,7 +916,7 @@ h1 {
916
916
 
917
917
  .assistant-workspace.model-hidden .conversation-panel {
918
918
  flex: 1;
919
- max-width: 900px;
919
+ max-width: 1100px;
920
920
  margin: 0 auto;
921
921
  }
922
922
 
@@ -1486,7 +1486,7 @@ h1 {
1486
1486
  gap: 8px;
1487
1487
  margin-bottom: 8px;
1488
1488
  margin-top: 0;
1489
- max-width: min(680px, 100%);
1489
+ max-width: 100%;
1490
1490
  }
1491
1491
 
1492
1492
  .agent-activity-toggle {
@@ -1521,7 +1521,7 @@ h1 {
1521
1521
  border: 0;
1522
1522
  border-radius: 0;
1523
1523
  color: var(--text-main);
1524
- width: min(680px, 100%);
1524
+ width: 100%;
1525
1525
  max-width: 100%;
1526
1526
  padding: 4px 0;
1527
1527
  box-shadow: none;
@@ -3047,12 +3047,12 @@ input:checked + .slider:before {
3047
3047
  .assistant-workspace.model-hidden .conversation-panel {
3048
3048
  position: relative;
3049
3049
  display: grid;
3050
- grid-template-rows: minmax(0, 1fr) auto;
3051
- width: min(840px, calc(100% - 64px));
3050
+ grid-template-rows: auto minmax(0, 1fr) auto auto;
3051
+ width: min(1100px, calc(100% - 64px));
3052
3052
  height: 100%;
3053
3053
  max-width: none;
3054
3054
  margin: 0 auto;
3055
- padding: 56px 0 28px;
3055
+ padding: 32px 0 8px;
3056
3056
  grid-column: 1 / -1;
3057
3057
  z-index: 1;
3058
3058
  }
@@ -3075,13 +3075,13 @@ input:checked + .slider:before {
3075
3075
  }
3076
3076
 
3077
3077
  .assistant-workspace.model-hidden .conversation-panel.is-empty {
3078
- grid-template-rows: auto minmax(0, 1fr);
3079
- align-content: center;
3078
+ grid-template-rows: auto minmax(0, 1fr) auto auto;
3079
+ align-content: stretch;
3080
3080
  justify-content: stretch;
3081
3081
  width: min(840px, calc(100% - 64px));
3082
3082
  margin: 0 auto;
3083
- padding-top: 56px;
3084
- padding-bottom: 0;
3083
+ padding-top: 0;
3084
+ padding-bottom: 8px;
3085
3085
  }
3086
3086
 
3087
3087
  .conversation-panel.is-empty .chat-container {
@@ -3643,9 +3643,14 @@ input:checked + .slider:before {
3643
3643
 
3644
3644
  .assistant-workspace {
3645
3645
  display: flex;
3646
+ flex-direction: column;
3646
3647
  margin-left: 0;
3647
- padding: 12px;
3648
- gap: 12px;
3648
+ padding: 0;
3649
+ gap: 0;
3650
+ flex: 1;
3651
+ min-height: 0;
3652
+ height: 100%;
3653
+ overflow: hidden;
3649
3654
  }
3650
3655
 
3651
3656
  .assistant-workspace.layout-chat {
@@ -3654,6 +3659,10 @@ input:checked + .slider:before {
3654
3659
 
3655
3660
  .app-body {
3656
3661
  grid-template-columns: 1fr;
3662
+ display: flex;
3663
+ flex-direction: column;
3664
+ min-height: 0;
3665
+ flex: 1;
3657
3666
  }
3658
3667
 
3659
3668
  .sidebar-toggle {
@@ -3701,15 +3710,28 @@ input:checked + .slider:before {
3701
3710
  .conversation-panel {
3702
3711
  position: static;
3703
3712
  width: 100%;
3713
+ height: 100%;
3704
3714
  max-height: none;
3705
3715
  transform: none;
3706
3716
  flex: 1;
3707
3717
  min-width: 0;
3718
+ min-height: 0;
3719
+ border-radius: 0;
3720
+ overflow: hidden;
3708
3721
  }
3709
3722
 
3710
3723
  .chat-container {
3711
3724
  max-height: none;
3712
3725
  }
3726
+
3727
+ .input-area {
3728
+ padding: 10px;
3729
+ }
3730
+
3731
+ #chat-form {
3732
+ min-height: auto;
3733
+ padding: 6px;
3734
+ }
3713
3735
  }
3714
3736
 
3715
3737
  /* Chat Bold Highlight styling */
@@ -3726,6 +3748,19 @@ input:checked + .slider:before {
3726
3748
  color: #38bdf8; /* Sky-400 for midnight theme */
3727
3749
  }
3728
3750
 
3751
+ /* Input Disclaimer Footer */
3752
+ .input-disclaimer {
3753
+ text-align: center;
3754
+ font-size: 0.7rem;
3755
+ color: var(--text-muted);
3756
+ opacity: 0.55;
3757
+ padding: 6px 20px 10px;
3758
+ margin: 0;
3759
+ flex-shrink: 0;
3760
+ line-height: 1.5;
3761
+ user-select: none;
3762
+ }
3763
+
3729
3764
  /* Web-Native Chat Header */
3730
3765
  .chat-header {
3731
3766
  display: flex;
@@ -3839,6 +3874,51 @@ input:checked + .slider:before {
3839
3874
  .mobile-menu-btn {
3840
3875
  display: flex;
3841
3876
  }
3877
+
3878
+ /* Override desktop grid layout so chat-header (hamburger) is visible on mobile */
3879
+ .assistant-workspace.model-hidden .conversation-panel,
3880
+ .assistant-workspace.model-hidden .conversation-panel.is-empty {
3881
+ display: flex;
3882
+ flex-direction: column;
3883
+ width: 100%;
3884
+ height: 100%;
3885
+ padding: 0;
3886
+ margin: 0;
3887
+ grid-template-rows: none;
3888
+ grid-column: auto;
3889
+ }
3890
+
3891
+ /* Solid header fixed to top with background and blur */
3892
+ .chat-header {
3893
+ position: fixed;
3894
+ top: 0;
3895
+ left: 0;
3896
+ right: 0;
3897
+ z-index: 200;
3898
+ background: var(--chrome-bg);
3899
+ backdrop-filter: var(--glass-blur);
3900
+ -webkit-backdrop-filter: var(--glass-blur);
3901
+ border-bottom: 1px solid var(--border);
3902
+ height: 52px;
3903
+ padding: 10px 16px;
3904
+ }
3905
+
3906
+ .assistant-workspace.model-hidden .chat-header {
3907
+ display: flex;
3908
+ flex-shrink: 0;
3909
+ }
3910
+
3911
+ /* Push chat content below fixed header */
3912
+ .assistant-workspace.model-hidden .chat-container {
3913
+ flex: 1;
3914
+ overflow-y: auto;
3915
+ padding: 64px 12px 8px;
3916
+ }
3917
+
3918
+ .assistant-workspace.model-hidden .input-area {
3919
+ width: 100%;
3920
+ flex-shrink: 0;
3921
+ }
3842
3922
  }
3843
3923
 
3844
3924
  /* System Event Divider with Pill */
@@ -4079,3 +4159,11 @@ input:checked + .slider:before {
4079
4159
  font-size: 0.9rem;
4080
4160
  }
4081
4161
 
4162
+ /* Allow global text selection everywhere */
4163
+ * {
4164
+ user-select: text !important;
4165
+ -webkit-user-select: text !important;
4166
+ -moz-user-select: text !important;
4167
+ -ms-user-select: text !important;
4168
+ }
4169
+
@@ -379,7 +379,6 @@ button {
379
379
  }
380
380
  }
381
381
 
382
- /* Premium Markdown Styles for Chat Messages */
383
382
  .chat-inline-code {
384
383
  background: rgba(16, 185, 129, 0.15);
385
384
  border: 1px solid rgba(16, 185, 129, 0.25);
@@ -390,6 +389,18 @@ button {
390
389
  font-size: 0.88em;
391
390
  }
392
391
 
392
+ .chat-mention {
393
+ background: color-mix(in srgb, var(--accent, #10b981) 12%, transparent);
394
+ border: 1px solid color-mix(in srgb, var(--accent, #10b981) 25%, transparent);
395
+ color: var(--accent, #34d399);
396
+ padding: 2px 6px;
397
+ border-radius: 6px;
398
+ font-weight: 500;
399
+ font-size: 0.9em;
400
+ display: inline-block;
401
+ margin: 0 2px;
402
+ }
403
+
393
404
  .chat-code-block-container {
394
405
  display: flex;
395
406
  flex-direction: column;
@@ -134,6 +134,28 @@ export async function getRuntimeStatus(): Promise<RuntimeStatus> {
134
134
  return invoke<RuntimeStatus>('get_runtime_status')
135
135
  }
136
136
 
137
+ export interface DetectedTools {
138
+ docker: boolean
139
+ git: boolean
140
+ gh: boolean
141
+ node: boolean
142
+ }
143
+
144
+ export async function detectSystemTools(): Promise<DetectedTools> {
145
+ if (typeof window === 'undefined' || !isTauriRuntime()) {
146
+ const API_BASE = getApiBase();
147
+ try {
148
+ const res = await fetch(`${API_BASE}/detect-tools`);
149
+ return await res.json();
150
+ } catch (e) {
151
+ console.error("Failed to detect tools from local server:", e);
152
+ return { docker: false, git: false, gh: false, node: false };
153
+ }
154
+ }
155
+ const { invoke } = await import('@tauri-apps/api/core')
156
+ return invoke<DetectedTools>('detect_system_tools')
157
+ }
158
+
137
159
  export async function sendChatMessage(
138
160
  message: string,
139
161
  imageDataUri?: string | null,
@@ -577,12 +599,12 @@ export async function setDefaultImageProvider(provider: string): Promise<boolean
577
599
 
578
600
 
579
601
 
580
- export async function submitToolApproval(token: string, approved: boolean): Promise<void> {
602
+ export async function submitToolApproval(token: string, approved: boolean, answer?: string): Promise<void> {
581
603
  if (typeof window === 'undefined' || !isTauriRuntime()) {
582
604
  return;
583
605
  }
584
606
  const { invoke } = await import('@tauri-apps/api/core')
585
- return invoke('submit_tool_approval', { token, approved })
607
+ return invoke('submit_tool_approval', { token, approved, answer })
586
608
  }
587
609
 
588
610
  export async function proposeCodeEdits(root: string, edits: CodeEdit[]): Promise<CodeEditProposal> {