@pheem49/mint 1.9.0 → 1.10.0

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 (90) hide show
  1. package/Cargo.lock +55 -3
  2. package/Cargo.toml +2 -1
  3. package/Release_Note.md +285 -140
  4. package/bin/mint +0 -0
  5. package/crates/mint-cli/Cargo.toml +1 -0
  6. package/crates/mint-cli/src/actions.rs +233 -0
  7. package/crates/mint-cli/src/agent.rs +287 -43
  8. package/crates/mint-cli/src/gmail.rs +22 -2
  9. package/crates/mint-cli/src/interactive.rs +2888 -0
  10. package/crates/mint-cli/src/main.rs +336 -2854
  11. package/crates/mint-cli/src/markdown.rs +294 -0
  12. package/crates/mint-cli/src/onboard.rs +44 -1
  13. package/crates/mint-cli/src/updater.rs +48 -6
  14. package/crates/mint-core/Cargo.toml +1 -0
  15. package/crates/mint-core/src/agent_loop.rs +1 -0
  16. package/crates/mint-core/src/api_server.rs +376 -13
  17. package/crates/mint-core/src/browser.rs +629 -101
  18. package/crates/mint-core/src/channels.rs +105 -6
  19. package/crates/mint-core/src/chat.rs +52 -4
  20. package/crates/mint-core/src/code_tools.rs +29 -11
  21. package/crates/mint-core/src/config.rs +118 -0
  22. package/crates/mint-core/src/image_gen.rs +294 -33
  23. package/crates/mint-core/src/lib.rs +5 -2
  24. package/crates/mint-core/src/mcp.rs +119 -6
  25. package/crates/mint-core/src/orchestration.rs +133 -11
  26. package/crates/mint-core/src/pictures.rs +64 -1
  27. package/crates/mint-core/src/skills.rs +82 -6
  28. package/crates/mint-core/src/video_gen.rs +235 -0
  29. package/package.json +8 -2
  30. package/shared-modules/luajit/luajit.json +2 -2
  31. package/skills-lock.json +41 -0
  32. package/src/renderer/index-web.html +1 -1
  33. package/src/renderer/index.html +1 -1
  34. package/src/renderer/shared/constants/slashCommands.ts +27 -0
  35. package/src/renderer/shared/platform.ts +3 -0
  36. package/src/renderer/shared/types.ts +4 -0
  37. package/src/renderer/shared/utils/approval.tsx +86 -7
  38. package/src/renderer/shared/utils/markdown.tsx +217 -88
  39. package/src/renderer/shared/utils/slashCommandProcessor.ts +239 -0
  40. package/src/renderer/src/components/ChatPanel.tsx +461 -64
  41. package/src/renderer/src/components/DashboardSidebar.tsx +11 -2
  42. package/src/renderer/src/components/ImageStudioPanel.tsx +87 -4
  43. package/src/renderer/src/components/MintDashboard.tsx +150 -5
  44. package/src/renderer/src/components/PicturesLibrary.tsx +156 -15
  45. package/src/renderer/src/components/Settings/AutomationTab.tsx +316 -18
  46. package/src/renderer/src/components/Settings/GeneralTab.tsx +762 -634
  47. package/src/renderer/src/components/Settings/PluginsTab.tsx +304 -70
  48. package/src/renderer/src/components/Settings/ThemeTab.tsx +6 -1
  49. package/src/renderer/src/components/SettingsWindow.tsx +38 -2
  50. package/src/renderer/src/components/VeoStudioPanel.tsx +549 -0
  51. package/src/renderer/src/css/image-studio.css +106 -0
  52. package/src/renderer/src/css/settings.css +158 -0
  53. package/src/renderer/src/css/styles.css +352 -1
  54. package/src/renderer/src/css/veo-studio.css +626 -0
  55. package/src/renderer/src/index.css +1 -0
  56. package/src/renderer/src/tauri.ts +144 -17
  57. package/src/renderer/src-web/components/ChatPanel.tsx +360 -88
  58. package/src/renderer/src-web/components/DashboardSidebar.tsx +16 -1
  59. package/src/renderer/src-web/components/ImageStudioPanel.tsx +87 -4
  60. package/src/renderer/src-web/components/MintDashboard.tsx +137 -4
  61. package/src/renderer/src-web/components/PicturesLibrary.tsx +155 -15
  62. package/src/renderer/src-web/components/Settings/AutomationTab.tsx +316 -18
  63. package/src/renderer/src-web/components/Settings/GeneralTab.tsx +762 -629
  64. package/src/renderer/src-web/components/Settings/PluginsTab.tsx +304 -70
  65. package/src/renderer/src-web/components/Settings/ThemeTab.tsx +6 -1
  66. package/src/renderer/src-web/components/SettingsWindow.tsx +35 -2
  67. package/src/renderer/src-web/components/VeoStudioPanel.tsx +549 -0
  68. package/src/renderer/src-web/css/image-studio.css +106 -0
  69. package/src/renderer/src-web/css/settings.css +158 -0
  70. package/src/renderer/src-web/css/styles.css +353 -1
  71. package/src/renderer/src-web/css/veo-studio.css +650 -0
  72. package/src/renderer/src-web/index.css +1 -0
  73. package/src/renderer/src-web/tauri.ts +135 -16
  74. package/src-tauri/src/lib.rs +114 -8
  75. package/src-tauri/src/webhooks.rs +45 -31
  76. package/src-tauri/src/workflows.rs +1 -0
  77. package/src-tauri/tauri.conf.json +1 -1
  78. package/temp_gridgeist/AGENTS.md +8 -0
  79. package/temp_gridgeist/CHANGELOG.md +130 -0
  80. package/temp_gridgeist/LICENSE +21 -0
  81. package/temp_gridgeist/README.md +323 -0
  82. package/temp_gridgeist/README.th.md +325 -0
  83. package/temp_gridgeist/skills/gridgeist/SKILL.md +88 -0
  84. package/temp_gridgeist/skills/gridgeist/agents/openai.yaml +9 -0
  85. package/temp_gridgeist/skills/gridgeist/assets/gridgeist-small.svg +10 -0
  86. package/temp_gridgeist/skills/gridgeist/assets/gridgeist.png +0 -0
  87. package/temp_gridgeist/skills/gridgeist/assets/gridgeist.svg +18 -0
  88. package/temp_gridgeist/skills/gridgeist/references/design-language.md +88 -0
  89. package/temp_gridgeist/skills/gridgeist/references/review-checklist.md +128 -0
  90. package/temp_gridgeist/skills/gridgeist/references/system-contract.md +109 -0
@@ -13,11 +13,126 @@ interface PluginsTabProps {
13
13
  setMcpArgs: (val: string) => void
14
14
  mcpEnv: string
15
15
  setMcpEnv: (val: string) => void
16
+ mcpIcon?: string
17
+ setMcpIcon?: (val: string) => void
16
18
  handleAddMcpServer: () => void
17
19
  handleRemoveMcpServer: (name: string) => void
18
20
  handleConnectPlugin: (plugin: string) => void
19
21
  }
20
22
 
23
+ function renderMcpSvgIcon(name: string, customSvgOrUrl?: string) {
24
+ if (customSvgOrUrl && customSvgOrUrl.trim()) {
25
+ const trimmed = customSvgOrUrl.trim()
26
+ if (trimmed.startsWith('<svg') || trimmed.includes('<svg')) {
27
+ return (
28
+ <span
29
+ style={{ width: '22px', height: '22px', display: 'flex', alignItems: 'center', justifyContent: 'center' }}
30
+ dangerouslySetInnerHTML={{ __html: trimmed }}
31
+ />
32
+ )
33
+ }
34
+ if (trimmed.startsWith('data:image') || trimmed.startsWith('http://') || trimmed.startsWith('https://')) {
35
+ return <img src={trimmed} alt={name} style={{ width: '22px', height: '22px', objectFit: 'contain' }} />
36
+ }
37
+ const cleanCustom = trimmed.toLowerCase()
38
+ if (cleanCustom === 'search') {
39
+ return (
40
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#3b82f6" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
41
+ <circle cx="11" cy="11" r="8" />
42
+ <line x1="21" y1="21" x2="16.65" y2="16.65" />
43
+ </svg>
44
+ )
45
+ }
46
+ if (cleanCustom === 'database') {
47
+ return (
48
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#a855f7" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
49
+ <ellipse cx="12" cy="5" rx="9" ry="3"/>
50
+ <path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"/>
51
+ <path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/>
52
+ </svg>
53
+ )
54
+ }
55
+ if (cleanCustom === 'cloud') {
56
+ return (
57
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#38bdf8" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
58
+ <path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"/>
59
+ </svg>
60
+ )
61
+ }
62
+ if (cleanCustom === 'code' || cleanCustom === 'terminal') {
63
+ return (
64
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#10b981" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
65
+ <polyline points="16 18 22 12 16 6" />
66
+ <polyline points="8 6 2 12 8 18" />
67
+ </svg>
68
+ )
69
+ }
70
+ if (cleanCustom === 'api' || cleanCustom === 'bolt') {
71
+ return (
72
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#f59e0b" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
73
+ <polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>
74
+ </svg>
75
+ )
76
+ }
77
+ }
78
+
79
+ const cleanName = name.toLowerCase().trim()
80
+ if (cleanName === 'docker') {
81
+ return (
82
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="#0db7ed">
83
+ <path d="M13 8.5h2v2h-2zm-3 0h2v2h-2zm-3 0h2v2H7zm-3 3h2v2H4zm3 0h2v2H7zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-12 3h2v2H4zm3 0h2v2H7zm3 0h2v2h-2zm3 0h2v2h-2zm3 0h2v2h-2zm-6.2 3.8c-.8.5-2.1.8-3.4.6-2.5-.3-4.5-2.2-4.8-4.7h-1c.4 3.4 3.1 6 6.5 6 3 0 5.6-2 6.4-4.8.6-.2 1.4-.4 2.2-.2.3.1.6.3.8.5.6.6 1.4.9 2.2.9h.4c.5-.7.8-1.5.8-2.4 0-.3 0-.6-.1-.9-.7.1-1.4 0-2.1-.3-.6-.3-1.1-.8-1.5-1.4l-.2-.3h-2.1c-.2.7-.6 1.3-1.2 1.7-.6.4-1.3.6-2 .4z"/>
84
+ </svg>
85
+ )
86
+ }
87
+ if (cleanName === 'git' || cleanName === 'gitkraken') {
88
+ return (
89
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#f05032" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
90
+ <circle cx="18" cy="18" r="3" />
91
+ <circle cx="6" cy="6" r="3" />
92
+ <circle cx="6" cy="18" r="3" />
93
+ <path d="M6 9v6" />
94
+ <path d="M9 18h6" />
95
+ </svg>
96
+ )
97
+ }
98
+ if (cleanName === 'github') {
99
+ return (
100
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="#f0f6fc">
101
+ <path fillRule="evenodd" clipRule="evenodd" d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.53 1.032 1.53 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"/>
102
+ </svg>
103
+ )
104
+ }
105
+ if (cleanName === 'node' || cleanName === 'nodejs') {
106
+ return (
107
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="#68a063">
108
+ <path d="M12 2L2 7.5v9L12 22l10-5.5v-9L12 2zm0 2.3l7.7 4.2v7L12 19.7 4.3 15.5v-7L12 4.3zm-1 4.2v3.5l-3-1.7V8.5l3 1.7zm2 0l3-1.7v1.8l-3 1.7V8.5z"/>
109
+ </svg>
110
+ )
111
+ }
112
+ if (cleanName === 'spotify') {
113
+ return (
114
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="#1ed760">
115
+ <path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm4.586 14.424c-.18.295-.563.387-.857.207-2.35-1.434-5.308-1.758-8.793-.963-.335.077-.67-.133-.746-.467-.077-.334.132-.67.467-.746 3.812-.871 7.102-.494 9.722 1.112.294.18.386.563.207.857zm1.233-2.743c-.226.367-.706.482-1.073.257-2.687-1.652-6.785-2.131-9.965-1.166-.413.126-.847-.106-.973-.519-.125-.413.106-.847.519-.973 3.632-1.102 8.147-.568 11.235 1.328.367.226.482.706.257 1.073zm.105-2.835C14.692 8.95 9.375 8.775 6.297 9.71c-.496.15-1.022-.132-1.173-.628-.15-.496.132-1.022.628-1.173 3.535-1.073 9.404-.871 12.984 1.254.446.265.592.844.327 1.29-.265.446-.844.592-1.29.327z"/>
116
+ </svg>
117
+ )
118
+ }
119
+ if (cleanName === 'discord') {
120
+ return (
121
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="#5865f2">
122
+ <path d="M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994.021-.041.001-.09-.041-.106a13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.061 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.893.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.028zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418z"/>
123
+ </svg>
124
+ )
125
+ }
126
+ return (
127
+ <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#10b981" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
128
+ <rect x="2" y="2" width="20" height="8" rx="2" ry="2"/>
129
+ <rect x="2" y="14" width="20" height="8" rx="2" ry="2"/>
130
+ <line x1="6" y1="6" x2="6.01" y2="6"/>
131
+ <line x1="6" y1="18" x2="6.01" y2="18"/>
132
+ </svg>
133
+ )
134
+ }
135
+
21
136
  export default function PluginsTab({
22
137
  config,
23
138
  updateField,
@@ -29,6 +144,8 @@ export default function PluginsTab({
29
144
  setMcpArgs,
30
145
  mcpEnv,
31
146
  setMcpEnv,
147
+ mcpIcon = '',
148
+ setMcpIcon,
32
149
  handleAddMcpServer,
33
150
  handleRemoveMcpServer,
34
151
  handleConnectPlugin
@@ -123,6 +240,20 @@ export default function PluginsTab({
123
240
  }
124
241
  }
125
242
 
243
+ const [expandedMcp, setExpandedMcp] = useState<string | null>(null)
244
+
245
+ const handleUpdateMcpServerField = (serverName: string, field: string, value: any) => {
246
+ const currentMcp = config.mcpServers?.[serverName] || { command: 'npx', args: [], env: {} }
247
+ const updatedMcp = {
248
+ ...(config.mcpServers || {}),
249
+ [serverName]: {
250
+ ...currentMcp,
251
+ [field]: value
252
+ }
253
+ }
254
+ updateField('mcpServers', updatedMcp)
255
+ }
256
+
126
257
  // Local state to toggle expansion of configuration sections
127
258
  const [expandedPlugin, setExpandedPlugin] = useState<string | null>(null)
128
259
 
@@ -162,6 +293,7 @@ export default function PluginsTab({
162
293
  command: string
163
294
  args: string[]
164
295
  icon: string
296
+ customIcon?: string
165
297
  isEnabled: boolean
166
298
  isConfigured: boolean
167
299
  description: string
@@ -179,6 +311,7 @@ export default function PluginsTab({
179
311
  command: srv.command,
180
312
  args: srv.args || [],
181
313
  icon,
314
+ customIcon: srv.icon,
182
315
  isEnabled: true,
183
316
  isConfigured: true,
184
317
  description: `Command: ${srv.command} ${(srv.args || []).join(' ')}`
@@ -375,80 +508,171 @@ export default function PluginsTab({
375
508
  No MCP servers configured or discovered.
376
509
  </div>
377
510
  ) : (
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>}
511
+ mcpListItems.map((item) => {
512
+ const isExpanded = expandedMcp === item.name;
513
+ const srvConfig = config.mcpServers?.[item.name] || { command: item.command, args: item.args, env: {}, icon: item.customIcon };
514
+
515
+ return (
516
+ <div
517
+ className={`plugin-card-wrapper ${isExpanded ? 'active-plugin-card' : ''}`}
518
+ key={item.name}
519
+ style={{
520
+ border: '1px solid var(--border)',
521
+ borderRadius: '12px',
522
+ background: item.isConfigured ? 'var(--surface-bg)' : 'rgba(16, 185, 129, 0.03)',
523
+ opacity: item.isConfigured ? 1 : 0.65,
524
+ marginBottom: '10px',
525
+ transition: 'all 0.2s ease',
526
+ overflow: 'hidden'
527
+ }}
528
+ >
529
+ <div className="plugin-card" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '16px 20px' }}>
530
+ <div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
531
+ <div className="plugin-icon" style={{ fontSize: '1.5rem', width: '40px', height: '40px', display: 'grid', placeItems: 'center', background: 'var(--surface-strong)', borderRadius: '10px' }}>
532
+ {renderMcpSvgIcon(item.name, item.customIcon)}
533
+ </div>
534
+ <div className="plugin-info">
535
+ <div className="plugin-name" style={{ fontWeight: '600', color: 'var(--text-main)' }}>
536
+ {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>}
537
+ </div>
538
+ <div className="plugin-desc" style={{ fontFamily: 'monospace', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
539
+ {item.description}
540
+ </div>
541
+ </div>
402
542
  </div>
403
- <div className="plugin-desc" style={{ fontFamily: 'monospace', fontSize: '0.8rem', color: 'var(--text-muted)' }}>
404
- {item.description}
543
+ <div className="plugin-actions" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
544
+ {item.isConfigured && (
545
+ <>
546
+ <button
547
+ className="btn-icon-edit"
548
+ onClick={() => setExpandedMcp(isExpanded ? null : item.name)}
549
+ style={{
550
+ background: isExpanded ? 'var(--accent)' : 'transparent',
551
+ border: 'none',
552
+ color: isExpanded ? '#fff' : 'var(--text-soft)',
553
+ cursor: 'pointer',
554
+ padding: '6px',
555
+ display: 'flex',
556
+ alignItems: 'center',
557
+ justifyContent: 'center',
558
+ borderRadius: '6px',
559
+ transition: 'all 0.2s'
560
+ }}
561
+ title="Edit MCP Server Settings"
562
+ >
563
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
564
+ <path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path>
565
+ <path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path>
566
+ </svg>
567
+ </button>
568
+ <button
569
+ className="btn-icon-danger"
570
+ onClick={() => handleRemoveMcpServer(item.name)}
571
+ style={{
572
+ background: 'transparent',
573
+ border: 'none',
574
+ color: '#ef4444',
575
+ cursor: 'pointer',
576
+ padding: '6px',
577
+ display: 'flex',
578
+ alignItems: 'center',
579
+ justifyContent: 'center',
580
+ borderRadius: '6px',
581
+ transition: 'background 0.2s'
582
+ }}
583
+ title="Delete MCP Server"
584
+ >
585
+ <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">
586
+ <polyline points="3 6 5 6 21 6"></polyline>
587
+ <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>
588
+ <line x1="10" y1="11" x2="10" y2="17"></line>
589
+ <line x1="14" y1="11" x2="14" y2="17"></line>
590
+ </svg>
591
+ </button>
592
+ </>
593
+ )}
594
+ <label className="settings-toggle-switch">
595
+ <input
596
+ type="checkbox"
597
+ checked={item.isEnabled}
598
+ onChange={(e) => {
599
+ if (e.target.checked) {
600
+ handleEnableTool(item.name, item.command, item.args);
601
+ } else {
602
+ handleRemoveMcpServer(item.name);
603
+ }
604
+ }}
605
+ />
606
+ <span className="settings-toggle-slider" />
607
+ </label>
405
608
  </div>
406
609
  </div>
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>
610
+
611
+ {item.isConfigured && isExpanded && (
612
+ <div className="plugin-config-panel" style={{
613
+ padding: '16px 20px',
614
+ borderTop: '1px solid var(--border)',
615
+ background: 'rgba(0, 0, 0, 0.18)',
616
+ display: 'flex',
617
+ flexDirection: 'column',
618
+ gap: '12px'
619
+ }}>
620
+ <h4 style={{ fontSize: '0.85rem', fontWeight: '600', color: 'var(--accent)', textTransform: 'uppercase', letterSpacing: '0.5px' }}>
621
+ Edit Settings for {item.name}
622
+ </h4>
623
+
624
+ <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
625
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
626
+ <label style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>Command</label>
627
+ <input
628
+ type="text"
629
+ value={srvConfig.command || ''}
630
+ onChange={(e) => handleUpdateMcpServerField(item.name, 'command', e.target.value)}
631
+ style={{ padding: '8px 12px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--input-bg)', color: 'var(--text-main)', fontSize: '0.85rem' }}
632
+ />
633
+ </div>
634
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
635
+ <label style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>Icon (Optional)</label>
636
+ <input
637
+ type="text"
638
+ placeholder="SVG code, URL, or preset: search/database/cloud/code/api"
639
+ value={srvConfig.icon || ''}
640
+ onChange={(e) => handleUpdateMcpServerField(item.name, 'icon', e.target.value)}
641
+ style={{ padding: '8px 12px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--input-bg)', color: 'var(--text-main)', fontSize: '0.85rem' }}
642
+ />
643
+ </div>
644
+ </div>
645
+
646
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
647
+ <label style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>Arguments (Space separated)</label>
648
+ <input
649
+ type="text"
650
+ value={(srvConfig.args || []).join(' ')}
651
+ onChange={(e) => handleUpdateMcpServerField(item.name, 'args', e.target.value.split(/\s+/).filter(Boolean))}
652
+ style={{ padding: '8px 12px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--input-bg)', color: 'var(--text-main)', fontSize: '0.85rem', width: '100%' }}
653
+ />
654
+ </div>
655
+
656
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '6px' }}>
657
+ <label style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>Environment Variables (JSON format)</label>
658
+ <textarea
659
+ value={typeof srvConfig.env === 'object' ? JSON.stringify(srvConfig.env, null, 2) : (srvConfig.env || '')}
660
+ onChange={(e) => {
661
+ try {
662
+ const parsed = JSON.parse(e.target.value)
663
+ handleUpdateMcpServerField(item.name, 'env', parsed)
664
+ } catch {
665
+ // allow live typing
666
+ }
667
+ }}
668
+ style={{ padding: '8px 12px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--input-bg)', color: 'var(--text-main)', fontSize: '0.85rem', fontFamily: 'monospace', height: '65px', resize: 'vertical' }}
669
+ />
670
+ </div>
671
+ </div>
434
672
  )}
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
673
  </div>
450
- </div>
451
- ))
674
+ )
675
+ })
452
676
  )}
453
677
  </div>
454
678
 
@@ -502,6 +726,16 @@ export default function PluginsTab({
502
726
  style={{ padding: '10px 14px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--input-bg)', color: 'var(--text-main)', fontSize: '0.9rem', width: '100%' }}
503
727
  />
504
728
  </div>
729
+ <div style={{ display: 'flex', flexDirection: 'column', gap: '6px', marginBottom: '12px' }}>
730
+ <label style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>Icon (Optional)</label>
731
+ <input
732
+ type="text"
733
+ placeholder="SVG code, URL, or preset: search/database/cloud/code/api"
734
+ value={mcpIcon}
735
+ onChange={(e) => setMcpIcon && setMcpIcon(e.target.value)}
736
+ style={{ padding: '10px 14px', borderRadius: '8px', border: '1px solid var(--border)', background: 'var(--input-bg)', color: 'var(--text-main)', fontSize: '0.9rem', width: '100%' }}
737
+ />
738
+ </div>
505
739
  <div style={{ display: 'flex', flexDirection: 'column', gap: '6px', marginBottom: '14px' }}>
506
740
  <label style={{ fontSize: '0.8rem', color: 'var(--text-muted)' }}>Environment Variables (JSON)</label>
507
741
  <textarea
@@ -546,7 +780,7 @@ export default function PluginsTab({
546
780
  }}>
547
781
  <div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
548
782
  <div className="plugin-icon" style={{ fontSize: '1.5rem', width: '40px', height: '40px', display: 'grid', placeItems: 'center', background: 'var(--surface-strong)', borderRadius: '10px' }}>
549
- {p.icon}
783
+ {renderMcpSvgIcon(p.key)}
550
784
  </div>
551
785
  <div className="plugin-info">
552
786
  <div className="plugin-name" style={{ fontWeight: '600', color: 'var(--text-main)' }}>{p.name}</div>
@@ -107,9 +107,14 @@ export default function ThemeTab({ config, updateField }: ThemeTabProps) {
107
107
  <label>Font Family</label>
108
108
  <select value={config.fontFamily} onChange={(e) => updateField('fontFamily', e.target.value)}>
109
109
  <option value="'Outfit', sans-serif">Outfit (Default)</option>
110
- <option value="'Mali', cursive">Mali (Cute Thai Font)</option>
111
110
  <option value="'Prompt', sans-serif">Prompt (Modern Thai)</option>
112
111
  <option value="'Sarabun', sans-serif">Sarabun (Formal Thai)</option>
112
+ <option value="'Kanit', sans-serif">Kanit (Trendy Thai)</option>
113
+ <option value="'Mitr', sans-serif">Mitr (Friendly Thai)</option>
114
+ <option value="'Noto Sans Thai', sans-serif">Noto Sans Thai (Clean Thai)</option>
115
+ <option value="'Mali', cursive">Mali (Cute Thai Font)</option>
116
+ <option value="'Inter', sans-serif">Inter (Clean Sans-Serif)</option>
117
+ <option value="'Fira Code', monospace">Fira Code (Developer Code Font)</option>
113
118
  </select>
114
119
  </div>
115
120
  <div className="setting-row">
@@ -1,5 +1,5 @@
1
1
  import React, { useState, useEffect } from 'react'
2
- import { getProfileValue, setProfileValue } from '../tauri'
2
+ import { getProfileValue, setProfileValue, setActiveModel } from '../tauri'
3
3
  import GeneralTab from './Settings/GeneralTab'
4
4
  import MemoryTab from './Settings/MemoryTab'
5
5
  import AudioTab from './Settings/AudioTab'
@@ -91,11 +91,17 @@ export const DEFAULT_CONFIG = {
91
91
  lineChannelAccessToken: '',
92
92
  lineChannelSecret: '',
93
93
  enableLineBridge: false,
94
+ lineWebhookHost: '127.0.0.1',
95
+ lineWebhookPort: 3000,
94
96
  whatsappCloudAccessToken: '',
95
97
  whatsappPhoneNumberId: '',
96
98
  whatsappVerifyToken: '',
97
99
  whatsappAppSecret: '',
98
100
  enableWhatsappBridge: false,
101
+ whatsappWebhookHost: '127.0.0.1',
102
+ whatsappWebhookPort: 3001,
103
+ enableBridgeAckNotification: true,
104
+ bridgeAckMessage: '[Mint Agent] Remote command received, processing...',
99
105
  notionApiKey: '',
100
106
  notionDatabaseId: '',
101
107
  gmailClientId: '',
@@ -114,6 +120,9 @@ export const DEFAULT_CONFIG = {
114
120
  stabilityApiKey: '',
115
121
  ideogramApiKey: '',
116
122
  replicateApiKey: '',
123
+ // Video Generation
124
+ videoGenProvider: 'veo' as 'veo',
125
+ veoModel: 'veo-2.0-generate-001',
117
126
  // Multi-Agent Configuration
118
127
  agents: [
119
128
  {
@@ -239,6 +248,7 @@ export default function SettingsWindow() {
239
248
  const [mcpCmd, setMcpCmd] = useState('')
240
249
  const [mcpArgs, setMcpArgs] = useState('')
241
250
  const [mcpEnv, setMcpEnv] = useState('')
251
+ const [mcpIcon, setMcpIcon] = useState('')
242
252
  const [updateMessage, setUpdateMessage] = useState('')
243
253
  const [updateAvailable, setUpdateAvailable] = useState(false)
244
254
 
@@ -422,6 +432,28 @@ export default function SettingsWindow() {
422
432
  if (window.settingsApi) {
423
433
  await window.settingsApi.saveSettings(finalConfig)
424
434
  applyThemeStyles(finalConfig)
435
+ try {
436
+ const getActiveModelName = (provider: string, cfg: typeof finalConfig) => {
437
+ switch (provider) {
438
+ case 'gemini': return cfg.geminiModel
439
+ case 'openai': return cfg.openaiModel
440
+ case 'openrouter': return cfg.openrouterModel
441
+ case 'deepseek': return cfg.deepseekModel
442
+ case 'anthropic': return cfg.anthropicModel
443
+ case 'huggingface': return cfg.hfModel
444
+ case 'local_openai': return cfg.localModelName
445
+ case 'ollama': return cfg.ollamaModel
446
+ default: return ''
447
+ }
448
+ }
449
+ const oldModel = getActiveModelName(config.aiProvider, config)
450
+ const newModel = getActiveModelName(finalConfig.aiProvider, finalConfig)
451
+ if (config.aiProvider !== finalConfig.aiProvider || oldModel !== newModel) {
452
+ await setActiveModel(finalConfig.aiProvider, newModel)
453
+ }
454
+ } catch (e) {
455
+ console.warn('Failed to sync active model event:', e)
456
+ }
425
457
  window.settingsApi.closeSettings()
426
458
  }
427
459
  }
@@ -532,7 +564,8 @@ export default function SettingsWindow() {
532
564
  [mcpName.trim()]: {
533
565
  command: mcpCmd.trim(),
534
566
  args: argList,
535
- env: parsedEnv
567
+ env: parsedEnv,
568
+ icon: mcpIcon.trim() || undefined
536
569
  }
537
570
  }
538
571
 
@@ -545,6 +578,7 @@ export default function SettingsWindow() {
545
578
  setMcpCmd('')
546
579
  setMcpArgs('')
547
580
  setMcpEnv('')
581
+ setMcpIcon('')
548
582
  }
549
583
 
550
584
  const handleRemoveMcpServer = (name: string) => {
@@ -770,6 +804,8 @@ export default function SettingsWindow() {
770
804
  setMcpArgs={setMcpArgs}
771
805
  mcpEnv={mcpEnv}
772
806
  setMcpEnv={setMcpEnv}
807
+ mcpIcon={mcpIcon}
808
+ setMcpIcon={setMcpIcon}
773
809
  handleAddMcpServer={handleAddMcpServer}
774
810
  handleRemoveMcpServer={handleRemoveMcpServer}
775
811
  handleConnectPlugin={handleConnectPlugin}