@pheem49/mint 1.3.0 → 1.4.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.
- package/README.md +28 -24
- package/mint-cli.js +201 -26
- package/package.json +13 -2
- package/src/AI_Brain/Gemini_API.js +299 -46
- package/src/AI_Brain/agent_orchestrator.js +73 -0
- package/src/AI_Brain/autonomous_brain.js +2 -0
- package/src/AI_Brain/memory_store.js +318 -0
- package/src/CLI/chat_router.js +17 -2
- package/src/CLI/chat_ui.js +83 -1
- package/src/CLI/code_agent.js +143 -30
- package/src/CLI/onboarding.js +53 -6
- package/src/CLI/workspace_manager.js +81 -0
- package/src/Plugins/spotify.js +168 -40
- package/src/Plugins/system_monitor.js +72 -0
- package/src/System/config_manager.js +35 -2
- package/src/System/notifications.js +23 -0
- package/src/UI/settings.html +143 -65
- package/src/UI/settings.js +155 -41
- package/tests/agent_orchestrator.test.js +41 -0
- package/tests/config_manager.test.js +141 -0
- package/tests/memory_store.test.js +185 -0
- package/tests/spotify.test.js +201 -0
- package/tests/system_monitor.test.js +37 -0
- package/tests/workspace_manager.test.js +41 -0
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
const { GoogleGenAI } = require('@google/genai');
|
|
2
2
|
const { readChatHistory, writeChatHistory, clearChatHistory } = require('../System/chat_history_manager');
|
|
3
|
-
const { readConfig } = require('../System/config_manager');
|
|
3
|
+
const { readConfig, getAvailableProviders } = require('../System/config_manager');
|
|
4
4
|
const pluginManager = require('../Plugins/plugin_manager');
|
|
5
5
|
const mcpManager = require('../Plugins/mcp_manager');
|
|
6
|
+
const memoryStore = require('./memory_store');
|
|
7
|
+
const agentOrchestrator = require('./agent_orchestrator');
|
|
8
|
+
const workspaceManager = require('../CLI/workspace_manager');
|
|
6
9
|
|
|
7
10
|
let ai = null;
|
|
8
11
|
let activeApiKey = '';
|
|
@@ -92,6 +95,41 @@ Input: "อากาศวันนี้เป็นยังไง" or "What's
|
|
|
92
95
|
Output: { "response": "มิ้นท์ไปดูอากาศให้เลยนะคะ", "action": { "type": "system_info", "target": "Bangkok" } }
|
|
93
96
|
`;
|
|
94
97
|
|
|
98
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
99
|
+
// buildSystemPrompt() — single source of truth for all provider system prompts
|
|
100
|
+
// Replaces 5 previously duplicated mcpPrompt blocks.
|
|
101
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
102
|
+
function buildSystemPrompt() {
|
|
103
|
+
pluginManager.loadPlugins();
|
|
104
|
+
const mcpTools = mcpManager.getAllTools();
|
|
105
|
+
|
|
106
|
+
let mcpSection = '\n\nAVAILABLE MCP TOOLS (Model Context Protocol):\n';
|
|
107
|
+
if (mcpTools.length > 0) {
|
|
108
|
+
mcpTools.forEach(tool => {
|
|
109
|
+
mcpSection += `- Server: ${tool.serverName}, Tool: ${tool.name}\n Desc: ${tool.description}\n Args: ${JSON.stringify(tool.inputSchema.properties)}\n`;
|
|
110
|
+
});
|
|
111
|
+
mcpSection += "\nTo use these tools, use action type 'mcp_tool', specify the 'server' name, set 'target' to the tool name, and provide 'args'.\n";
|
|
112
|
+
} else {
|
|
113
|
+
mcpSection += 'No MCP tools currently connected.\n';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Inject long-term user context (non-blocking read from SQLite)
|
|
117
|
+
const userContext = memoryStore.getUserContext();
|
|
118
|
+
|
|
119
|
+
// Get current specialized persona instruction
|
|
120
|
+
const agent = agentOrchestrator.getCurrentAgent();
|
|
121
|
+
const personaInstruction = `\n\n[CURRENT PERSONA: ${agent.name}]\n${agent.instruction}\n`;
|
|
122
|
+
|
|
123
|
+
// Inject Workspace Context if available
|
|
124
|
+
let workspaceSection = "";
|
|
125
|
+
const ws = workspaceManager.getWorkspaceByPath(process.cwd());
|
|
126
|
+
if (ws) {
|
|
127
|
+
workspaceSection = `\n\n[WORKSPACE DETECTED: ${ws.name}]\nPath: ${ws.path}\nProject Instructions: ${ws.instructions}\n`;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return systemInstruction + personaInstruction + workspaceSection + pluginManager.getPromptDescriptions() + mcpSection + userContext;
|
|
131
|
+
}
|
|
132
|
+
|
|
95
133
|
function resolveApiKey() {
|
|
96
134
|
let settingsKey = '';
|
|
97
135
|
try {
|
|
@@ -134,22 +172,6 @@ let lastLoggedModel = '';
|
|
|
134
172
|
const MAX_HISTORY_MESSAGES = 20; // Keep only the last 20 messages (approx 10 turns)
|
|
135
173
|
|
|
136
174
|
function createChat(history = []) {
|
|
137
|
-
// Load plugins and get dynamic description for the prompt
|
|
138
|
-
pluginManager.loadPlugins();
|
|
139
|
-
// Inject MCP Tools
|
|
140
|
-
const mcpTools = mcpManager.getAllTools();
|
|
141
|
-
let mcpPrompt = "\n\nAVAILABLE MCP TOOLS (Model Context Protocol):\n";
|
|
142
|
-
if (mcpTools.length > 0) {
|
|
143
|
-
mcpTools.forEach(tool => {
|
|
144
|
-
mcpPrompt += `- Server: ${tool.serverName}, Tool: ${tool.name}\n Desc: ${tool.description}\n Args: ${JSON.stringify(tool.inputSchema.properties)}\n`;
|
|
145
|
-
});
|
|
146
|
-
mcpPrompt += "\nTo use these tools, use action type 'mcp_tool', specify the 'server' name, set 'target' to the tool name, and provide 'args'.\n";
|
|
147
|
-
} else {
|
|
148
|
-
mcpPrompt += "No MCP tools currently connected.\n";
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const dynamicPrompt = systemInstruction + pluginManager.getPromptDescriptions() + mcpPrompt;
|
|
152
|
-
|
|
153
175
|
// Truncate history and strip custom fields like 'timestamp' before passing to SDK
|
|
154
176
|
const cleanedHistory = (history || []).map(msg => ({
|
|
155
177
|
role: msg.role,
|
|
@@ -159,13 +181,12 @@ function createChat(history = []) {
|
|
|
159
181
|
|
|
160
182
|
activeModel = resolveGeminiModel();
|
|
161
183
|
if (activeModel && activeModel !== lastLoggedModel) {
|
|
162
|
-
// console.log(`[Gemini] Using model: ${activeModel}`);
|
|
163
184
|
lastLoggedModel = activeModel;
|
|
164
185
|
}
|
|
165
186
|
chat = ai.chats.create({
|
|
166
187
|
model: activeModel,
|
|
167
188
|
config: {
|
|
168
|
-
systemInstruction:
|
|
189
|
+
systemInstruction: buildSystemPrompt(),
|
|
169
190
|
responseMimeType: "application/json"
|
|
170
191
|
},
|
|
171
192
|
history: truncatedHistory
|
|
@@ -224,18 +245,57 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
|
|
|
224
245
|
}
|
|
225
246
|
}
|
|
226
247
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
}
|
|
248
|
+
const { getAvailableProviders } = require('../System/config_manager');
|
|
249
|
+
const availableProviders = getAvailableProviders(config);
|
|
230
250
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
251
|
+
// Ensure the requested provider is prioritized. If not available, fallback to the first available.
|
|
252
|
+
let providersToTry = [provider];
|
|
253
|
+
const alternates = availableProviders.filter(p => p !== provider);
|
|
254
|
+
providersToTry = providersToTry.concat(alternates);
|
|
255
|
+
|
|
256
|
+
for (let i = 0; i < providersToTry.length; i++) {
|
|
257
|
+
const currentProv = providersToTry[i];
|
|
258
|
+
try {
|
|
259
|
+
if (currentProv === 'ollama') {
|
|
260
|
+
return await handleOllamaChat(finalMessage, base64Image, base64Audio, config);
|
|
261
|
+
}
|
|
262
|
+
if (currentProv === 'anthropic') {
|
|
263
|
+
return await handleAnthropicChat(finalMessage, base64Image, config);
|
|
264
|
+
}
|
|
265
|
+
if (currentProv === 'openai') {
|
|
266
|
+
return await handleOpenAIChat(finalMessage, base64Image, config);
|
|
267
|
+
}
|
|
268
|
+
if (currentProv === 'local_openai') {
|
|
269
|
+
return await handleLocalOpenAIChat(finalMessage, base64Image, config);
|
|
270
|
+
}
|
|
271
|
+
if (currentProv === 'huggingface') {
|
|
272
|
+
return await handleHuggingFaceChat(finalMessage, base64Image, config);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return await handleGeminiChat(finalMessage, base64Image, base64Audio);
|
|
276
|
+
} catch (error) {
|
|
277
|
+
console.error(`[Fallback System] Provider '${currentProv}' failed:`, error.message);
|
|
278
|
+
if (i === providersToTry.length - 1) {
|
|
279
|
+
console.error("[Fallback System] All available providers failed.");
|
|
280
|
+
throw error; // No more providers to fallback to
|
|
281
|
+
}
|
|
282
|
+
console.log(`[Fallback System] Switching to next available provider: '${providersToTry[i+1]}'`);
|
|
283
|
+
// Continue the loop to try the next provider
|
|
284
|
+
}
|
|
237
285
|
}
|
|
286
|
+
} catch (globalError) {
|
|
287
|
+
console.error("handleChat error:", globalError);
|
|
288
|
+
throw globalError;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
238
291
|
|
|
292
|
+
async function handleGeminiChat(finalMessage, base64Image, base64Audio) {
|
|
293
|
+
try {
|
|
294
|
+
// 1. Check cache first for text-only messages
|
|
295
|
+
if (finalMessage && !base64Image && !base64Audio) {
|
|
296
|
+
const cached = memoryStore.getCachedResponse(finalMessage);
|
|
297
|
+
if (cached) return cached;
|
|
298
|
+
}
|
|
239
299
|
|
|
240
300
|
const desiredModel = resolveGeminiModel();
|
|
241
301
|
if (!chat || activeModel !== desiredModel) {
|
|
@@ -318,7 +378,7 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
|
|
|
318
378
|
}
|
|
319
379
|
}
|
|
320
380
|
|
|
321
|
-
//
|
|
381
|
+
// Decode any remaining unicode escapes in the response text
|
|
322
382
|
if (parsedResult && typeof parsedResult.response === 'string') {
|
|
323
383
|
parsedResult.response = decodeUnicode(parsedResult.response);
|
|
324
384
|
}
|
|
@@ -326,6 +386,17 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
|
|
|
326
386
|
// Attach timestamp to the result
|
|
327
387
|
parsedResult.timestamp = now;
|
|
328
388
|
|
|
389
|
+
// Record interaction for long-term memory (non-blocking)
|
|
390
|
+
if (finalMessage && parsedResult.response) {
|
|
391
|
+
setImmediate(() => {
|
|
392
|
+
memoryStore.recordInteraction(finalMessage, parsedResult.response);
|
|
393
|
+
// Cache text-only responses
|
|
394
|
+
if (!base64Image && !base64Audio) {
|
|
395
|
+
memoryStore.cacheResponse(finalMessage, parsedResult);
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
329
400
|
return parsedResult;
|
|
330
401
|
|
|
331
402
|
} catch (error) {
|
|
@@ -334,18 +405,115 @@ async function handleChat(message, base64Image = null, base64Audio = null) {
|
|
|
334
405
|
}
|
|
335
406
|
}
|
|
336
407
|
|
|
408
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
409
|
+
// handleGeminiChatStream() — Streaming async generator (CLI only)
|
|
410
|
+
// Yields: { chunk: string } during streaming
|
|
411
|
+
// { done: true, parsed: object, timestamp: string } when complete
|
|
412
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
413
|
+
async function* handleGeminiChatStream(finalMessage, base64Image, base64Audio) {
|
|
414
|
+
try {
|
|
415
|
+
// 1. Check cache first
|
|
416
|
+
if (finalMessage && !base64Image && !base64Audio) {
|
|
417
|
+
const cached = memoryStore.getCachedResponse(finalMessage);
|
|
418
|
+
if (cached) {
|
|
419
|
+
yield { chunk: `{"response":"${cached.response.replace(/"/g, '\\"')}", "action": {"type":"none"}}` };
|
|
420
|
+
yield { done: true, parsed: cached, timestamp: cached.timestamp || new Date().toISOString() };
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const desiredModel = resolveGeminiModel();
|
|
426
|
+
if (!chat || activeModel !== desiredModel) {
|
|
427
|
+
createChat(readChatHistory());
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const parts = [];
|
|
431
|
+
if (finalMessage) {
|
|
432
|
+
parts.push({ text: finalMessage });
|
|
433
|
+
} else if (base64Audio && !base64Image) {
|
|
434
|
+
parts.push({ text: "Please listen to this voice command and respond in Thai with the appropriate JSON action if needed." });
|
|
435
|
+
} else if (!base64Image && !base64Audio) {
|
|
436
|
+
parts.push({ text: "Analyze this input." });
|
|
437
|
+
}
|
|
438
|
+
if (base64Image) {
|
|
439
|
+
const base64Data = base64Image.replace(/^data:image\/\w+;base64,/, '');
|
|
440
|
+
parts.push({ inlineData: { mimeType: "image/png", data: base64Data } });
|
|
441
|
+
}
|
|
442
|
+
if (base64Audio) {
|
|
443
|
+
let mimeType = "audio/webm";
|
|
444
|
+
const mimeMatch = base64Audio.match(/^data:(audio\/\w+);base64,/);
|
|
445
|
+
if (mimeMatch) mimeType = mimeMatch[1];
|
|
446
|
+
const base64Data = base64Audio.replace(/^data:audio\/\w+;base64,/, '');
|
|
447
|
+
parts.push({ inlineData: { mimeType, data: base64Data } });
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const stream = await chat.sendMessageStream({ message: parts });
|
|
451
|
+
let fullText = '';
|
|
452
|
+
|
|
453
|
+
for await (const chunk of stream) {
|
|
454
|
+
let chunkText = '';
|
|
455
|
+
try {
|
|
456
|
+
chunkText = (typeof chunk.text === 'function') ? chunk.text() : (chunk.text || '');
|
|
457
|
+
} catch (_) {}
|
|
458
|
+
if (chunkText) {
|
|
459
|
+
fullText += chunkText;
|
|
460
|
+
yield { chunk: chunkText };
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Save history
|
|
465
|
+
const history = await chat.getHistory();
|
|
466
|
+
const now = new Date().toISOString();
|
|
467
|
+
if (history.length >= 2) {
|
|
468
|
+
const modelMsg = history[history.length - 1];
|
|
469
|
+
const userMsg = history[history.length - 2];
|
|
470
|
+
if (!modelMsg.timestamp) modelMsg.timestamp = now;
|
|
471
|
+
if (!userMsg.timestamp) userMsg.timestamp = now;
|
|
472
|
+
}
|
|
473
|
+
writeChatHistory(history);
|
|
474
|
+
|
|
475
|
+
// Parse complete JSON response
|
|
476
|
+
let parsedResult;
|
|
477
|
+
try {
|
|
478
|
+
parsedResult = JSON.parse(fullText);
|
|
479
|
+
} catch (_) {
|
|
480
|
+
const jsonMatch = fullText.match(/```json\n([\s\S]*?)\n```/) || fullText.match(/\{[\s\S]*\}/);
|
|
481
|
+
if (jsonMatch) {
|
|
482
|
+
parsedResult = JSON.parse(jsonMatch[jsonMatch.length > 1 ? 1 : 0]);
|
|
483
|
+
} else {
|
|
484
|
+
parsedResult = { response: fullText, action: { type: 'none', target: '' } };
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
if (parsedResult && typeof parsedResult.response === 'string') {
|
|
488
|
+
parsedResult.response = decodeUnicode(parsedResult.response);
|
|
489
|
+
}
|
|
490
|
+
parsedResult.timestamp = now;
|
|
491
|
+
|
|
492
|
+
// Record for long-term memory
|
|
493
|
+
if (finalMessage && parsedResult.response) {
|
|
494
|
+
setImmediate(() => {
|
|
495
|
+
memoryStore.recordInteraction(finalMessage, parsedResult.response);
|
|
496
|
+
// Cache text-only responses
|
|
497
|
+
if (!base64Image && !base64Audio) {
|
|
498
|
+
memoryStore.cacheResponse(finalMessage, parsedResult);
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
yield { done: true, parsed: parsedResult, timestamp: now };
|
|
504
|
+
|
|
505
|
+
} catch (error) {
|
|
506
|
+
console.error('[Stream] Gemini stream error:', error);
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
337
511
|
async function handleAnthropicChat(finalMessage, base64Image, config) {
|
|
338
512
|
const history = readChatHistory() || [];
|
|
339
513
|
const apiKey = config.anthropicApiKey || process.env.ANTHROPIC_API_KEY;
|
|
340
514
|
if (!apiKey) return { response: "กรุณาใส่ Anthropic API Key ในการตั้งค่าก่อนนะคะ", action: { type: "none" } };
|
|
341
515
|
|
|
342
|
-
const
|
|
343
|
-
let mcpPrompt = "\n\nAVAILABLE MCP TOOLS:\n";
|
|
344
|
-
mcpTools.forEach(tool => {
|
|
345
|
-
mcpPrompt += `- Server: ${tool.serverName}, Tool: ${tool.name}\n Desc: ${tool.description}\n Args: ${JSON.stringify(tool.inputSchema.properties)}\n`;
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
const systemPrompt = systemInstruction + pluginManager.getPromptDescriptions() + mcpPrompt;
|
|
516
|
+
const systemPrompt = buildSystemPrompt();
|
|
349
517
|
|
|
350
518
|
const messages = [];
|
|
351
519
|
for (const msg of history.slice(-MAX_HISTORY_MESSAGES)) {
|
|
@@ -392,13 +560,7 @@ async function handleOpenAIChat(finalMessage, base64Image, config) {
|
|
|
392
560
|
const apiKey = config.openaiApiKey || process.env.OPENAI_API_KEY;
|
|
393
561
|
if (!apiKey) return { response: "กรุณาใส่ OpenAI API Key ในการตั้งค่าก่อนนะคะ", action: { type: "none" } };
|
|
394
562
|
|
|
395
|
-
const
|
|
396
|
-
let mcpPrompt = "\n\nAVAILABLE MCP TOOLS:\n";
|
|
397
|
-
mcpTools.forEach(tool => {
|
|
398
|
-
mcpPrompt += `- Server: ${tool.serverName}, Tool: ${tool.name}\n Desc: ${tool.description}\n Args: ${JSON.stringify(tool.inputSchema.properties)}\n`;
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
const systemPrompt = systemInstruction + pluginManager.getPromptDescriptions() + mcpPrompt;
|
|
563
|
+
const systemPrompt = buildSystemPrompt();
|
|
402
564
|
|
|
403
565
|
const messages = [{ role: "system", content: systemPrompt }];
|
|
404
566
|
for (const msg of history.slice(-MAX_HISTORY_MESSAGES)) {
|
|
@@ -435,6 +597,96 @@ async function handleOpenAIChat(finalMessage, base64Image, config) {
|
|
|
435
597
|
return parseAiResponse(outputText);
|
|
436
598
|
}
|
|
437
599
|
|
|
600
|
+
async function handleLocalOpenAIChat(finalMessage, base64Image, config) {
|
|
601
|
+
const history = readChatHistory() || [];
|
|
602
|
+
const apiKey = 'lm-studio';
|
|
603
|
+
const baseUrl = config.localApiBaseUrl || 'http://localhost:1234/v1';
|
|
604
|
+
|
|
605
|
+
const systemPrompt = buildSystemPrompt();
|
|
606
|
+
|
|
607
|
+
const messages = [{ role: "system", content: systemPrompt }];
|
|
608
|
+
for (const msg of history.slice(-MAX_HISTORY_MESSAGES)) {
|
|
609
|
+
const role = msg.role === 'model' ? 'assistant' : 'user';
|
|
610
|
+
let text = Array.isArray(msg.parts) ? msg.parts.map(p => p.text || '').join('\n') : '';
|
|
611
|
+
if (text) messages.push({ role, content: text });
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
const content = [{ type: "text", text: finalMessage || "Analyze this." }];
|
|
615
|
+
if (base64Image) {
|
|
616
|
+
content.push({
|
|
617
|
+
type: "image_url",
|
|
618
|
+
image_url: { url: base64Image }
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
messages.push({ role: "user", content });
|
|
622
|
+
|
|
623
|
+
const response = await axios.post(`${baseUrl.replace(/\/$/, '')}/chat/completions`, {
|
|
624
|
+
model: config.localModelName || 'local-model',
|
|
625
|
+
messages: messages,
|
|
626
|
+
// response_format json_object is sometimes problematic on weak local models, but required by our prompt.
|
|
627
|
+
// We'll keep it as some local servers like LM Studio support it for specific models.
|
|
628
|
+
// If not supported, the system prompt usually coerces it anyway.
|
|
629
|
+
response_format: { type: "json_object" }
|
|
630
|
+
}, {
|
|
631
|
+
headers: {
|
|
632
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
633
|
+
'Content-Type': 'application/json'
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
const outputText = response.data.choices[0].message.content;
|
|
638
|
+
history.push({ role: 'user', parts: [{ text: finalMessage }] });
|
|
639
|
+
history.push({ role: 'model', parts: [{ text: outputText }] });
|
|
640
|
+
writeChatHistory(history.slice(-MAX_HISTORY_MESSAGES));
|
|
641
|
+
|
|
642
|
+
return parseAiResponse(outputText);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
async function handleHuggingFaceChat(finalMessage, base64Image, config) {
|
|
646
|
+
const history = readChatHistory() || [];
|
|
647
|
+
const apiKey = config.hfApiKey || process.env.HF_API_KEY;
|
|
648
|
+
if (!apiKey) return { response: "กรุณาใส่ Hugging Face API Key ในการตั้งค่าก่อนนะคะ", action: { type: "none" } };
|
|
649
|
+
|
|
650
|
+
const modelId = config.hfModel || 'meta-llama/Meta-Llama-3-8B-Instruct';
|
|
651
|
+
const baseUrl = `https://api-inference.huggingface.co/models/${modelId}/v1/chat/completions`;
|
|
652
|
+
|
|
653
|
+
const systemPrompt = buildSystemPrompt();
|
|
654
|
+
|
|
655
|
+
const messages = [{ role: "system", content: systemPrompt }];
|
|
656
|
+
for (const msg of history.slice(-MAX_HISTORY_MESSAGES)) {
|
|
657
|
+
const role = msg.role === 'model' ? 'assistant' : 'user';
|
|
658
|
+
let text = Array.isArray(msg.parts) ? msg.parts.map(p => p.text || '').join('\n') : '';
|
|
659
|
+
if (text) messages.push({ role, content: text });
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
const content = [{ type: "text", text: finalMessage || "Analyze this." }];
|
|
663
|
+
if (base64Image) {
|
|
664
|
+
content.push({
|
|
665
|
+
type: "image_url",
|
|
666
|
+
image_url: { url: base64Image }
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
messages.push({ role: "user", content });
|
|
670
|
+
|
|
671
|
+
const response = await axios.post(baseUrl, {
|
|
672
|
+
model: modelId,
|
|
673
|
+
messages: messages,
|
|
674
|
+
max_tokens: 4096
|
|
675
|
+
}, {
|
|
676
|
+
headers: {
|
|
677
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
678
|
+
'Content-Type': 'application/json'
|
|
679
|
+
}
|
|
680
|
+
});
|
|
681
|
+
|
|
682
|
+
const outputText = response.data.choices[0].message.content;
|
|
683
|
+
history.push({ role: 'user', parts: [{ text: finalMessage }] });
|
|
684
|
+
history.push({ role: 'model', parts: [{ text: outputText }] });
|
|
685
|
+
writeChatHistory(history.slice(-MAX_HISTORY_MESSAGES));
|
|
686
|
+
|
|
687
|
+
return parseAiResponse(outputText);
|
|
688
|
+
}
|
|
689
|
+
|
|
438
690
|
function parseAiResponse(outputText) {
|
|
439
691
|
let parsedResult;
|
|
440
692
|
try {
|
|
@@ -456,10 +708,9 @@ function parseAiResponse(outputText) {
|
|
|
456
708
|
|
|
457
709
|
async function handleOllamaChat(finalMessage, base64Image, base64Audio, config) {
|
|
458
710
|
const history = readChatHistory() || [];
|
|
459
|
-
pluginManager.loadPlugins();
|
|
460
711
|
|
|
461
712
|
const ollamaMessages = [
|
|
462
|
-
{ role: 'system', content:
|
|
713
|
+
{ role: 'system', content: buildSystemPrompt() }
|
|
463
714
|
];
|
|
464
715
|
|
|
465
716
|
for (const msg of history.slice(-MAX_HISTORY_MESSAGES)) {
|
|
@@ -486,7 +737,8 @@ async function handleOllamaChat(finalMessage, base64Image, base64Audio, config)
|
|
|
486
737
|
|
|
487
738
|
ollamaMessages.push(userMessage);
|
|
488
739
|
|
|
489
|
-
const
|
|
740
|
+
const ollamaBaseUrl = (config.ollamaHost || 'http://localhost:11434').replace(/\/$/, '');
|
|
741
|
+
const response = await axios.post(`${ollamaBaseUrl}/api/chat`, {
|
|
490
742
|
model: config.ollamaModel || 'llama3:latest',
|
|
491
743
|
messages: ollamaMessages,
|
|
492
744
|
format: 'json',
|
|
@@ -631,6 +883,7 @@ async function translateImageContent(base64Image) {
|
|
|
631
883
|
|
|
632
884
|
module.exports = {
|
|
633
885
|
handleChat,
|
|
886
|
+
handleGeminiChatStream,
|
|
634
887
|
resetChat,
|
|
635
888
|
getChatTranscript,
|
|
636
889
|
translateImageContent,
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mint Agent Orchestrator
|
|
3
|
+
* -----------------------
|
|
4
|
+
* Manages specialized AI personas (Experts) and their system prompts.
|
|
5
|
+
* Allows switching the agent's behavior on the fly.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const AGENT_PERSONAS = {
|
|
9
|
+
'general': {
|
|
10
|
+
name: 'Mint Default',
|
|
11
|
+
icon: '💎',
|
|
12
|
+
instruction: 'You are Mint, a versatile and helpful AI assistant. You maintain a friendly, professional, and slightly cheerful personality. Use emojis appropriately.'
|
|
13
|
+
},
|
|
14
|
+
'coder': {
|
|
15
|
+
name: 'Mint Coder',
|
|
16
|
+
icon: '💻',
|
|
17
|
+
instruction: 'You are Mint Coder, an expert software engineer. Your responses should be technically precise, focus on best practices, and provide optimized code snippets. Explain complex logic clearly.'
|
|
18
|
+
},
|
|
19
|
+
'researcher': {
|
|
20
|
+
name: 'Mint Researcher',
|
|
21
|
+
icon: '🔍',
|
|
22
|
+
instruction: 'You are Mint Researcher, an academic and analytical assistant. Focus on citations, data-driven facts, and objective analysis. Avoid speculation and be highly detailed.'
|
|
23
|
+
},
|
|
24
|
+
'creative': {
|
|
25
|
+
name: 'Mint Creative',
|
|
26
|
+
icon: '🎨',
|
|
27
|
+
instruction: 'You are Mint Creative, a storytelling and brainstorming partner. Use vivid language, poetic descriptions, and think outside the box. Be highly expressive and encouraging.'
|
|
28
|
+
},
|
|
29
|
+
'manager': {
|
|
30
|
+
name: 'Mint Manager',
|
|
31
|
+
icon: '💼',
|
|
32
|
+
instruction: 'You are Mint Manager, a productivity and project management expert. Focus on task lists, deadlines, efficiency, and clear action plans. Be concise and goal-oriented.'
|
|
33
|
+
},
|
|
34
|
+
'reviewer': {
|
|
35
|
+
name: 'Mint Reviewer',
|
|
36
|
+
icon: '⚖️',
|
|
37
|
+
instruction: 'You are Mint Reviewer, a senior code critic. Your job is to find flaws, security vulnerabilities, performance bottlenecks, and logic errors in any provided content. Be brutal but constructive. Use a formal, objective tone.'
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
let currentAgentType = 'general';
|
|
42
|
+
|
|
43
|
+
function getAgent(type) {
|
|
44
|
+
return AGENT_PERSONAS[type] || AGENT_PERSONAS['general'];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function setAgent(type) {
|
|
48
|
+
if (AGENT_PERSONAS[type]) {
|
|
49
|
+
currentAgentType = type;
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getCurrentAgent() {
|
|
56
|
+
return getAgent(currentAgentType);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function listAgents() {
|
|
60
|
+
return Object.keys(AGENT_PERSONAS);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resetAgent() {
|
|
64
|
+
currentAgentType = 'general';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = {
|
|
68
|
+
getAgent,
|
|
69
|
+
setAgent,
|
|
70
|
+
getCurrentAgent,
|
|
71
|
+
listAgents,
|
|
72
|
+
resetAgent
|
|
73
|
+
};
|
|
@@ -7,6 +7,7 @@ const fs = require('fs');
|
|
|
7
7
|
const path = require('path');
|
|
8
8
|
|
|
9
9
|
const os = require('os');
|
|
10
|
+
const { sendNotification } = require('../System/notifications');
|
|
10
11
|
|
|
11
12
|
const DEFAULT_GEMINI_MODEL = 'gemini-2.5-flash';
|
|
12
13
|
|
|
@@ -119,6 +120,7 @@ async function executeAutonomousTask(taskDescription, notifyCallback) {
|
|
|
119
120
|
break;
|
|
120
121
|
case 'propose_bash':
|
|
121
122
|
if (notifyCallback) notifyCallback(`💡 มิ้นท์เสนอให้รันคำสั่ง: ${actionObj.target}`);
|
|
123
|
+
sendNotification('Mint Bash Proposal', `Mint wants to run: ${actionObj.target}`);
|
|
122
124
|
observation = `USER NOTIFIED of bash command: ${actionObj.target}. Note: You must wait for user to run it manually. If you can continue without it, do so. Otherwise, indicate you are waiting or done with this phase.`;
|
|
123
125
|
break;
|
|
124
126
|
default:
|