@pheem49/mint 1.4.2 → 1.5.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 (97) hide show
  1. package/GUIDE_TH.md +113 -0
  2. package/README.md +267 -78
  3. package/assets/CLI_Screen.png +0 -0
  4. package/main.js +76 -890
  5. package/mint-cli-logic.js +3 -107
  6. package/mint-cli.js +594 -29
  7. package/models/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
  8. package/models/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +14 -0
  9. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +10 -0
  10. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253/347/234/274/347/217/240/346/221/207/346/231/203.exp3.json +15 -0
  11. package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json +10 -0
  12. package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json +50 -0
  13. package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json +10 -0
  14. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +10 -0
  15. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json +10 -0
  16. package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json +10 -0
  17. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png +0 -0
  18. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png +0 -0
  19. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png +0 -0
  20. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png +0 -0
  21. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json +1498 -0
  22. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3 +0 -0
  23. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +47 -0
  24. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json +6658 -0
  25. package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json +1299 -0
  26. package/models/Shiroko_Model/Shiroko//342/232/241/351/253/230/344/272/256/342/232/241/344/275/277/347/224/250/346/225/231/347/250/213/344/270/216/346/263/250/346/204/217/344/272/213/351/241/271.txt +23 -0
  27. package/package.json +37 -4
  28. package/src/AI_Brain/Gemini_API.js +223 -65
  29. package/src/AI_Brain/autonomous_brain.js +11 -0
  30. package/src/AI_Brain/behavior_memory.js +26 -5
  31. package/src/AI_Brain/headless_agent.js +4 -0
  32. package/src/AI_Brain/knowledge_base.js +61 -8
  33. package/src/AI_Brain/memory_store.js +354 -10
  34. package/src/Automation_Layer/file_operations.js +1 -1
  35. package/src/CLI/chat_router.js +20 -7
  36. package/src/CLI/chat_ui.js +596 -825
  37. package/src/CLI/code_agent.js +347 -56
  38. package/src/CLI/gmail_auth.js +210 -0
  39. package/src/CLI/image_input.js +90 -0
  40. package/src/CLI/list_features.js +2 -0
  41. package/src/CLI/onboarding.js +364 -55
  42. package/src/CLI/updater.js +210 -0
  43. package/src/Channels/brave_search_bridge.js +35 -0
  44. package/src/Channels/discord_bridge.js +68 -0
  45. package/src/Channels/google_search_bridge.js +38 -0
  46. package/src/Channels/line_bridge.js +60 -0
  47. package/src/Channels/slack_bridge.js +53 -0
  48. package/src/Channels/telegram_bridge.js +49 -0
  49. package/src/Channels/whatsapp_bridge.js +55 -0
  50. package/src/Command_Parser/parser.js +12 -1
  51. package/src/Plugins/gmail.js +251 -0
  52. package/src/Plugins/google_calendar.js +245 -19
  53. package/src/Plugins/notion.js +256 -0
  54. package/src/System/action_executor.js +178 -0
  55. package/src/System/bridge_manager.js +76 -0
  56. package/src/System/chat_history_manager.js +23 -5
  57. package/src/System/config_manager.js +71 -7
  58. package/src/System/custom_workflows.js +31 -2
  59. package/src/System/google_tts_urls.js +51 -0
  60. package/src/System/granular_automation.js +122 -53
  61. package/src/System/ipc_handlers.js +238 -0
  62. package/src/System/proactive_loop.js +153 -0
  63. package/src/System/safety_manager.js +273 -0
  64. package/src/System/sandbox_runner.js +182 -0
  65. package/src/System/screen_capture.js +175 -0
  66. package/src/System/system_automation.js +127 -81
  67. package/src/System/system_info.js +70 -0
  68. package/src/System/task_manager.js +15 -5
  69. package/src/System/tool_registry.js +280 -0
  70. package/src/System/window_manager.js +212 -0
  71. package/src/UI/live2d_manager.js +368 -0
  72. package/src/UI/renderer.js +208 -24
  73. package/src/UI/settings.html +24 -0
  74. package/src/UI/settings.js +14 -4
  75. package/src/UI/styles.css +466 -32
  76. package/.codex +0 -0
  77. package/docs/assets/Agent_Mint.png +0 -0
  78. package/docs/assets/CLI_Screen.png +0 -0
  79. package/docs/assets/Settings.png +0 -0
  80. package/docs/assets/icon.png +0 -0
  81. package/docs/index.html +0 -132
  82. package/docs/style.css +0 -579
  83. package/index.html +0 -16
  84. package/src/UI/index.html +0 -126
  85. package/tech_news.txt +0 -3
  86. package/test_knowledge.txt +0 -3
  87. package/tests/agent_orchestrator.test.js +0 -41
  88. package/tests/chat_router.test.js +0 -42
  89. package/tests/code_agent.test.js +0 -69
  90. package/tests/config_manager.test.js +0 -141
  91. package/tests/docker.test.js +0 -46
  92. package/tests/file_operations.test.js +0 -57
  93. package/tests/memory_store.test.js +0 -185
  94. package/tests/provider_routing.test.js +0 -67
  95. package/tests/spotify.test.js +0 -201
  96. package/tests/system_monitor.test.js +0 -37
  97. package/tests/workspace_manager.test.js +0 -56
@@ -11,6 +11,9 @@ const visionBtn = document.getElementById('vision-btn');
11
11
  const imagePreviewContainer = document.getElementById('image-preview-container');
12
12
  const imagePreview = document.getElementById('image-preview');
13
13
  const removeImageBtn = document.getElementById('remove-image-btn');
14
+ const modelMount = document.getElementById('model-mount');
15
+ const modelShell = document.getElementById('model-shell');
16
+ const modelStatus = document.getElementById('model-status');
14
17
 
15
18
  // Proactive Assistant elements
16
19
  const proactiveBar = document.getElementById('proactive-bar');
@@ -24,6 +27,31 @@ let ttsProvider = 'google';
24
27
  let ttsVolume = 1.0;
25
28
  let ttsSpeed = 1.0;
26
29
  let ttsPitch = 1.0;
30
+ let lastConversationLanguage = 'auto';
31
+
32
+ function detectConversationLanguage(text) {
33
+ const value = String(text || '');
34
+ if (/[\u0E00-\u0E7F]/.test(value)) return 'thai';
35
+ if (/[A-Za-z]/.test(value)) return 'english';
36
+ return 'auto';
37
+ }
38
+
39
+ function rememberConversationLanguage(text) {
40
+ const detected = detectConversationLanguage(text);
41
+ if (detected !== 'auto') {
42
+ lastConversationLanguage = detected;
43
+ }
44
+ }
45
+
46
+ function buildInteractionLanguageInstruction() {
47
+ if (lastConversationLanguage === 'thai') {
48
+ return 'Current conversation language: Thai. Reply in Thai. Do not reply in English just because this interaction instruction is written in English.';
49
+ }
50
+ if (lastConversationLanguage === 'english') {
51
+ return 'Current conversation language: English. Reply in English. Do not switch to Thai.';
52
+ }
53
+ return 'Infer the reply language from the recent conversation before this interaction instruction, not from the language of this instruction.';
54
+ }
27
55
 
28
56
  // --- Theme Loading ---
29
57
  function applyTheme(theme, accentColor, systemTextColor, config = {}) {
@@ -392,11 +420,17 @@ let currentAudioPlayer = null;
392
420
 
393
421
  function speakText(text, options = {}) {
394
422
  if (window.api && window.api.setAiState) window.api.setAiState('speaking');
395
- const onEnd = typeof options.onEnd === 'function' ? options.onEnd : null;
423
+ const onEnd = typeof options.onEnd === 'function' ? options.onEnd : () => {};
424
+
425
+ const wrappedOnEnd = () => {
426
+ if (window.Live2DManager) Live2DManager.stopLipSync();
427
+ onEnd();
428
+ };
429
+
396
430
  return new Promise(async (resolve) => {
397
431
  if (!enableVoiceReply) {
398
432
  if (window.api && window.api.setAiState) window.api.setAiState('idle');
399
- if (onEnd) onEnd();
433
+ wrappedOnEnd();
400
434
  return resolve();
401
435
  }
402
436
 
@@ -406,16 +440,20 @@ function speakText(text, options = {}) {
406
440
  currentAudioPlayer.currentTime = 0;
407
441
  currentAudioPlayer = null;
408
442
  }
443
+ if (window.Live2DManager) Live2DManager.stopLipSync();
444
+
409
445
  if ('speechSynthesis' in window) {
410
446
  window.speechSynthesis.cancel();
411
447
  }
412
448
 
413
449
  if (!text || !text.trim()) {
414
450
  if (window.api && window.api.setAiState) window.api.setAiState('idle');
415
- if (onEnd) onEnd();
451
+ wrappedOnEnd();
416
452
  return resolve();
417
453
  }
418
454
 
455
+ if (window.Live2DManager) Live2DManager.startLipSync();
456
+
419
457
  try {
420
458
  if (ttsProvider !== 'native') {
421
459
  const urls = await window.api.getTtsUrls(text);
@@ -424,7 +462,7 @@ function speakText(text, options = {}) {
424
462
  const playNext = () => {
425
463
  if (i >= urls.length) {
426
464
  if (window.api && window.api.setAiState) window.api.setAiState('idle');
427
- if (onEnd) onEnd();
465
+ wrappedOnEnd();
428
466
  return resolve();
429
467
  }
430
468
  const audio = new Audio(urls[i].url);
@@ -443,7 +481,7 @@ function speakText(text, options = {}) {
443
481
  };
444
482
  audio.play().catch(e => {
445
483
  console.error("Audio playback prevented:", e);
446
- fallbackSpeak(text, onEnd, resolve);
484
+ fallbackSpeak(text, wrappedOnEnd, resolve);
447
485
  });
448
486
  };
449
487
  playNext();
@@ -455,7 +493,7 @@ function speakText(text, options = {}) {
455
493
  }
456
494
 
457
495
  // Fallback
458
- fallbackSpeak(text, onEnd, resolve);
496
+ fallbackSpeak(text, wrappedOnEnd, resolve);
459
497
  });
460
498
  }
461
499
 
@@ -558,7 +596,15 @@ clearBtn.addEventListener('click', async () => {
558
596
  appendMessage('Chat history cleared. Starting fresh! 🌿', 'ai', null, new Date().toISOString());
559
597
  });
560
598
 
561
- function appendMessage(text, sender, base64Image = null, timestamp = null) {
599
+ function formatProviderInfo(providerInfo) {
600
+ if (!providerInfo || typeof providerInfo !== 'object') return '';
601
+ const provider = String(providerInfo.provider || '').trim();
602
+ const model = String(providerInfo.model || '').trim();
603
+ if (!provider && !model) return '';
604
+ return model ? `${provider || 'AI'} • ${model}` : provider;
605
+ }
606
+
607
+ function appendMessage(text, sender, base64Image = null, timestamp = null, options = {}) {
562
608
  const messageDiv = document.createElement('div');
563
609
  messageDiv.classList.add('message', `${sender}-message`);
564
610
 
@@ -586,11 +632,23 @@ function appendMessage(text, sender, base64Image = null, timestamp = null) {
586
632
 
587
633
  bubbleWrapper.appendChild(bubble);
588
634
 
589
- // Add Timestamp
590
- if (timestamp) {
635
+ const providerLabel = sender === 'ai' ? formatProviderInfo(options.providerInfo) : '';
636
+
637
+ // Add metadata
638
+ if (timestamp || providerLabel) {
591
639
  const timeDiv = document.createElement('div');
592
640
  timeDiv.classList.add('message-time');
593
- timeDiv.textContent = formatTime(timestamp);
641
+ if (providerLabel) {
642
+ const providerSpan = document.createElement('span');
643
+ providerSpan.classList.add('provider-badge');
644
+ providerSpan.textContent = providerLabel;
645
+ timeDiv.appendChild(providerSpan);
646
+ }
647
+ if (timestamp) {
648
+ const timeSpan = document.createElement('span');
649
+ timeSpan.textContent = formatTime(timestamp);
650
+ timeDiv.appendChild(timeSpan);
651
+ }
594
652
  bubbleWrapper.appendChild(timeDiv);
595
653
  }
596
654
 
@@ -638,6 +696,7 @@ function estimateMessageDelay(text) {
638
696
  async function appendAiMessages(text, options = {}) {
639
697
  const allowDelay = options.allowDelay !== false;
640
698
  const timestamp = options.timestamp || new Date().toISOString();
699
+ const providerInfo = options.providerInfo || null;
641
700
  const parts = splitAiMessages(text);
642
701
  let lastDiv = null;
643
702
 
@@ -649,7 +708,8 @@ async function appendAiMessages(text, options = {}) {
649
708
  }
650
709
  // Only show timestamp for the last bubble in a group if multiple
651
710
  const partTimestamp = (index === parts.length - 1) ? timestamp : null;
652
- lastDiv = appendMessage(parts[index], 'ai', null, partTimestamp);
711
+ const partProviderInfo = (index === parts.length - 1) ? providerInfo : null;
712
+ lastDiv = appendMessage(parts[index], 'ai', null, partTimestamp, { providerInfo: partProviderInfo });
653
713
  }
654
714
 
655
715
  return lastDiv;
@@ -734,14 +794,52 @@ function scrollToBottom() {
734
794
  chatContainer.scrollTop = chatContainer.scrollHeight;
735
795
  }
736
796
 
797
+ function loadScript(src) {
798
+ return new Promise((resolve, reject) => {
799
+ if (document.querySelector(`script[src="${src}"]`)) {
800
+ resolve();
801
+ return;
802
+ }
803
+ const script = document.createElement('script');
804
+ script.src = src;
805
+ script.onload = resolve;
806
+ script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
807
+ document.body.appendChild(script);
808
+ });
809
+ }
810
+
811
+ async function loadLive2DWhenIdle() {
812
+ if (!modelMount || window.Live2DManager) return;
813
+ try {
814
+ await loadScript('../../node_modules/@hazart-pkg/live2d-core/live2dcubismcore.min.js');
815
+ await loadScript('../../node_modules/pixi.js/dist/browser/pixi.min.js');
816
+ await loadScript('../../node_modules/pixi-live2d-display/dist/cubism4.min.js');
817
+ await loadScript('live2d_manager.js');
818
+ if (window.Live2DManager) {
819
+ await Live2DManager.loadModel(modelMount, modelStatus, modelShell);
820
+ }
821
+ } catch (err) {
822
+ console.error('[Live2D] Deferred load failed:', err);
823
+ if (modelStatus) {
824
+ modelStatus.classList.add('is-error');
825
+ modelStatus.textContent = 'Live2D model unavailable.';
826
+ }
827
+ }
828
+ }
829
+
737
830
  async function loadChatHistory() {
738
831
  try {
739
832
  const history = await window.api.getChatHistory();
833
+ const initial = chatContainer.querySelector('.message.initial');
834
+
740
835
  if (!Array.isArray(history) || history.length === 0) {
836
+ if (initial) {
837
+ initial.style.display = 'flex';
838
+ initial.style.opacity = '1';
839
+ }
741
840
  return;
742
841
  }
743
842
 
744
- const initial = chatContainer.querySelector('.message.initial');
745
843
  if (initial) {
746
844
  initial.remove();
747
845
  }
@@ -749,11 +847,12 @@ async function loadChatHistory() {
749
847
  for (const item of history) {
750
848
  if (!item || typeof item.text !== 'string' || !item.text.trim()) continue;
751
849
  const sender = item.sender === 'user' ? 'user' : 'ai';
752
- if (sender === 'ai') {
753
- await appendAiMessages(item.text, { allowDelay: false, timestamp: item.timestamp });
754
- } else {
755
- appendMessage(item.text, sender, null, item.timestamp);
850
+ if (sender === 'user' && !String(item.text).startsWith('Model interaction:')) {
851
+ rememberConversationLanguage(item.text);
756
852
  }
853
+ appendMessage(item.text, sender, null, item.timestamp, {
854
+ providerInfo: sender === 'ai' ? item.providerInfo : null
855
+ });
757
856
  }
758
857
  } catch (error) {
759
858
  console.error('Failed to load chat history:', error);
@@ -763,23 +862,31 @@ async function loadChatHistory() {
763
862
  async function sendTextMessage(text, options = {}) {
764
863
  const cleanText = (text || '').trim();
765
864
  const allowSmartContext = options.allowSmartContext !== false;
865
+ const includePendingImage = options.includePendingImage !== false;
866
+ const displayText = options.displayText !== undefined ? options.displayText : cleanText;
867
+ const trackLanguage = options.trackLanguage !== false;
766
868
 
767
869
  // We can send either a text message, an image, or both.
768
- if (!cleanText && !currentBase64Image) return;
870
+ if (!cleanText && (!includePendingImage || !currentBase64Image)) return;
769
871
 
770
872
  // Cache the image for sending and UI, then clear
771
- let imageToSend = currentBase64Image;
873
+ let imageToSend = includePendingImage ? currentBase64Image : null;
772
874
 
773
875
  // Clear input & UI for explicit images
774
876
  chatInput.value = '';
775
- currentBase64Image = null;
776
- imagePreviewContainer.style.display = 'none';
777
- imagePreview.src = '';
877
+ if (includePendingImage) {
878
+ currentBase64Image = null;
879
+ imagePreviewContainer.style.display = 'none';
880
+ imagePreview.src = '';
881
+ }
778
882
 
779
883
  const now = new Date().toISOString();
780
884
 
781
885
  // Show user message (with explicit image if available)
782
- appendMessage(cleanText, 'user', imageToSend, now);
886
+ appendMessage(displayText, 'user', imageToSend, now);
887
+ if (trackLanguage) {
888
+ rememberConversationLanguage(displayText || cleanText);
889
+ }
783
890
 
784
891
  // Show typing early so user knows we are processing
785
892
  showTyping();
@@ -824,12 +931,18 @@ async function sendTextMessage(text, options = {}) {
824
931
  } else {
825
932
  // General system info (date, time, RAM, CPU)
826
933
  const info = await window.api.getSystemInfo();
827
- response.response += `\n\n📅 วันนี้: ${info.date}\n⏰ เวลา: ${info.time}\n💻 RAM: ${info.ram.used} / ${info.ram.total} (${info.ram.percent})`;
934
+ const machine = info.machine && info.machine.display ? `\n🖥️ รุ่นเครื่อง: ${info.machine.display}` : '';
935
+ const distro = info.distro ? `\nระบบ: ${info.distro}` : '';
936
+ response.response += `\n\n📅 วันนี้: ${info.date}\n⏰ เวลา: ${info.time}${machine}${distro}\n💻 CPU: ${info.cpu.model} (${info.cpu.cores} คอร์)\n💻 RAM: ${info.ram.used} / ${info.ram.total} (${info.ram.percent})`;
828
937
  }
829
938
  }
830
939
 
831
940
  // Show AI response
832
- const msgDiv = await appendAiMessages(response.response, { allowDelay: true });
941
+ const msgDiv = await appendAiMessages(response.response, {
942
+ allowDelay: true,
943
+ timestamp: response.timestamp,
944
+ providerInfo: response.providerInfo
945
+ });
833
946
 
834
947
  // Speak AI response
835
948
  await speakText(normalizeAiText(response.response), { onEnd: resumeSpeechIfNeeded });
@@ -854,6 +967,20 @@ chatForm.addEventListener('submit', throttle(async (e) => {
854
967
  await sendTextMessage(text);
855
968
  }, 500));
856
969
 
970
+ window.addEventListener('live2d-model-interaction', async (event) => {
971
+ const prompt = event?.detail?.prompt;
972
+ if (!prompt) return;
973
+ if (window.api && window.api.setAiState) window.api.setAiState('thinking');
974
+ const interactionPrompt = `${prompt}\n\n${buildInteractionLanguageInstruction()}`;
975
+ const displayPrefix = lastConversationLanguage === 'thai' ? 'แตะโมเดล' : 'Model interaction';
976
+ await sendTextMessage(interactionPrompt, {
977
+ allowSmartContext: false,
978
+ includePendingImage: false,
979
+ trackLanguage: false,
980
+ displayText: `${displayPrefix}: ${event.detail.label || event.detail.region || 'Interaction'}`
981
+ });
982
+ });
983
+
857
984
  // --- Image Paste and Drag-n-Drop Support ---
858
985
  function handleImageFile(file) {
859
986
  if (!file || !file.type.startsWith('image/')) return;
@@ -910,6 +1037,8 @@ window.addEventListener('DOMContentLoaded', async () => {
910
1037
  chatInput.focus();
911
1038
  await loadTheme();
912
1039
  await loadChatHistory();
1040
+ const scheduleLive2DLoad = window.requestIdleCallback || ((callback) => setTimeout(callback, 750));
1041
+ scheduleLive2DLoad(() => loadLive2DWhenIdle());
913
1042
  });
914
1043
 
915
1044
  // Proactive OS Notifications (Battery, Network, etc.)
@@ -1002,6 +1131,61 @@ if (smartContextToggle) {
1002
1131
  });
1003
1132
  }
1004
1133
 
1134
+ // Toggle Live2D Model visibility
1135
+ const toggleModelBtn = document.getElementById('toggle-model-btn');
1136
+ const assistantWorkspace = document.querySelector('.assistant-workspace');
1137
+
1138
+ if (toggleModelBtn && assistantWorkspace) {
1139
+ toggleModelBtn.addEventListener('click', () => {
1140
+ const isHidden = assistantWorkspace.classList.toggle('model-hidden');
1141
+ toggleModelBtn.classList.toggle('active', isHidden);
1142
+
1143
+ // Save preference to local storage
1144
+ localStorage.setItem('mint-model-hidden', String(isHidden));
1145
+
1146
+ // Refit model if shown
1147
+ if (!isHidden && window.Live2DManager && Live2DManager.model) {
1148
+ // Wait for transition
1149
+ setTimeout(() => {
1150
+ // Trigger a resize event to refit
1151
+ window.dispatchEvent(new Event('resize'));
1152
+ }, 450);
1153
+ }
1154
+ });
1155
+
1156
+ // Restore preference on load
1157
+ const savedModelHidden = localStorage.getItem('mint-model-hidden');
1158
+ const savedHidden = savedModelHidden === null || savedModelHidden === 'true';
1159
+ if (savedHidden) {
1160
+ assistantWorkspace.classList.add('model-hidden');
1161
+ toggleModelBtn.classList.add('active');
1162
+ }
1163
+ }
1164
+
1165
+ // Cycle Shiroko's Expression
1166
+ const changeExpressionBtn = document.getElementById('change-expression-btn');
1167
+ if (changeExpressionBtn) {
1168
+ changeExpressionBtn.addEventListener('click', () => {
1169
+ if (window.Live2DManager) {
1170
+ Live2DManager.cycleExpression();
1171
+ }
1172
+ });
1173
+ }
1174
+
1175
+ // Toggle Live2D interaction area guide
1176
+ const interactionGuideBtn = document.getElementById('interaction-guide-btn');
1177
+ if (interactionGuideBtn && modelShell) {
1178
+ const savedGuideVisible = localStorage.getItem('mint-interaction-guide-visible') === 'true';
1179
+ modelShell.classList.toggle('show-interaction-guide', savedGuideVisible);
1180
+ interactionGuideBtn.classList.toggle('active', savedGuideVisible);
1181
+
1182
+ interactionGuideBtn.addEventListener('click', () => {
1183
+ const isVisible = modelShell.classList.toggle('show-interaction-guide');
1184
+ interactionGuideBtn.classList.toggle('active', isVisible);
1185
+ localStorage.setItem('mint-interaction-guide-visible', String(isVisible));
1186
+ });
1187
+ }
1188
+
1005
1189
  // Spotlight integration
1006
1190
  window.api.onSpotlightToChat((query) => {
1007
1191
  chatInput.value = query;
@@ -417,6 +417,30 @@
417
417
  </div>
418
418
  </div>
419
419
 
420
+ <!-- Gmail -->
421
+ <div class="plugin-card">
422
+ <div class="plugin-icon">✉️</div>
423
+ <div class="plugin-info">
424
+ <div class="plugin-name">Gmail</div>
425
+ <div class="plugin-desc">Read email and create drafts safely</div>
426
+ </div>
427
+ <div class="plugin-actions">
428
+ <button class="btn-connect" id="btn-plugin-gmail" data-plugin="gmail">Connect</button>
429
+ </div>
430
+ </div>
431
+
432
+ <!-- Notion -->
433
+ <div class="plugin-card">
434
+ <div class="plugin-icon">📝</div>
435
+ <div class="plugin-info">
436
+ <div class="plugin-name">Notion</div>
437
+ <div class="plugin-desc">Create notes, pages, and read databases</div>
438
+ </div>
439
+ <div class="plugin-actions">
440
+ <button class="btn-connect" id="btn-plugin-notion" data-plugin="notion">Connect</button>
441
+ </div>
442
+ </div>
443
+
420
444
  <!-- Discord -->
421
445
  <div class="plugin-card">
422
446
  <div class="plugin-icon">💬</div>
@@ -23,9 +23,17 @@ const DEFAULT_CONFIG = {
23
23
  ttsPitch: 1.0,
24
24
  pluginSpotifyEnabled: true,
25
25
  pluginCalendarEnabled: false,
26
+ pluginGmailEnabled: false,
27
+ pluginNotionEnabled: false,
26
28
  pluginDiscordEnabled: false,
27
29
  showDesktopWidget: true,
28
- mcpServers: {}
30
+ mcpServers: {},
31
+ openaiModel: 'gpt-4o',
32
+ anthropicModel: 'claude-3-5-sonnet-latest',
33
+ hfModel: 'meta-llama/Meta-Llama-3-8B-Instruct',
34
+ localApiBaseUrl: '',
35
+ localModelName: 'local-model',
36
+ ollamaHost: ''
29
37
  };
30
38
 
31
39
  let currentConfig = { ...DEFAULT_CONFIG };
@@ -68,7 +76,7 @@ function applyConfig(config) {
68
76
  if (hfInput) hfInput.value = config.hfApiKey || '';
69
77
 
70
78
  const ollamaHostInput = document.getElementById('ollama-host-input');
71
- if (ollamaHostInput) ollamaHostInput.value = config.ollamaHost || 'http://localhost:11434';
79
+ if (ollamaHostInput) ollamaHostInput.value = config.ollamaHost || '';
72
80
 
73
81
  // Apply Gemini model
74
82
  applyModelSelection(config.geminiModel);
@@ -95,7 +103,7 @@ function applyConfig(config) {
95
103
 
96
104
  const localApiBaseUrlInput = document.getElementById('local-api-base-url');
97
105
  if (localApiBaseUrlInput) {
98
- localApiBaseUrlInput.value = config.localApiBaseUrl || 'http://localhost:1234/v1';
106
+ localApiBaseUrlInput.value = config.localApiBaseUrl || '';
99
107
  }
100
108
 
101
109
  const localModelNameInput = document.getElementById('local-model-name');
@@ -142,6 +150,8 @@ function applyConfig(config) {
142
150
  // Plugins logic
143
151
  updatePluginButton('spotify', config.pluginSpotifyEnabled);
144
152
  updatePluginButton('calendar', config.pluginCalendarEnabled);
153
+ updatePluginButton('gmail', config.pluginGmailEnabled);
154
+ updatePluginButton('notion', config.pluginNotionEnabled);
145
155
  updatePluginButton('discord', config.pluginDiscordEnabled);
146
156
 
147
157
  // Apply Automation Browser
@@ -691,7 +701,7 @@ function updatePluginButton(pluginName, isEnabled) {
691
701
  }
692
702
 
693
703
  // Bind plugin buttons
694
- ['spotify', 'calendar', 'discord'].forEach(plugin => {
704
+ ['spotify', 'calendar', 'gmail', 'notion', 'discord'].forEach(plugin => {
695
705
  const btn = document.getElementById(`btn-plugin-${plugin}`);
696
706
  if (btn) {
697
707
  btn.addEventListener('click', () => {