@pheem49/mint 1.5.0 → 1.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -1
- package/main.js +28 -14
- package/mint-cli-logic.js +3 -119
- package/mint-cli.js +201 -500
- package/models/Shiroko_Model/Shiroko/Shiroko_Core/72d86db84cfa9730b894c241fd24c0db.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core/items_pinned_to_model.json +14 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +40 -0
- 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
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/233/264/350/243/231.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/215/347/205/247.exp3.json +50 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//346/213/277/347/254/224.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +15 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/214/253/345/222/252/346/273/244/351/225/234.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/234/274/351/225/234.exp3.json +10 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_00.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_01.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_02.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.4096/texture_03.png +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.cdi3.json +1498 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.moc3 +0 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.model3.json +47 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.physics3.json +6658 -0
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//351/235/242/351/245/2740.vtube.json +1299 -0
- 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
- package/package.json +40 -17
- package/src/AI_Brain/Gemini_API.js +147 -46
- package/src/AI_Brain/autonomous_brain.js +2 -1
- package/src/AI_Brain/memory_store.js +299 -3
- package/src/AI_Brain/proactive_engine.js +12 -2
- package/src/Automation_Layer/browser_automation.js +26 -24
- package/src/CLI/approval_handler.js +42 -0
- package/src/CLI/chat_router.js +18 -6
- package/src/CLI/chat_ui.js +583 -52
- package/src/CLI/cli_colors.js +32 -0
- package/src/CLI/cli_formatters.js +89 -0
- package/src/CLI/code_agent.js +369 -71
- package/src/CLI/image_input.js +90 -0
- package/src/CLI/intent_detectors.js +181 -0
- package/src/CLI/interactive_chat.js +479 -0
- package/src/CLI/list_features.js +3 -0
- package/src/CLI/onboarding.js +72 -15
- package/src/CLI/repo_summarizer.js +282 -0
- package/src/CLI/semantic_code_search.js +312 -0
- package/src/CLI/skill_manager.js +41 -0
- package/src/CLI/slash_command_handler.js +418 -0
- package/src/CLI/symbol_indexer.js +231 -0
- package/src/CLI/updater.js +6 -4
- package/src/Channels/discord_bridge.js +11 -13
- package/src/Channels/line_bridge.js +10 -10
- package/src/Channels/slack_bridge.js +7 -12
- package/src/Channels/telegram_bridge.js +6 -14
- package/src/Channels/whatsapp_bridge.js +11 -9
- package/src/System/action_executor.js +59 -10
- package/src/System/chat_history_manager.js +20 -12
- package/src/System/config_manager.js +31 -1
- package/src/System/granular_automation.js +122 -53
- package/src/System/optional_require.js +23 -0
- package/src/System/proactive_loop.js +19 -3
- package/src/System/safety_manager.js +108 -0
- package/src/System/sandbox_runner.js +182 -0
- package/src/System/system_automation.js +127 -81
- package/src/System/system_info.js +70 -0
- package/src/System/tool_registry.js +280 -0
- package/src/System/window_manager.js +4 -2
- package/src/UI/live2d_manager.js +566 -0
- package/src/UI/renderer.js +339 -21
- package/src/UI/settings.css +655 -420
- package/src/UI/settings.html +478 -432
- package/src/UI/settings.js +10 -8
- package/src/UI/styles.css +516 -31
- package/.codex +0 -0
- package/docs/assets/Agent_Mint.png +0 -0
- package/docs/assets/CLI_Screen.png +0 -0
- package/docs/assets/Settings.png +0 -0
- package/docs/assets/icon.png +0 -0
- package/docs/guide.html +0 -632
- package/docs/index.html +0 -133
- package/docs/style.css +0 -579
- package/index.html +0 -16
- package/src/UI/index.html +0 -126
- package/tech_news.txt +0 -3
- package/test_knowledge.txt +0 -3
- package/tests/action_executor_safety.test.js +0 -67
- package/tests/agent_orchestrator.test.js +0 -41
- package/tests/chat_router.test.js +0 -42
- package/tests/code_agent.test.js +0 -69
- package/tests/config_manager.test.js +0 -141
- package/tests/docker.test.js +0 -46
- package/tests/file_operations.test.js +0 -57
- package/tests/gmail.test.js +0 -135
- package/tests/gmail_auth.test.js +0 -129
- package/tests/google_calendar.test.js +0 -113
- package/tests/google_tts_urls.test.js +0 -24
- package/tests/memory_store.test.js +0 -185
- package/tests/notion.test.js +0 -121
- package/tests/provider_routing.test.js +0 -83
- package/tests/safety_manager.test.js +0 -40
- package/tests/spotify.test.js +0 -201
- package/tests/system_monitor.test.js +0 -37
- package/tests/updater.test.js +0 -32
- package/tests/workspace_manager.test.js +0 -56
package/src/UI/renderer.js
CHANGED
|
@@ -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 :
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
496
|
+
fallbackSpeak(text, wrappedOnEnd, resolve);
|
|
459
497
|
});
|
|
460
498
|
}
|
|
461
499
|
|
|
@@ -566,6 +604,95 @@ function formatProviderInfo(providerInfo) {
|
|
|
566
604
|
return model ? `${provider || 'AI'} • ${model}` : provider;
|
|
567
605
|
}
|
|
568
606
|
|
|
607
|
+
function splitListOutro(text) {
|
|
608
|
+
const value = String(text || '').trim();
|
|
609
|
+
const markers = [
|
|
610
|
+
' คุณภีมอยาก',
|
|
611
|
+
' อยากให้',
|
|
612
|
+
' อยากดู',
|
|
613
|
+
' บอกมิ้นท์',
|
|
614
|
+
' Would you',
|
|
615
|
+
' Do you want',
|
|
616
|
+
' Tell me'
|
|
617
|
+
];
|
|
618
|
+
|
|
619
|
+
for (const marker of markers) {
|
|
620
|
+
const index = value.indexOf(marker);
|
|
621
|
+
if (index > 60) {
|
|
622
|
+
return {
|
|
623
|
+
main: value.slice(0, index).trim(),
|
|
624
|
+
outro: value.slice(index).trim()
|
|
625
|
+
};
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
return { main: value, outro: '' };
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
function buildAiTextBlocks(text) {
|
|
633
|
+
const normalized = normalizeAiText(text).replace(/\r\n/g, '\n').trim();
|
|
634
|
+
if (!normalized) return [];
|
|
635
|
+
|
|
636
|
+
const readable = normalized
|
|
637
|
+
.replace(/\s+(\d+)[.)]\s+/g, '\n$1. ')
|
|
638
|
+
.replace(/\n{3,}/g, '\n\n');
|
|
639
|
+
|
|
640
|
+
const blocks = [];
|
|
641
|
+
const lines = readable.split(/\n+/).map(line => line.trim()).filter(Boolean);
|
|
642
|
+
|
|
643
|
+
for (const line of lines) {
|
|
644
|
+
const numbered = line.match(/^\d+[.)]\s+(.+)$/);
|
|
645
|
+
const bullet = line.match(/^[-*•]\s+(.+)$/);
|
|
646
|
+
|
|
647
|
+
if (numbered || bullet) {
|
|
648
|
+
const content = numbered ? numbered[1] : bullet[1];
|
|
649
|
+
const { main, outro } = splitListOutro(content);
|
|
650
|
+
blocks.push({ type: 'bullet', text: main });
|
|
651
|
+
if (outro) blocks.push({ type: 'paragraph', text: outro });
|
|
652
|
+
} else {
|
|
653
|
+
blocks.push({ type: 'paragraph', text: line });
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return blocks;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
function appendFormattedMessageText(bubble, text, sender) {
|
|
661
|
+
if (sender !== 'ai') {
|
|
662
|
+
const textSpan = document.createElement('span');
|
|
663
|
+
textSpan.textContent = text;
|
|
664
|
+
bubble.appendChild(textSpan);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const blocks = buildAiTextBlocks(text);
|
|
669
|
+
if (blocks.length === 0) return;
|
|
670
|
+
|
|
671
|
+
const wrapper = document.createElement('div');
|
|
672
|
+
wrapper.classList.add('formatted-ai-text');
|
|
673
|
+
|
|
674
|
+
for (const block of blocks) {
|
|
675
|
+
const item = document.createElement(block.type === 'bullet' ? 'div' : 'p');
|
|
676
|
+
item.classList.add(block.type === 'bullet' ? 'ai-list-item' : 'ai-paragraph');
|
|
677
|
+
|
|
678
|
+
if (block.type === 'bullet') {
|
|
679
|
+
const bullet = document.createElement('span');
|
|
680
|
+
bullet.classList.add('ai-list-bullet');
|
|
681
|
+
bullet.textContent = '•';
|
|
682
|
+
const content = document.createElement('span');
|
|
683
|
+
content.textContent = block.text;
|
|
684
|
+
item.appendChild(bullet);
|
|
685
|
+
item.appendChild(content);
|
|
686
|
+
} else {
|
|
687
|
+
item.textContent = block.text;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
wrapper.appendChild(item);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
bubble.appendChild(wrapper);
|
|
694
|
+
}
|
|
695
|
+
|
|
569
696
|
function appendMessage(text, sender, base64Image = null, timestamp = null, options = {}) {
|
|
570
697
|
const messageDiv = document.createElement('div');
|
|
571
698
|
messageDiv.classList.add('message', `${sender}-message`);
|
|
@@ -587,9 +714,7 @@ function appendMessage(text, sender, base64Image = null, timestamp = null, optio
|
|
|
587
714
|
}
|
|
588
715
|
|
|
589
716
|
if (text) {
|
|
590
|
-
|
|
591
|
-
textSpan.textContent = text;
|
|
592
|
-
bubble.appendChild(textSpan);
|
|
717
|
+
appendFormattedMessageText(bubble, text, sender);
|
|
593
718
|
}
|
|
594
719
|
|
|
595
720
|
bubbleWrapper.appendChild(bubble);
|
|
@@ -635,6 +760,9 @@ function normalizeAiText(input) {
|
|
|
635
760
|
function splitAiMessages(text) {
|
|
636
761
|
const normalized = normalizeAiText(text).trim();
|
|
637
762
|
if (!normalized) return [];
|
|
763
|
+
if (/(^|\s)\d+[.)]\s+/.test(normalized) || /(^|\n)\s*[-*•]\s+/.test(normalized)) {
|
|
764
|
+
return [normalized];
|
|
765
|
+
}
|
|
638
766
|
const byBlankLine = normalized
|
|
639
767
|
.split(/\n\s*\n/)
|
|
640
768
|
.map((part) => part.trim())
|
|
@@ -756,14 +884,52 @@ function scrollToBottom() {
|
|
|
756
884
|
chatContainer.scrollTop = chatContainer.scrollHeight;
|
|
757
885
|
}
|
|
758
886
|
|
|
887
|
+
function loadScript(src) {
|
|
888
|
+
return new Promise((resolve, reject) => {
|
|
889
|
+
if (document.querySelector(`script[src="${src}"]`)) {
|
|
890
|
+
resolve();
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
const script = document.createElement('script');
|
|
894
|
+
script.src = src;
|
|
895
|
+
script.onload = resolve;
|
|
896
|
+
script.onerror = () => reject(new Error(`Failed to load script: ${src}`));
|
|
897
|
+
document.body.appendChild(script);
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
async function loadLive2DWhenIdle() {
|
|
902
|
+
if (!modelMount || window.Live2DManager) return;
|
|
903
|
+
try {
|
|
904
|
+
await loadScript('../../node_modules/@hazart-pkg/live2d-core/live2dcubismcore.min.js');
|
|
905
|
+
await loadScript('../../node_modules/pixi.js/dist/browser/pixi.min.js');
|
|
906
|
+
await loadScript('../../node_modules/pixi-live2d-display/dist/cubism4.min.js');
|
|
907
|
+
await loadScript('live2d_manager.js');
|
|
908
|
+
if (window.Live2DManager) {
|
|
909
|
+
await Live2DManager.loadModel(modelMount, modelStatus, modelShell);
|
|
910
|
+
}
|
|
911
|
+
} catch (err) {
|
|
912
|
+
console.error('[Live2D] Deferred load failed:', err);
|
|
913
|
+
if (modelStatus) {
|
|
914
|
+
modelStatus.classList.add('is-error');
|
|
915
|
+
modelStatus.textContent = 'Live2D model unavailable.';
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
759
920
|
async function loadChatHistory() {
|
|
760
921
|
try {
|
|
761
922
|
const history = await window.api.getChatHistory();
|
|
923
|
+
const initial = chatContainer.querySelector('.message.initial');
|
|
924
|
+
|
|
762
925
|
if (!Array.isArray(history) || history.length === 0) {
|
|
926
|
+
if (initial) {
|
|
927
|
+
initial.style.display = 'flex';
|
|
928
|
+
initial.style.opacity = '1';
|
|
929
|
+
}
|
|
763
930
|
return;
|
|
764
931
|
}
|
|
765
932
|
|
|
766
|
-
const initial = chatContainer.querySelector('.message.initial');
|
|
767
933
|
if (initial) {
|
|
768
934
|
initial.remove();
|
|
769
935
|
}
|
|
@@ -771,11 +937,12 @@ async function loadChatHistory() {
|
|
|
771
937
|
for (const item of history) {
|
|
772
938
|
if (!item || typeof item.text !== 'string' || !item.text.trim()) continue;
|
|
773
939
|
const sender = item.sender === 'user' ? 'user' : 'ai';
|
|
774
|
-
if (sender === '
|
|
775
|
-
|
|
776
|
-
} else {
|
|
777
|
-
appendMessage(item.text, sender, null, item.timestamp);
|
|
940
|
+
if (sender === 'user' && !String(item.text).startsWith('Model interaction:')) {
|
|
941
|
+
rememberConversationLanguage(item.text);
|
|
778
942
|
}
|
|
943
|
+
appendMessage(item.text, sender, null, item.timestamp, {
|
|
944
|
+
providerInfo: sender === 'ai' ? item.providerInfo : null
|
|
945
|
+
});
|
|
779
946
|
}
|
|
780
947
|
} catch (error) {
|
|
781
948
|
console.error('Failed to load chat history:', error);
|
|
@@ -785,23 +952,31 @@ async function loadChatHistory() {
|
|
|
785
952
|
async function sendTextMessage(text, options = {}) {
|
|
786
953
|
const cleanText = (text || '').trim();
|
|
787
954
|
const allowSmartContext = options.allowSmartContext !== false;
|
|
955
|
+
const includePendingImage = options.includePendingImage !== false;
|
|
956
|
+
const displayText = options.displayText !== undefined ? options.displayText : cleanText;
|
|
957
|
+
const trackLanguage = options.trackLanguage !== false;
|
|
788
958
|
|
|
789
959
|
// We can send either a text message, an image, or both.
|
|
790
|
-
if (!cleanText && !currentBase64Image) return;
|
|
960
|
+
if (!cleanText && (!includePendingImage || !currentBase64Image)) return;
|
|
791
961
|
|
|
792
962
|
// Cache the image for sending and UI, then clear
|
|
793
|
-
let imageToSend = currentBase64Image;
|
|
963
|
+
let imageToSend = includePendingImage ? currentBase64Image : null;
|
|
794
964
|
|
|
795
965
|
// Clear input & UI for explicit images
|
|
796
966
|
chatInput.value = '';
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
967
|
+
if (includePendingImage) {
|
|
968
|
+
currentBase64Image = null;
|
|
969
|
+
imagePreviewContainer.style.display = 'none';
|
|
970
|
+
imagePreview.src = '';
|
|
971
|
+
}
|
|
800
972
|
|
|
801
973
|
const now = new Date().toISOString();
|
|
802
974
|
|
|
803
975
|
// Show user message (with explicit image if available)
|
|
804
|
-
appendMessage(
|
|
976
|
+
appendMessage(displayText, 'user', imageToSend, now);
|
|
977
|
+
if (trackLanguage) {
|
|
978
|
+
rememberConversationLanguage(displayText || cleanText);
|
|
979
|
+
}
|
|
805
980
|
|
|
806
981
|
// Show typing early so user knows we are processing
|
|
807
982
|
showTyping();
|
|
@@ -846,7 +1021,9 @@ async function sendTextMessage(text, options = {}) {
|
|
|
846
1021
|
} else {
|
|
847
1022
|
// General system info (date, time, RAM, CPU)
|
|
848
1023
|
const info = await window.api.getSystemInfo();
|
|
849
|
-
|
|
1024
|
+
const machine = info.machine && info.machine.display ? `\n🖥️ รุ่นเครื่อง: ${info.machine.display}` : '';
|
|
1025
|
+
const distro = info.distro ? `\nระบบ: ${info.distro}` : '';
|
|
1026
|
+
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})`;
|
|
850
1027
|
}
|
|
851
1028
|
}
|
|
852
1029
|
|
|
@@ -880,6 +1057,20 @@ chatForm.addEventListener('submit', throttle(async (e) => {
|
|
|
880
1057
|
await sendTextMessage(text);
|
|
881
1058
|
}, 500));
|
|
882
1059
|
|
|
1060
|
+
window.addEventListener('live2d-model-interaction', async (event) => {
|
|
1061
|
+
const prompt = event?.detail?.prompt;
|
|
1062
|
+
if (!prompt) return;
|
|
1063
|
+
if (window.api && window.api.setAiState) window.api.setAiState('thinking');
|
|
1064
|
+
const interactionPrompt = `${prompt}\n\n${buildInteractionLanguageInstruction()}`;
|
|
1065
|
+
const displayPrefix = lastConversationLanguage === 'thai' ? 'แตะโมเดล' : 'Model interaction';
|
|
1066
|
+
await sendTextMessage(interactionPrompt, {
|
|
1067
|
+
allowSmartContext: false,
|
|
1068
|
+
includePendingImage: false,
|
|
1069
|
+
trackLanguage: false,
|
|
1070
|
+
displayText: `${displayPrefix}: ${event.detail.label || event.detail.region || 'Interaction'}`
|
|
1071
|
+
});
|
|
1072
|
+
});
|
|
1073
|
+
|
|
883
1074
|
// --- Image Paste and Drag-n-Drop Support ---
|
|
884
1075
|
function handleImageFile(file) {
|
|
885
1076
|
if (!file || !file.type.startsWith('image/')) return;
|
|
@@ -936,6 +1127,8 @@ window.addEventListener('DOMContentLoaded', async () => {
|
|
|
936
1127
|
chatInput.focus();
|
|
937
1128
|
await loadTheme();
|
|
938
1129
|
await loadChatHistory();
|
|
1130
|
+
const scheduleLive2DLoad = window.requestIdleCallback || ((callback) => setTimeout(callback, 750));
|
|
1131
|
+
scheduleLive2DLoad(() => loadLive2DWhenIdle());
|
|
939
1132
|
});
|
|
940
1133
|
|
|
941
1134
|
// Proactive OS Notifications (Battery, Network, etc.)
|
|
@@ -1028,6 +1221,131 @@ if (smartContextToggle) {
|
|
|
1028
1221
|
});
|
|
1029
1222
|
}
|
|
1030
1223
|
|
|
1224
|
+
// Toggle Live2D Model visibility
|
|
1225
|
+
const toggleModelBtn = document.getElementById('toggle-model-btn');
|
|
1226
|
+
const assistantWorkspace = document.querySelector('.assistant-workspace');
|
|
1227
|
+
|
|
1228
|
+
if (toggleModelBtn && assistantWorkspace) {
|
|
1229
|
+
toggleModelBtn.addEventListener('click', () => {
|
|
1230
|
+
const isHidden = assistantWorkspace.classList.toggle('model-hidden');
|
|
1231
|
+
toggleModelBtn.classList.toggle('active', isHidden);
|
|
1232
|
+
|
|
1233
|
+
// Save preference to local storage
|
|
1234
|
+
localStorage.setItem('mint-model-hidden', String(isHidden));
|
|
1235
|
+
|
|
1236
|
+
// Refit model if shown
|
|
1237
|
+
if (!isHidden && window.Live2DManager && Live2DManager.model) {
|
|
1238
|
+
// Wait for transition
|
|
1239
|
+
setTimeout(() => {
|
|
1240
|
+
// Trigger a resize event to refit
|
|
1241
|
+
window.dispatchEvent(new Event('resize'));
|
|
1242
|
+
}, 450);
|
|
1243
|
+
}
|
|
1244
|
+
});
|
|
1245
|
+
|
|
1246
|
+
// Restore preference on load
|
|
1247
|
+
const savedModelHidden = localStorage.getItem('mint-model-hidden');
|
|
1248
|
+
const savedHidden = savedModelHidden === null || savedModelHidden === 'true';
|
|
1249
|
+
if (savedHidden) {
|
|
1250
|
+
assistantWorkspace.classList.add('model-hidden');
|
|
1251
|
+
toggleModelBtn.classList.add('active');
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
// Cycle Shiroko's Expression
|
|
1256
|
+
const changeExpressionBtn = document.getElementById('change-expression-btn');
|
|
1257
|
+
if (changeExpressionBtn) {
|
|
1258
|
+
changeExpressionBtn.addEventListener('click', () => {
|
|
1259
|
+
if (window.Live2DManager) {
|
|
1260
|
+
Live2DManager.cycleExpression();
|
|
1261
|
+
}
|
|
1262
|
+
});
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
// Cycle Live2D accessories
|
|
1266
|
+
const accessoryStorageKey = 'mint-live2d-accessories';
|
|
1267
|
+
const accessoryCycleBtn = document.getElementById('accessory-cycle-btn');
|
|
1268
|
+
const accessoryCycleLabel = document.getElementById('accessory-cycle-label');
|
|
1269
|
+
const accessoryCycleOrder = [null, 'glasses', 'pen', 'cat'];
|
|
1270
|
+
const accessoryLabels = {
|
|
1271
|
+
glasses: 'Glasses',
|
|
1272
|
+
pen: 'Pen',
|
|
1273
|
+
cat: 'Cat'
|
|
1274
|
+
};
|
|
1275
|
+
let savedAccessories = {};
|
|
1276
|
+
try {
|
|
1277
|
+
savedAccessories = JSON.parse(localStorage.getItem(accessoryStorageKey) || '{}') || {};
|
|
1278
|
+
} catch (_) {
|
|
1279
|
+
savedAccessories = {};
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
const getSavedAccessoryId = () => accessoryCycleOrder.find(id => id && savedAccessories[id] === true) || null;
|
|
1283
|
+
|
|
1284
|
+
function updateAccessoryCycleButton(accessoryId) {
|
|
1285
|
+
if (!accessoryCycleBtn) return;
|
|
1286
|
+
const isActive = Boolean(accessoryId);
|
|
1287
|
+
const label = accessoryId ? accessoryLabels[accessoryId] : 'Accessory';
|
|
1288
|
+
accessoryCycleBtn.classList.toggle('active', isActive);
|
|
1289
|
+
accessoryCycleBtn.setAttribute('aria-pressed', String(isActive));
|
|
1290
|
+
accessoryCycleBtn.title = `Accessory: ${label}`;
|
|
1291
|
+
if (accessoryCycleLabel) accessoryCycleLabel.textContent = label;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
let currentAccessoryId = getSavedAccessoryId();
|
|
1295
|
+
updateAccessoryCycleButton(currentAccessoryId);
|
|
1296
|
+
|
|
1297
|
+
if (accessoryCycleBtn) {
|
|
1298
|
+
accessoryCycleBtn.addEventListener('click', () => {
|
|
1299
|
+
const currentIndex = accessoryCycleOrder.indexOf(currentAccessoryId);
|
|
1300
|
+
currentAccessoryId = accessoryCycleOrder[(currentIndex + 1) % accessoryCycleOrder.length];
|
|
1301
|
+
updateAccessoryCycleButton(currentAccessoryId);
|
|
1302
|
+
|
|
1303
|
+
if (window.Live2DManager) {
|
|
1304
|
+
Live2DManager.setExclusiveAccessory(currentAccessoryId, true);
|
|
1305
|
+
} else {
|
|
1306
|
+
savedAccessories = {};
|
|
1307
|
+
if (currentAccessoryId) savedAccessories[currentAccessoryId] = true;
|
|
1308
|
+
localStorage.setItem(accessoryStorageKey, JSON.stringify(savedAccessories));
|
|
1309
|
+
}
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
// Toggle Live2D model interaction
|
|
1314
|
+
const toggleInteractionBtn = document.getElementById('toggle-interaction-btn');
|
|
1315
|
+
if (toggleInteractionBtn) {
|
|
1316
|
+
const savedInteractionEnabled = localStorage.getItem('mint-model-interaction-enabled') !== 'false';
|
|
1317
|
+
toggleInteractionBtn.classList.toggle('active', savedInteractionEnabled);
|
|
1318
|
+
toggleInteractionBtn.setAttribute('aria-pressed', String(savedInteractionEnabled));
|
|
1319
|
+
if (window.Live2DManager) {
|
|
1320
|
+
Live2DManager.setInteractionEnabled(savedInteractionEnabled);
|
|
1321
|
+
}
|
|
1322
|
+
|
|
1323
|
+
toggleInteractionBtn.addEventListener('click', () => {
|
|
1324
|
+
const isEnabled = !toggleInteractionBtn.classList.contains('active');
|
|
1325
|
+
toggleInteractionBtn.classList.toggle('active', isEnabled);
|
|
1326
|
+
toggleInteractionBtn.setAttribute('aria-pressed', String(isEnabled));
|
|
1327
|
+
if (window.Live2DManager) {
|
|
1328
|
+
Live2DManager.setInteractionEnabled(isEnabled, true);
|
|
1329
|
+
} else {
|
|
1330
|
+
localStorage.setItem('mint-model-interaction-enabled', String(isEnabled));
|
|
1331
|
+
}
|
|
1332
|
+
});
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// Toggle Live2D interaction area guide
|
|
1336
|
+
const interactionGuideBtn = document.getElementById('interaction-guide-btn');
|
|
1337
|
+
if (interactionGuideBtn && modelShell) {
|
|
1338
|
+
const savedGuideVisible = localStorage.getItem('mint-interaction-guide-visible') === 'true';
|
|
1339
|
+
modelShell.classList.toggle('show-interaction-guide', savedGuideVisible);
|
|
1340
|
+
interactionGuideBtn.classList.toggle('active', savedGuideVisible);
|
|
1341
|
+
|
|
1342
|
+
interactionGuideBtn.addEventListener('click', () => {
|
|
1343
|
+
const isVisible = modelShell.classList.toggle('show-interaction-guide');
|
|
1344
|
+
interactionGuideBtn.classList.toggle('active', isVisible);
|
|
1345
|
+
localStorage.setItem('mint-interaction-guide-visible', String(isVisible));
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1031
1349
|
// Spotlight integration
|
|
1032
1350
|
window.api.onSpotlightToChat((query) => {
|
|
1033
1351
|
chatInput.value = query;
|