@pheem49/mint 1.6.3 → 1.7.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 (63) hide show
  1. package/Cargo.lock +3 -3
  2. package/Cargo.toml +1 -1
  3. package/README.md +82 -21
  4. package/assets/Mint_CLI.png +0 -0
  5. package/assets/Mint_Desktop.png +0 -0
  6. package/assets/Mint_Web.png +0 -0
  7. package/assets/Mint_WorkSpace.png +0 -0
  8. package/assets/Setting.png +0 -0
  9. package/assets/bridges.svg +4 -0
  10. package/assets/chat.svg +3 -0
  11. package/assets/code.svg +4 -0
  12. package/assets/features.svg +3 -0
  13. package/assets/imagegen.svg +5 -0
  14. package/assets/live2d.svg +6 -0
  15. package/assets/memory.svg +5 -0
  16. package/assets/screencapture.svg +4 -0
  17. package/assets/setup.svg +3 -0
  18. package/assets/tools.svg +4 -0
  19. package/bin/mint +0 -0
  20. package/crates/mint-cli/src/agent.rs +47 -15
  21. package/crates/mint-cli/src/main.rs +534 -7
  22. package/crates/mint-cli/src/onboard.rs +134 -1
  23. package/crates/mint-cli/src/setup.rs +173 -32
  24. package/crates/mint-core/src/api_server.rs +147 -3
  25. package/crates/mint-core/src/config.rs +22 -3
  26. package/crates/mint-core/src/image_gen.rs +895 -0
  27. package/crates/mint-core/src/lib.rs +4 -0
  28. package/crates/mint-core/src/memory.rs +27 -1
  29. package/crates/mint-core/src/orchestration.rs +1 -1
  30. package/crates/mint-core/src/plugins.rs +32 -0
  31. package/crates/mint-core/tests/memory_persistence.rs +28 -0
  32. package/package.json +1 -1
  33. package/src/bin/index.js +0 -0
  34. package/src/renderer/src/agentProgress.ts +39 -0
  35. package/src/renderer/src/components/ChatPanel.tsx +547 -47
  36. package/src/renderer/src/components/DashboardSidebar.tsx +12 -4
  37. package/src/renderer/src/components/ImageStudioPanel.tsx +638 -0
  38. package/src/renderer/src/components/MintDashboard.tsx +84 -31
  39. package/src/renderer/src/components/ModelPanel.tsx +2 -2
  40. package/src/renderer/src/components/PicturesLibrary.tsx +5 -1
  41. package/src/renderer/src/components/SettingsWindow.tsx +29 -20
  42. package/src/renderer/src/components/SpotlightWindow.tsx +8 -8
  43. package/src/renderer/src/components/WorkspacePanel.tsx +1 -1
  44. package/src/renderer/src/css/image-studio.css +722 -0
  45. package/src/renderer/src/css/settings.css +13 -3
  46. package/src/renderer/src/css/styles.css +245 -43
  47. package/src/renderer/src/index.css +152 -0
  48. package/src/renderer/src/tauri.ts +148 -0
  49. package/src/renderer/src-web/App.tsx +16 -1
  50. package/src/renderer/src-web/agentProgress.ts +39 -0
  51. package/src/renderer/src-web/components/ChatPanel.tsx +568 -46
  52. package/src/renderer/src-web/components/DashboardSidebar.tsx +10 -2
  53. package/src/renderer/src-web/components/ImageStudioPanel.tsx +638 -0
  54. package/src/renderer/src-web/components/MintDashboard.tsx +65 -19
  55. package/src/renderer/src-web/components/PicturesLibrary.tsx +5 -1
  56. package/src/renderer/src-web/components/SettingsWindow.tsx +29 -20
  57. package/src/renderer/src-web/css/image-studio.css +722 -0
  58. package/src/renderer/src-web/css/settings.css +14 -4
  59. package/src/renderer/src-web/css/styles.css +292 -44
  60. package/src/renderer/src-web/index.css +152 -0
  61. package/src/renderer/src-web/tauri.ts +149 -0
  62. package/src-tauri/src/lib.rs +65 -5
  63. package/src-tauri/tauri.conf.json +1 -1
@@ -1,4 +1,5 @@
1
1
  import { type ChangeEvent, type FormEvent, useEffect, useRef, useState } from 'react'
2
+ import { mergeActivitySnapshots, trimAgentProgress } from '../agentProgress'
2
3
  import {
3
4
  clearChatHistory,
4
5
  deleteChatSession,
@@ -7,6 +8,7 @@ import {
7
8
  getRuntimeStatus,
8
9
  listChatSessions,
9
10
  listSavedPictures,
11
+ saveInteractionAgentActivity,
10
12
  streamChatMessage,
11
13
  submitToolApproval,
12
14
  listen,
@@ -21,17 +23,18 @@ import {
21
23
  import ChatPanel from './ChatPanel'
22
24
  import DashboardSidebar, { type DashboardView } from './DashboardSidebar'
23
25
  import PicturesLibrary from './PicturesLibrary'
26
+ import ImageStudioPanel from './ImageStudioPanel'
24
27
 
25
28
  const DEFAULT_CONFIG = {
26
29
  theme: 'dark',
27
- accentColor: '#4f83e6',
30
+ accentColor: '#10b981',
28
31
  systemTextColor: '#f8fafc',
29
32
  customBgStart: '#0f172a',
30
33
  customBgEnd: '#1e1b4b',
31
34
  customPanelBg: '#1e293b',
32
35
  glassBlur: 'blur(16px)',
33
36
  fontFamily: "'Outfit', sans-serif",
34
- fontSize: '15px',
37
+ fontSize: '18px',
35
38
  }
36
39
 
37
40
  const ACTIVE_CONVERSATION_ID_KEY = 'mint:web-active-conversation-id'
@@ -56,7 +59,7 @@ function activeConversationId() {
56
59
  const MOCK_WELCOME_INTERACTION = {
57
60
  id: -1,
58
61
  userText: '',
59
- aiText: `มิ้นท์กำลังรอสแตนด์บายเตรียมพร้อมช่วยคุณทีมอยู่เลยค่ะ! ✨ แล้วก็แอบนั่งจัดระเบียบข้อมูลนิดๆ หน่อยๆ ให้พร้อมใช้ด้วยค่ะ 😊💖\n\nแต่พอคุณทีมทักมา มิ้นท์ก็วางมือจากทุกอย่างมาคุยกับคุณทีมก่อนเลยนะคะค้าา! ช่วงนี้มีอะไรให้มิ้นท์ช่วยดูแล หรืออยากชวนคุยเรื่องไหนเป็นพิเศษไหมคะ มิ้นท์พร้อมมว๊ากกกค่ะ! 🚀🎯`,
62
+ aiText: `Hi there! I'm Mint, your AI assistant! 🎯✨ I'm here and ready to help you with whatever you need. I've been organizing some background data to make things smoother for you. 💖\n\nBut the moment you start chatting with me, I'll put everything aside and focus on you! Is there something I can help you with today, or would you like to chat about something special? Let's do this! 🚀💪`,
60
63
  provider: 'gemini',
61
64
  model: 'gemini-3-flash-preview',
62
65
  createdAt: new Date().toISOString(),
@@ -166,7 +169,7 @@ const applyThemeStyles = (cfg: any) => {
166
169
  document.documentElement.style.setProperty('--text-main', systemTextColor)
167
170
  document.documentElement.style.setProperty('--glass-blur', cfg.glassBlur || 'blur(16px)')
168
171
  document.body.style.fontFamily = cfg.fontFamily || "'Outfit', sans-serif"
169
- document.documentElement.style.fontSize = cfg.fontSize || '15px'
172
+ document.documentElement.style.fontSize = cfg.fontSize || '18px'
170
173
 
171
174
  if (theme === 'custom') {
172
175
  if (cfg.customBgStart && cfg.customBgEnd) {
@@ -211,8 +214,11 @@ export default function MintDashboard() {
211
214
  const [sendingImageCount, setSendingImageCount] = useState(0)
212
215
  const [streamedReply, setStreamedReply] = useState('')
213
216
  const [streamedResponse, setStreamedResponse] = useState<ChatResponse | null>(null)
217
+ const [streamingConversationId, setStreamingConversationId] = useState<string | null>(null)
214
218
  const [agentProgress, setAgentProgress] = useState<AgentProgress[]>([])
215
219
  const [agentActivitySnapshots, setAgentActivitySnapshots] = useState<Record<string, AgentProgress[]>>({})
220
+ const [thinkingExpanded, setThinkingExpanded] = useState<Record<string, boolean>>({})
221
+ const liveThinkingOpenRef = useRef(true)
216
222
  const [imageAttachments, setImageAttachments] = useState<Array<{ dataUri: string; name: string; previewDataUri?: string }>>([])
217
223
  const [documentAttachment, setDocumentAttachment] = useState<DocumentAttachment | null>(null)
218
224
  const [pendingApproval, setPendingApproval] = useState<any | null>(null)
@@ -231,7 +237,9 @@ export default function MintDashboard() {
231
237
 
232
238
  async function refreshHistory() {
233
239
  const history = await getRecentInteractions(50, conversationId)
234
- setInteractions(history.reverse())
240
+ const reversed = history.reverse()
241
+ setInteractions(reversed)
242
+ setAgentActivitySnapshots((current) => mergeActivitySnapshots(current, reversed))
235
243
  }
236
244
 
237
245
  async function refreshChatSessions(nextActiveId = conversationId) {
@@ -346,12 +354,15 @@ export default function MintDashboard() {
346
354
  const outgoingImage = outgoingImages.map((img) => img.dataUri).join(' ')
347
355
  const outgoingImageCount = outgoingImages.length
348
356
  setSending(true)
357
+ setStreamingConversationId(conversationId)
349
358
  setSendingMessage(promptText)
350
359
  setSendingImageCount(outgoingImageCount)
351
360
  setError('')
352
361
  setStreamedReply('')
353
362
  setStreamedResponse(null)
354
363
  setAgentProgress([])
364
+ liveThinkingOpenRef.current = true
365
+ setThinkingExpanded((current) => ({ ...current, live: true }))
355
366
  const progressSnapshot: AgentProgress[] = []
356
367
  if (options.clearComposer) {
357
368
  setMessage('')
@@ -368,7 +379,7 @@ export default function MintDashboard() {
368
379
  options.systemInstruction ?? '',
369
380
  (progress) => {
370
381
  progressSnapshot.push(progress)
371
- setAgentProgress((current) => [...current, progress].slice(-24))
382
+ setAgentProgress((current) => trimAgentProgress([...current, progress]))
372
383
  },
373
384
  outgoingDocument,
374
385
  null,
@@ -376,18 +387,33 @@ export default function MintDashboard() {
376
387
  )
377
388
  setStreamedResponse(response)
378
389
  const history = (await getRecentInteractions(50, conversationId)).reverse()
390
+ let enrichedHistory = history
379
391
  if (progressSnapshot.length > 0) {
380
392
  const newestInteraction = [...history]
381
393
  .reverse()
382
394
  .find((interaction) => interaction.aiText === response.text || interaction.userText === promptText) ?? history[history.length - 1]
383
395
  if (newestInteraction?.id != null) {
396
+ const interactionKey = String(newestInteraction.id)
397
+ enrichedHistory = history.map((interaction) =>
398
+ interaction.id === newestInteraction.id
399
+ ? { ...interaction, agentActivity: progressSnapshot }
400
+ : interaction,
401
+ )
384
402
  setAgentActivitySnapshots((current) => ({
385
403
  ...current,
386
- [String(newestInteraction.id)]: progressSnapshot.slice(),
404
+ [interactionKey]: progressSnapshot.slice(),
387
405
  }))
406
+ if (liveThinkingOpenRef.current) {
407
+ setThinkingExpanded((current) => ({
408
+ ...current,
409
+ [interactionKey]: true,
410
+ }))
411
+ }
412
+ await saveInteractionAgentActivity(newestInteraction.id, progressSnapshot)
388
413
  }
389
414
  }
390
- setInteractions(history)
415
+ setInteractions(enrichedHistory)
416
+ setAgentActivitySnapshots((current) => mergeActivitySnapshots(current, enrichedHistory))
391
417
  await refreshChatSessions()
392
418
  await refreshPictures()
393
419
  setStreamedReply('')
@@ -396,6 +422,7 @@ export default function MintDashboard() {
396
422
  setError(errorMessage(reason))
397
423
  } finally {
398
424
  setSending(false)
425
+ setStreamingConversationId(null)
399
426
  setSendingMessage('')
400
427
  setSendingImageCount(0)
401
428
  }
@@ -417,7 +444,7 @@ export default function MintDashboard() {
417
444
  }
418
445
 
419
446
  async function sendVoiceMessage(transcript: string, audioDataUri?: string | null) {
420
- const promptText = transcript.trim() || 'ข้อความเสียง'
447
+ const promptText = transcript.trim() || 'Voice message'
421
448
  if (!promptText || sending) return
422
449
  await sendPrompt(promptText, {
423
450
  audioDataUri,
@@ -567,6 +594,11 @@ export default function MintDashboard() {
567
594
  }
568
595
  }
569
596
 
597
+ function handleThinkingExpandedChange(key: string, open: boolean) {
598
+ if (key === 'live') liveThinkingOpenRef.current = open
599
+ setThinkingExpanded((current) => ({ ...current, [key]: open }))
600
+ }
601
+
570
602
  async function selectConversation(id: string) {
571
603
  if (id === conversationId) {
572
604
  changeView('chat')
@@ -582,7 +614,9 @@ export default function MintDashboard() {
582
614
  setDocumentAttachment(null)
583
615
  setAgentProgress([])
584
616
  const history = await getRecentInteractions(50, id)
585
- setInteractions(history.reverse())
617
+ const reversed = history.reverse()
618
+ setInteractions(reversed)
619
+ setAgentActivitySnapshots((current) => mergeActivitySnapshots(current, reversed))
586
620
  }
587
621
 
588
622
  async function deleteConversation(id: string) {
@@ -603,7 +637,9 @@ export default function MintDashboard() {
603
637
  setConversationId(nextActive)
604
638
  setAgentProgress([])
605
639
  const history = await getRecentInteractions(50, nextActive)
606
- setInteractions(history.reverse())
640
+ const reversed = history.reverse()
641
+ setInteractions(reversed)
642
+ setAgentActivitySnapshots((current) => mergeActivitySnapshots(current, reversed))
607
643
  }
608
644
 
609
645
  await refreshChatSessions(nextActive)
@@ -706,17 +742,19 @@ export default function MintDashboard() {
706
742
  <main className="assistant-workspace model-hidden">
707
743
  <ChatPanel
708
744
  interactions={interactions}
709
- sending={sending}
710
- sendingMessage={sendingMessage}
711
- sendingImageCount={sendingImageCount}
712
- streamedReply={streamedReply}
713
- streamedResponse={streamedResponse}
714
- agentProgress={agentProgress}
745
+ sending={sending && streamingConversationId === conversationId}
746
+ sendingMessage={streamingConversationId === conversationId ? sendingMessage : ''}
747
+ sendingImageCount={streamingConversationId === conversationId ? sendingImageCount : 0}
748
+ streamedReply={streamingConversationId === conversationId ? streamedReply : ''}
749
+ streamedResponse={streamingConversationId === conversationId ? streamedResponse : null}
750
+ agentProgress={streamingConversationId === conversationId ? agentProgress : []}
715
751
  agentActivitySnapshots={agentActivitySnapshots}
752
+ thinkingExpanded={thinkingExpanded}
753
+ onThinkingExpandedChange={handleThinkingExpandedChange}
716
754
  message={message}
717
755
  imageAttachments={imageAttachments}
718
756
  documentName={documentAttachment?.filename ?? ''}
719
- pendingApproval={pendingApproval}
757
+ pendingApproval={streamingConversationId === conversationId ? pendingApproval : null}
720
758
  smartContext={smartContext}
721
759
  agentMode={agentMode}
722
760
  status={status}
@@ -744,7 +782,15 @@ export default function MintDashboard() {
744
782
  onToggleMobileSidebar={() => setMobileSidebarOpen(!mobileSidebarOpen)}
745
783
  />
746
784
  </main>
747
- <PicturesLibrary view={view} pictures={pictures} onSetView={changeView} />
785
+ <PicturesLibrary view={view} pictures={pictures} onSetView={changeView} onRefreshPictures={refreshPictures} />
786
+ <ImageStudioPanel
787
+ view={view}
788
+ onRefreshPictures={refreshPictures}
789
+ onSendToChat={(_url, imgPrompt) => {
790
+ setView('chat')
791
+ setMessage(imgPrompt)
792
+ }}
793
+ />
748
794
  </div>
749
795
  <div className={`startup-loading ${startupReady ? 'is-hidden' : ''}`} aria-live="polite" aria-busy={!startupReady}>
750
796
  <div className="startup-loading-content">
@@ -10,9 +10,10 @@ interface PicturesLibraryProps {
10
10
  view: DashboardView
11
11
  pictures: PictureEntry[]
12
12
  onSetView: (view: DashboardView) => void
13
+ onRefreshPictures?: () => Promise<void>
13
14
  }
14
15
 
15
- export default function PicturesLibrary({ view, pictures, onSetView }: PicturesLibraryProps) {
16
+ export default function PicturesLibrary({ view, pictures, onSetView, onRefreshPictures }: PicturesLibraryProps) {
16
17
  const [visibleCount, setVisibleCount] = useState(INITIAL_VISIBLE_PICTURES)
17
18
  const visiblePictures = useMemo(
18
19
  () => pictures.slice(0, visibleCount),
@@ -46,6 +47,9 @@ export default function PicturesLibrary({ view, pictures, onSetView }: PicturesL
46
47
  <div><span className="pictures-kicker">Gallery</span><h2>Saved Pictures</h2></div>
47
48
  <div className="pictures-header-actions">
48
49
  <button className="pictures-close-btn" onClick={() => onSetView('chat')}>Close Gallery</button>
50
+ <button type="button" className="picture-refresh-btn" title="Refresh" onClick={() => onRefreshPictures?.()}>
51
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.13-3.36L23 10"/><path d="M20.49 15a9 9 0 0 1-14.13 3.36L1 14"/></svg>
52
+ </button>
49
53
  </div>
50
54
  </header>
51
55
  {pictures.length === 0 ? (
@@ -3,14 +3,14 @@ import { getLocalApiBase, isTauriRuntime, getProfileValue, setProfileValue } fro
3
3
 
4
4
  const DEFAULT_CONFIG = {
5
5
  theme: 'dark',
6
- accentColor: '#4f83e6',
6
+ accentColor: '#10b981',
7
7
  systemTextColor: '#f8fafc',
8
8
  customBgStart: '#0f172a',
9
9
  customBgEnd: '#1e1b4b',
10
10
  customPanelBg: '#1e293b',
11
11
  glassBlur: 'blur(16px)',
12
12
  fontFamily: "'Outfit', sans-serif",
13
- fontSize: '15px',
13
+ fontSize: '18px',
14
14
  apiKey: '',
15
15
  aiProvider: 'gemini',
16
16
  geminiModel: 'gemini-2.5-flash',
@@ -143,6 +143,8 @@ const OLLAMA_MODELS = [
143
143
  'qwen2.5:latest'
144
144
  ]
145
145
 
146
+
147
+
146
148
  export default function SettingsWindow() {
147
149
  const isDesktopApp = isTauriRuntime()
148
150
  const [activeTab, setActiveTab] = useState<TabType>('sect-general')
@@ -170,6 +172,8 @@ export default function SettingsWindow() {
170
172
  const [updateMessage, setUpdateMessage] = useState('')
171
173
  const [updateAvailable, setUpdateAvailable] = useState(false)
172
174
 
175
+
176
+
173
177
  // Load settings on mount
174
178
  useEffect(() => {
175
179
  async function loadSettings() {
@@ -970,24 +974,28 @@ export default function SettingsWindow() {
970
974
  <div className="form-grid single">
971
975
  <div className="setting-row">
972
976
  <label>Your Name / Nickname</label>
973
- <input
974
- type="text"
975
- value={userName}
976
- onChange={(e) => setUserName(e.target.value)}
977
- placeholder="e.g. Pheem, Jane"
978
- />
979
- <p className="hint">Used by the assistant to address you in conversation.</p>
977
+ <div className="memory-field-container">
978
+ <input
979
+ type="text"
980
+ value={userName}
981
+ onChange={(e) => setUserName(e.target.value)}
982
+ placeholder="e.g. Pheem, Jane"
983
+ />
984
+ <p className="hint">Used by the assistant to address you in conversation.</p>
985
+ </div>
980
986
  </div>
981
987
 
982
988
  <div className="setting-row">
983
989
  <label>Custom Instructions & Preferences</label>
984
- <textarea
985
- value={userPreferences}
986
- onChange={(e) => setUserPreferences(e.target.value)}
987
- placeholder="e.g. Explain coding concepts step-by-step. Prefer TypeScript. Talk in Thai. Keep explanations concise."
988
- style={{ minHeight: '150px', resize: 'vertical' }}
989
- />
990
- <p className="hint">Preferences, guidelines, or persona instructions for the assistant.</p>
990
+ <div className="memory-field-container">
991
+ <textarea
992
+ value={userPreferences}
993
+ onChange={(e) => setUserPreferences(e.target.value)}
994
+ placeholder="e.g. Explain coding concepts step-by-step. Prefer TypeScript. Talk in Thai. Keep explanations concise."
995
+ style={{ minHeight: '150px', resize: 'vertical' }}
996
+ />
997
+ <p className="hint">Preferences, guidelines, or persona instructions for the assistant.</p>
998
+ </div>
991
999
  </div>
992
1000
  </div>
993
1001
  </section>
@@ -1371,10 +1379,11 @@ export default function SettingsWindow() {
1371
1379
  <div className="setting-row">
1372
1380
  <label>Font Size</label>
1373
1381
  <select value={config.fontSize} onChange={(e) => updateField('fontSize', e.target.value)}>
1374
- <option value="14px">Small</option>
1375
- <option value="15px">Medium (Default)</option>
1376
- <option value="16px">Large</option>
1377
- <option value="17px">Extra Large</option>
1382
+ <option value="16px">Small</option>
1383
+ <option value="18px">Medium (Default)</option>
1384
+ <option value="22px">Large</option>
1385
+ <option value="26px">Extra Large</option>
1386
+ <option value="30px">Extra Extra Large</option>
1378
1387
  </select>
1379
1388
  </div>
1380
1389
  </div>