@pheem49/mint 1.5.2 → 1.5.4
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/GUIDE_TH.md +23 -11
- package/README.md +148 -66
- package/assets/Agent_Mint.png +0 -0
- package/assets/Settings.png +0 -0
- package/install.ps1 +64 -0
- package/install.sh +54 -0
- package/main.js +12 -0
- package/package.json +5 -3
- package/preload.js +4 -0
- package/scripts/install_linux_desktop_entry.js +48 -0
- package/src/AI_Brain/Gemini_API.js +231 -498
- package/src/AI_Brain/autonomous_brain.js +46 -19
- package/src/AI_Brain/headless_agent.js +21 -2
- package/src/AI_Brain/provider_adapter.js +358 -0
- package/src/Automation_Layer/file_operations.js +17 -5
- package/src/CLI/approval_handler.js +5 -0
- package/src/CLI/chat_router.js +7 -0
- package/src/CLI/chat_ui.js +397 -76
- package/src/CLI/cli_colors.js +86 -3
- package/src/CLI/cli_formatters.js +6 -1
- package/src/CLI/code_agent.js +706 -273
- package/src/CLI/interactive_chat.js +311 -149
- package/src/CLI/slash_command_handler.js +2 -2
- package/src/CLI/updater.js +21 -1
- package/src/System/config_manager.js +5 -1
- package/src/System/ipc_handlers.js +95 -1
- package/src/System/picture_store.js +109 -0
- package/src/System/smart_context.js +227 -0
- package/src/System/task_manager.js +127 -0
- package/src/System/tool_registry.js +13 -0
- package/src/System/window_manager.js +16 -8
- package/src/UI/live2d_manager.js +42 -8
- package/src/UI/preload-spotlight.js +1 -0
- package/src/UI/renderer.js +837 -63
- package/src/UI/settings.css +160 -96
- package/src/UI/settings.html +9 -0
- package/src/UI/settings.js +35 -2
- package/src/UI/spotlight.js +13 -9
- package/src/UI/styles.css +1592 -165
- package/privacy.txt +0 -1
package/src/CLI/chat_ui.js
CHANGED
|
@@ -63,6 +63,8 @@ function formatActivityStep(info = {}) {
|
|
|
63
63
|
return { title: '⚠ Notice', detail: rawText };
|
|
64
64
|
case 'run_shell':
|
|
65
65
|
return { title: 'Ran', detail: rawText };
|
|
66
|
+
case 'plan':
|
|
67
|
+
return { title: 'Plan', detail: rawText };
|
|
66
68
|
case 'apply_patch':
|
|
67
69
|
case 'write_file':
|
|
68
70
|
return { title: 'Edited', detail: rawText };
|
|
@@ -146,6 +148,107 @@ function formatDuration(totalSeconds) {
|
|
|
146
148
|
return `${minutes}m ${remainingSeconds}s`;
|
|
147
149
|
}
|
|
148
150
|
|
|
151
|
+
function splitDiffStatSegments(value) {
|
|
152
|
+
const text = String(value || '');
|
|
153
|
+
const match = text.match(/\(\+(\d+)\s+-(\d+)\)/);
|
|
154
|
+
if (!match) return [{ text, color: 'cyanBright' }];
|
|
155
|
+
|
|
156
|
+
return [
|
|
157
|
+
{ text: text.slice(0, match.index), color: 'cyanBright' },
|
|
158
|
+
{ text: '(', color: 'gray' },
|
|
159
|
+
{ text: `+${match[1]}`, color: 'greenBright' },
|
|
160
|
+
{ text: ' ', color: 'gray' },
|
|
161
|
+
{ text: `-${match[2]}`, color: 'redBright' },
|
|
162
|
+
{ text: ')', color: 'gray' },
|
|
163
|
+
{ text: text.slice(match.index + match[0].length), color: 'cyanBright' }
|
|
164
|
+
].filter(part => part.text);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const APPROVAL_CHOICES = ['approve', 'approve_session', 'deny'];
|
|
168
|
+
const SUGGESTION_WINDOW_SIZE = 5;
|
|
169
|
+
|
|
170
|
+
function getNextApprovalChoice(current, direction = 1) {
|
|
171
|
+
const choices = APPROVAL_CHOICES;
|
|
172
|
+
const index = choices.indexOf(current);
|
|
173
|
+
const start = index === -1 ? 0 : index;
|
|
174
|
+
return choices[(start + direction + choices.length) % choices.length];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function getVisibleSuggestions(suggestions, selectedIndex, limit = SUGGESTION_WINDOW_SIZE) {
|
|
178
|
+
const items = Array.isArray(suggestions) ? suggestions : [];
|
|
179
|
+
const safeLimit = Math.max(1, Number(limit) || SUGGESTION_WINDOW_SIZE);
|
|
180
|
+
const safeSelected = Math.min(Math.max(0, Number(selectedIndex) || 0), Math.max(0, items.length - 1));
|
|
181
|
+
const start = Math.min(
|
|
182
|
+
Math.max(0, safeSelected - safeLimit + 1),
|
|
183
|
+
Math.max(0, items.length - safeLimit)
|
|
184
|
+
);
|
|
185
|
+
const visible = items.slice(start, start + safeLimit);
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
start,
|
|
189
|
+
visible,
|
|
190
|
+
current: items.length > 0 ? safeSelected + 1 : 0,
|
|
191
|
+
total: items.length
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function parseUnifiedDiffPreview(preview) {
|
|
196
|
+
const lines = String(preview || '').replace(/\r\n/g, '\n').split('\n');
|
|
197
|
+
const files = [];
|
|
198
|
+
let current = null;
|
|
199
|
+
|
|
200
|
+
for (const line of lines) {
|
|
201
|
+
if (line.startsWith('--- a/')) {
|
|
202
|
+
current = {
|
|
203
|
+
path: line.slice('--- a/'.length),
|
|
204
|
+
additions: 0,
|
|
205
|
+
deletions: 0,
|
|
206
|
+
lines: []
|
|
207
|
+
};
|
|
208
|
+
files.push(current);
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!current) continue;
|
|
213
|
+
if (line.startsWith('+++ b/')) {
|
|
214
|
+
current.path = line.slice('+++ b/'.length) || current.path;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (line.startsWith('@@')) {
|
|
219
|
+
current.lines.push({ type: 'hunk', text: line });
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (line.startsWith('+')) {
|
|
224
|
+
current.additions += 1;
|
|
225
|
+
current.lines.push({ type: 'add', text: line });
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (line.startsWith('-')) {
|
|
230
|
+
current.deletions += 1;
|
|
231
|
+
current.lines.push({ type: 'delete', text: line });
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
current.lines.push({ type: 'context', text: line });
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return files.filter(file => file.lines.length > 0 || file.additions > 0 || file.deletions > 0);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function isUnifiedDiffPreview(preview) {
|
|
242
|
+
return parseUnifiedDiffPreview(preview).length > 0;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function getDiffLineStyle(line = {}) {
|
|
246
|
+
if (line.type === 'add') return { color: 'greenBright' };
|
|
247
|
+
if (line.type === 'delete') return { color: 'redBright' };
|
|
248
|
+
if (line.type === 'hunk') return { color: 'cyanBright' };
|
|
249
|
+
return { color: 'gray', dimColor: true };
|
|
250
|
+
}
|
|
251
|
+
|
|
149
252
|
function shouldAppendMessage(role, text) {
|
|
150
253
|
if (role === 'assistant' || role === 'system') {
|
|
151
254
|
return String(text || '').trim().length > 0;
|
|
@@ -153,6 +256,21 @@ function shouldAppendMessage(role, text) {
|
|
|
153
256
|
return true;
|
|
154
257
|
}
|
|
155
258
|
|
|
259
|
+
function appendInlineImageToken(value, imageIndex) {
|
|
260
|
+
const token = `[Image #${imageIndex}]`;
|
|
261
|
+
const text = String(value || '').replace(/\s*[\r\n]+\s*/g, ' ').trimEnd();
|
|
262
|
+
return text ? `${text} ${token}` : token;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function removeImageToken(value, imageIndex) {
|
|
266
|
+
const tokenPattern = new RegExp(`\\s*\\[Image #${imageIndex}\\]`, 'g');
|
|
267
|
+
return String(value || '').replace(tokenPattern, '').replace(/\s{2,}/g, ' ').trim();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function removeAllImageTokens(value) {
|
|
271
|
+
return String(value || '').replace(/\s*\[Image #\d+\]/g, '').replace(/\s{2,}/g, ' ').trim();
|
|
272
|
+
}
|
|
273
|
+
|
|
156
274
|
/**
|
|
157
275
|
* We wrap everything in an async function to load ESM modules
|
|
158
276
|
*/
|
|
@@ -175,26 +293,28 @@ async function createChatUI(options) {
|
|
|
175
293
|
const [model, setModel] = useState('');
|
|
176
294
|
const [workspace, setWorkspace] = useState(process.cwd());
|
|
177
295
|
const [pendingImages, setPendingImages] = useState([]);
|
|
178
|
-
const [pendingImagePrefix, setPendingImagePrefix] = useState('');
|
|
179
296
|
const [pendingPaste, setPendingPaste] = useState(null);
|
|
180
297
|
const [pendingPastePrefix, setPendingPastePrefix] = useState('');
|
|
181
298
|
const [pendingApproval, setPendingApproval] = useState(null);
|
|
182
299
|
const [approvalChoice, setApprovalChoice] = useState('approve');
|
|
300
|
+
const [approvalSessionAutoApprove, setApprovalSessionAutoApprove] = useState(false);
|
|
301
|
+
const [inputResetKey, setInputResetKey] = useState(0);
|
|
183
302
|
|
|
184
303
|
// Suggestions State
|
|
185
304
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
186
305
|
const inputRef = React.useRef(input);
|
|
187
306
|
const pendingImagesRef = React.useRef(pendingImages);
|
|
188
|
-
const pendingImagePrefixRef = React.useRef(pendingImagePrefix);
|
|
189
307
|
const pendingPasteRef = React.useRef(pendingPaste);
|
|
190
308
|
const pendingPastePrefixRef = React.useRef(pendingPastePrefix);
|
|
191
309
|
const liveAssistantRef = React.useRef(liveAssistant);
|
|
192
310
|
const thinkingStartedAtRef = React.useRef(null);
|
|
193
311
|
const fastModeRef = React.useRef(fastMode);
|
|
194
312
|
const suppressPasteCharRef = React.useRef(false);
|
|
313
|
+
const suppressPasteBurstRef = React.useRef(false);
|
|
195
314
|
const selectedIndexRef = React.useRef(selectedIndex);
|
|
196
315
|
const pendingApprovalRef = React.useRef(null);
|
|
197
316
|
const approvalChoiceRef = React.useRef('approve');
|
|
317
|
+
const approvalSessionAutoApproveRef = React.useRef(false);
|
|
198
318
|
|
|
199
319
|
const removePasteArtifact = (value) => {
|
|
200
320
|
const text = String(value || '');
|
|
@@ -209,6 +329,10 @@ async function createChatUI(options) {
|
|
|
209
329
|
const text = String(value || '');
|
|
210
330
|
return text.length > 500 || /[\r\n]/.test(text);
|
|
211
331
|
};
|
|
332
|
+
|
|
333
|
+
const resetInputCursorToEnd = () => {
|
|
334
|
+
setInputResetKey(key => key + 1);
|
|
335
|
+
};
|
|
212
336
|
|
|
213
337
|
useEffect(() => {
|
|
214
338
|
inputRef.current = input;
|
|
@@ -218,10 +342,6 @@ async function createChatUI(options) {
|
|
|
218
342
|
pendingImagesRef.current = pendingImages;
|
|
219
343
|
}, [pendingImages]);
|
|
220
344
|
|
|
221
|
-
useEffect(() => {
|
|
222
|
-
pendingImagePrefixRef.current = pendingImagePrefix;
|
|
223
|
-
}, [pendingImagePrefix]);
|
|
224
|
-
|
|
225
345
|
useEffect(() => {
|
|
226
346
|
pendingPasteRef.current = pendingPaste;
|
|
227
347
|
}, [pendingPaste]);
|
|
@@ -265,12 +385,20 @@ async function createChatUI(options) {
|
|
|
265
385
|
approvalChoiceRef.current = approvalChoice;
|
|
266
386
|
}, [approvalChoice]);
|
|
267
387
|
|
|
388
|
+
useEffect(() => {
|
|
389
|
+
approvalSessionAutoApproveRef.current = approvalSessionAutoApprove;
|
|
390
|
+
}, [approvalSessionAutoApprove]);
|
|
391
|
+
|
|
268
392
|
const showSuggestions = input.startsWith('/') && !input.includes(' ');
|
|
269
393
|
const suggestions = useMemo(() => {
|
|
270
394
|
if (!showSuggestions) return [];
|
|
271
395
|
const query = input.toLowerCase();
|
|
272
396
|
return SLASH_COMMANDS.filter(s => s.cmd.startsWith(query));
|
|
273
397
|
}, [input, showSuggestions]);
|
|
398
|
+
const visibleSuggestions = useMemo(
|
|
399
|
+
() => getVisibleSuggestions(suggestions, selectedIndex),
|
|
400
|
+
[suggestions, selectedIndex]
|
|
401
|
+
);
|
|
274
402
|
|
|
275
403
|
// Reset index when suggestions change
|
|
276
404
|
useEffect(() => {
|
|
@@ -342,7 +470,12 @@ async function createChatUI(options) {
|
|
|
342
470
|
return next;
|
|
343
471
|
},
|
|
344
472
|
getFastMode: () => fastModeRef.current,
|
|
345
|
-
setInputText: (val) =>
|
|
473
|
+
setInputText: (val) => {
|
|
474
|
+
const next = val || '';
|
|
475
|
+
inputRef.current = next;
|
|
476
|
+
setInput(next);
|
|
477
|
+
resetInputCursorToEnd();
|
|
478
|
+
},
|
|
346
479
|
setPendingPasteText: (text) => {
|
|
347
480
|
const normalized = normalizeInputText(text);
|
|
348
481
|
setPendingPaste({ text: normalized, label: `[Pasted Content ${normalized.length} chars]` });
|
|
@@ -353,12 +486,13 @@ async function createChatUI(options) {
|
|
|
353
486
|
updateWorkspace: (val) => setWorkspace(val),
|
|
354
487
|
attachImage: (image) => {
|
|
355
488
|
setPendingImages(prev => {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
489
|
+
const imageIndex = prev.length + 1;
|
|
490
|
+
setInput(current => {
|
|
491
|
+
const next = appendInlineImageToken(current, imageIndex);
|
|
492
|
+
inputRef.current = next;
|
|
493
|
+
resetInputCursorToEnd();
|
|
494
|
+
return next;
|
|
495
|
+
});
|
|
362
496
|
return [...prev, image];
|
|
363
497
|
});
|
|
364
498
|
},
|
|
@@ -379,12 +513,15 @@ async function createChatUI(options) {
|
|
|
379
513
|
if (action === 'memory_context' && process.env.MINT_SHOW_MEMORY_TRACE !== '1') {
|
|
380
514
|
return;
|
|
381
515
|
}
|
|
516
|
+
if (phase === 'tool_call') {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
382
519
|
if (thought) {
|
|
383
|
-
if (process.env.
|
|
520
|
+
if (process.env.MINT_HIDE_AGENT_NOTES === '1') {
|
|
384
521
|
return;
|
|
385
522
|
}
|
|
386
523
|
text = thought;
|
|
387
|
-
label = '
|
|
524
|
+
label = 'Working';
|
|
388
525
|
labelColor = 'gray';
|
|
389
526
|
isThought = true;
|
|
390
527
|
} else if (action === 'thinking' || phase === 'thinking') {
|
|
@@ -435,11 +572,18 @@ async function createChatUI(options) {
|
|
|
435
572
|
}]);
|
|
436
573
|
},
|
|
437
574
|
requestApproval: (request = {}) => {
|
|
575
|
+
if (approvalSessionAutoApproveRef.current) {
|
|
576
|
+
return Promise.resolve(true);
|
|
577
|
+
}
|
|
578
|
+
|
|
438
579
|
return new Promise((resolve) => {
|
|
439
580
|
const approval = {
|
|
440
581
|
type: request.type || 'action',
|
|
441
582
|
label: request.label || 'Requested action',
|
|
442
583
|
preview: request.preview || '',
|
|
584
|
+
summary: request.summary || '',
|
|
585
|
+
openPath: request.openPath || '',
|
|
586
|
+
warnings: Array.isArray(request.warnings) ? request.warnings.filter(Boolean) : [],
|
|
443
587
|
resolve
|
|
444
588
|
};
|
|
445
589
|
pendingApprovalRef.current = approval;
|
|
@@ -452,21 +596,41 @@ async function createChatUI(options) {
|
|
|
452
596
|
useInput((inputStr, key) => {
|
|
453
597
|
const approval = pendingApprovalRef.current;
|
|
454
598
|
if (approval) {
|
|
455
|
-
const resolveApproval = (approved) => {
|
|
599
|
+
const resolveApproval = (approved, approveForSession = false) => {
|
|
600
|
+
if (approveForSession) {
|
|
601
|
+
approvalSessionAutoApproveRef.current = true;
|
|
602
|
+
setApprovalSessionAutoApprove(true);
|
|
603
|
+
}
|
|
456
604
|
pendingApprovalRef.current = null;
|
|
457
605
|
setPendingApproval(null);
|
|
458
|
-
setHistory(prev =>
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
606
|
+
setHistory(prev => {
|
|
607
|
+
if (approved && isUnifiedDiffPreview(approval.preview)) {
|
|
608
|
+
return [...prev, {
|
|
609
|
+
role: 'system',
|
|
610
|
+
label: 'Edited',
|
|
611
|
+
labelColor: 'greenBright',
|
|
612
|
+
preview: approval.preview,
|
|
613
|
+
isDiffPreview: true,
|
|
614
|
+
time: new Date()
|
|
615
|
+
}];
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
return [...prev, {
|
|
619
|
+
role: 'system',
|
|
620
|
+
label: 'Approval',
|
|
621
|
+
labelColor: approved ? 'greenBright' : 'redBright',
|
|
622
|
+
text: `${approveForSession ? 'Approved this session' : (approved ? 'Approved' : 'Denied')}: ${approval.label}`,
|
|
623
|
+
time: new Date()
|
|
624
|
+
}];
|
|
625
|
+
});
|
|
465
626
|
approval.resolve(approved);
|
|
466
627
|
};
|
|
467
628
|
|
|
468
|
-
if (key.leftArrow || key.rightArrow || key.tab) {
|
|
469
|
-
const next =
|
|
629
|
+
if (key.upArrow || key.downArrow || key.leftArrow || key.rightArrow || key.tab) {
|
|
630
|
+
const next = getNextApprovalChoice(
|
|
631
|
+
approvalChoiceRef.current,
|
|
632
|
+
(key.upArrow || key.leftArrow) ? -1 : 1
|
|
633
|
+
);
|
|
470
634
|
approvalChoiceRef.current = next;
|
|
471
635
|
setApprovalChoice(next);
|
|
472
636
|
return;
|
|
@@ -474,13 +638,17 @@ async function createChatUI(options) {
|
|
|
474
638
|
|
|
475
639
|
const answer = String(inputStr || '').toLowerCase();
|
|
476
640
|
if (key.return) {
|
|
477
|
-
resolveApproval(approvalChoiceRef.current === '
|
|
641
|
+
resolveApproval(approvalChoiceRef.current !== 'deny', approvalChoiceRef.current === 'approve_session');
|
|
478
642
|
return;
|
|
479
643
|
}
|
|
480
644
|
if (answer === 'y') {
|
|
481
645
|
resolveApproval(true);
|
|
482
646
|
return;
|
|
483
647
|
}
|
|
648
|
+
if (answer === 'a') {
|
|
649
|
+
resolveApproval(true, true);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
484
652
|
if (answer === 'n' || key.escape || (key.ctrl && inputStr === 'c')) {
|
|
485
653
|
resolveApproval(false);
|
|
486
654
|
return;
|
|
@@ -491,8 +659,12 @@ async function createChatUI(options) {
|
|
|
491
659
|
if (key.escape && pendingImagesRef.current.length > 0) {
|
|
492
660
|
setPendingImages([]);
|
|
493
661
|
pendingImagesRef.current = [];
|
|
494
|
-
|
|
495
|
-
|
|
662
|
+
setInput(current => {
|
|
663
|
+
const next = removeAllImageTokens(current);
|
|
664
|
+
inputRef.current = next;
|
|
665
|
+
resetInputCursorToEnd();
|
|
666
|
+
return next;
|
|
667
|
+
});
|
|
496
668
|
return;
|
|
497
669
|
}
|
|
498
670
|
|
|
@@ -501,16 +673,24 @@ async function createChatUI(options) {
|
|
|
501
673
|
pendingPasteRef.current = null;
|
|
502
674
|
setPendingPastePrefix('');
|
|
503
675
|
pendingPastePrefixRef.current = '';
|
|
676
|
+
suppressPasteBurstRef.current = false;
|
|
677
|
+
inputRef.current = '';
|
|
678
|
+
setInput('');
|
|
679
|
+
resetInputCursorToEnd();
|
|
504
680
|
return;
|
|
505
681
|
}
|
|
506
682
|
|
|
507
683
|
if (key.ctrl && key.backspace && pendingImagesRef.current.length > 0) {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
684
|
+
const imageIndex = pendingImagesRef.current.length;
|
|
685
|
+
const nextImages = pendingImagesRef.current.slice(0, -1);
|
|
686
|
+
pendingImagesRef.current = nextImages;
|
|
687
|
+
setPendingImages(nextImages);
|
|
688
|
+
setInput(current => {
|
|
689
|
+
const next = removeImageToken(current, imageIndex);
|
|
690
|
+
inputRef.current = next;
|
|
691
|
+
resetInputCursorToEnd();
|
|
692
|
+
return next;
|
|
693
|
+
});
|
|
514
694
|
return;
|
|
515
695
|
}
|
|
516
696
|
|
|
@@ -530,11 +710,14 @@ async function createChatUI(options) {
|
|
|
530
710
|
.then((image) => {
|
|
531
711
|
if (image) {
|
|
532
712
|
setPendingImages(prev => {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
713
|
+
const imageIndex = prev.length + 1;
|
|
714
|
+
setInput(current => {
|
|
715
|
+
const cleaned = removePasteArtifact(current);
|
|
716
|
+
const next = appendInlineImageToken(cleaned || inputBeforePaste, imageIndex);
|
|
717
|
+
inputRef.current = next;
|
|
718
|
+
resetInputCursorToEnd();
|
|
719
|
+
return next;
|
|
720
|
+
});
|
|
538
721
|
return [...prev, image];
|
|
539
722
|
});
|
|
540
723
|
}
|
|
@@ -582,14 +765,13 @@ async function createChatUI(options) {
|
|
|
582
765
|
const handleSubmit = (value) => {
|
|
583
766
|
const text = normalizeInputText(value).trim();
|
|
584
767
|
const images = pendingImagesRef.current;
|
|
585
|
-
const imagePrefix = normalizeInputText(pendingImagePrefixRef.current).trim();
|
|
586
768
|
const imageLabels = images.map((_, index) => `[Image #${index + 1}]`).join(' ');
|
|
587
769
|
const pasted = pendingPasteRef.current;
|
|
588
770
|
const pastePrefix = normalizeInputText(pendingPastePrefixRef.current).trim();
|
|
589
771
|
const submittedText = pasted
|
|
590
772
|
? [pastePrefix, pasted.text, text].filter(Boolean).join('\n\n')
|
|
591
773
|
: images.length > 0
|
|
592
|
-
?
|
|
774
|
+
? (text || imageLabels)
|
|
593
775
|
: text;
|
|
594
776
|
if (!submittedText && images.length === 0) return;
|
|
595
777
|
|
|
@@ -603,23 +785,35 @@ async function createChatUI(options) {
|
|
|
603
785
|
|
|
604
786
|
setInput('');
|
|
605
787
|
setPendingImages([]);
|
|
606
|
-
setPendingImagePrefix('');
|
|
607
788
|
setPendingPaste(null);
|
|
608
789
|
setPendingPastePrefix('');
|
|
609
790
|
pendingImagesRef.current = [];
|
|
610
|
-
pendingImagePrefixRef.current = '';
|
|
611
791
|
pendingPasteRef.current = null;
|
|
612
792
|
pendingPastePrefixRef.current = '';
|
|
793
|
+
suppressPasteBurstRef.current = false;
|
|
613
794
|
onSubmit(submittedText, { images, pasted });
|
|
614
795
|
};
|
|
615
796
|
|
|
616
797
|
const handleInputChange = (value) => {
|
|
798
|
+
if (suppressPasteBurstRef.current && pendingPasteRef.current) {
|
|
799
|
+
inputRef.current = '';
|
|
800
|
+
setInput('');
|
|
801
|
+
resetInputCursorToEnd();
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
|
|
617
805
|
if (shouldStoreAsPastedContent(value)) {
|
|
618
806
|
const normalized = normalizeInputText(value);
|
|
619
807
|
const previous = normalizeInputText(inputRef.current).trim();
|
|
620
|
-
|
|
808
|
+
const pasted = { text: normalized, label: `[Pasted Content ${normalized.length} chars]` };
|
|
809
|
+
pendingPasteRef.current = pasted;
|
|
810
|
+
pendingPastePrefixRef.current = previous;
|
|
811
|
+
suppressPasteBurstRef.current = true;
|
|
812
|
+
setPendingPaste(pasted);
|
|
621
813
|
setPendingPastePrefix(previous);
|
|
814
|
+
inputRef.current = '';
|
|
622
815
|
setInput('');
|
|
816
|
+
resetInputCursorToEnd();
|
|
623
817
|
return;
|
|
624
818
|
}
|
|
625
819
|
|
|
@@ -636,9 +830,73 @@ async function createChatUI(options) {
|
|
|
636
830
|
return;
|
|
637
831
|
}
|
|
638
832
|
}
|
|
833
|
+
inputRef.current = normalizedValue;
|
|
639
834
|
setInput(normalizedValue);
|
|
640
835
|
};
|
|
641
836
|
|
|
837
|
+
const renderActivityDetail = (value) => {
|
|
838
|
+
const segments = splitDiffStatSegments(value);
|
|
839
|
+
return segments.map((segment, segmentIndex) =>
|
|
840
|
+
h(Text, {
|
|
841
|
+
key: `activity-detail-${segmentIndex}`,
|
|
842
|
+
color: segment.color,
|
|
843
|
+
wrap: 'wrap'
|
|
844
|
+
}, segment.text)
|
|
845
|
+
);
|
|
846
|
+
};
|
|
847
|
+
|
|
848
|
+
const renderDiffLine = (line, index) => {
|
|
849
|
+
const style = getDiffLineStyle(line);
|
|
850
|
+
return h(Text, {
|
|
851
|
+
key: `diff-line-${index}`,
|
|
852
|
+
...style
|
|
853
|
+
}, line.text || ' ');
|
|
854
|
+
};
|
|
855
|
+
|
|
856
|
+
const renderDiffPreview = (preview) => {
|
|
857
|
+
const files = parseUnifiedDiffPreview(preview);
|
|
858
|
+
if (files.length === 0) return null;
|
|
859
|
+
|
|
860
|
+
return h(Box, { flexDirection: 'column', marginTop: 1 },
|
|
861
|
+
...files.map((file, fileIndex) =>
|
|
862
|
+
h(Box, { key: `approval-diff-${fileIndex}`, flexDirection: 'column', marginBottom: 1 },
|
|
863
|
+
h(Box, null,
|
|
864
|
+
h(Text, { color: 'gray' }, '• '),
|
|
865
|
+
h(Text, { bold: true, color: 'white' }, `Edited ${file.path} `),
|
|
866
|
+
h(Text, { color: 'gray' }, '('),
|
|
867
|
+
h(Text, { color: 'greenBright' }, `+${file.additions}`),
|
|
868
|
+
h(Text, { color: 'gray' }, ' '),
|
|
869
|
+
h(Text, { color: 'redBright' }, `-${file.deletions}`),
|
|
870
|
+
h(Text, { color: 'gray' }, ')')
|
|
871
|
+
),
|
|
872
|
+
h(Box, { flexDirection: 'column', paddingLeft: 2 },
|
|
873
|
+
...file.lines.slice(0, 120).map(renderDiffLine),
|
|
874
|
+
file.lines.length > 120 && h(Text, { color: 'gray', dimColor: true }, `... ${file.lines.length - 120} more diff lines`)
|
|
875
|
+
)
|
|
876
|
+
)
|
|
877
|
+
)
|
|
878
|
+
);
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
const renderApprovalPreview = (approval) => {
|
|
882
|
+
const preview = approval && approval.preview ? approval.preview : '';
|
|
883
|
+
if (approval && approval.type === 'plan') {
|
|
884
|
+
return h(Box, { flexDirection: 'column', marginTop: 1 },
|
|
885
|
+
h(Text, { color: 'gray' }, approval.summary || 'Mint prepared a plan for this task.'),
|
|
886
|
+
approval.openPath && h(Text, { color: 'gray', dimColor: true }, `Details: ${approval.label}`)
|
|
887
|
+
);
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
const diffPreview = renderDiffPreview(preview);
|
|
891
|
+
if (!diffPreview) {
|
|
892
|
+
return preview && preview !== approval.label
|
|
893
|
+
? h(Box, null, h(Text, { color: 'gray', dimColor: true }, preview))
|
|
894
|
+
: null;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
return diffPreview;
|
|
898
|
+
};
|
|
899
|
+
|
|
642
900
|
const renderMessage = (msg, index, keyPrefix = 'msg') => {
|
|
643
901
|
if (msg.isThought) {
|
|
644
902
|
return h(Box, { key: `${keyPrefix}-${index}`, flexDirection: 'row', marginBottom: 0, paddingLeft: 2 },
|
|
@@ -654,11 +912,17 @@ async function createChatUI(options) {
|
|
|
654
912
|
),
|
|
655
913
|
h(Box, { paddingLeft: 2, marginBottom: 1 },
|
|
656
914
|
h(Text, { color: 'gray' }, '└ '),
|
|
657
|
-
|
|
915
|
+
...renderActivityDetail(msg.activityDetail || msg.text)
|
|
658
916
|
)
|
|
659
917
|
);
|
|
660
918
|
}
|
|
661
919
|
|
|
920
|
+
if (msg.isDiffPreview) {
|
|
921
|
+
return h(Box, { key: `${keyPrefix}-${index}`, flexDirection: 'column', marginBottom: 0 },
|
|
922
|
+
renderDiffPreview(msg.preview || '')
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
|
|
662
926
|
let name = 'Mint';
|
|
663
927
|
let nameColor = 'greenBright';
|
|
664
928
|
|
|
@@ -696,59 +960,70 @@ async function createChatUI(options) {
|
|
|
696
960
|
h(Text, { color: 'yellow' }, '● Mint is thinking...')
|
|
697
961
|
),
|
|
698
962
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
borderColor: 'gray',
|
|
963
|
+
pendingApproval && h(Box, {
|
|
964
|
+
flexDirection: 'column',
|
|
965
|
+
borderStyle: 'single',
|
|
966
|
+
borderColor: 'cyanBright',
|
|
704
967
|
paddingX: 1,
|
|
705
968
|
marginBottom: 0
|
|
706
969
|
},
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
970
|
+
h(Box, null,
|
|
971
|
+
pendingApproval.type === 'plan'
|
|
972
|
+
? h(Text, { bold: true, color: 'greenBright' }, 'Plan')
|
|
973
|
+
: [
|
|
974
|
+
h(Text, { key: 'approval-title', bold: true, color: 'greenBright' }, 'Approval '),
|
|
975
|
+
h(Text, { key: 'approval-type', color: 'cyanBright' }, `[${pendingApproval.type}] `),
|
|
976
|
+
h(Text, { key: 'approval-label', color: 'white' }, pendingApproval.label)
|
|
977
|
+
]
|
|
978
|
+
),
|
|
979
|
+
pendingApproval.warnings && pendingApproval.warnings.length > 0 && h(Box, { flexDirection: 'column', marginTop: 1, marginBottom: 1 },
|
|
980
|
+
...pendingApproval.warnings.map((warning, index) =>
|
|
981
|
+
h(Box, { key: `approval-warning-${index}` },
|
|
982
|
+
h(Text, { color: 'yellowBright' }, 'Warning: '),
|
|
983
|
+
h(Text, { color: 'yellowBright' }, warning)
|
|
984
|
+
)
|
|
985
|
+
)
|
|
986
|
+
),
|
|
987
|
+
renderApprovalPreview(pendingApproval)
|
|
714
988
|
),
|
|
715
989
|
|
|
716
990
|
pendingApproval && h(Box, {
|
|
717
991
|
flexDirection: 'column',
|
|
718
992
|
borderStyle: 'single',
|
|
719
|
-
borderColor: '
|
|
993
|
+
borderColor: approvalChoice === 'deny' ? 'redBright' : 'greenBright',
|
|
720
994
|
paddingX: 1,
|
|
721
|
-
marginBottom:
|
|
995
|
+
marginBottom: 0
|
|
722
996
|
},
|
|
723
|
-
h(Box, null,
|
|
724
|
-
h(Text, { bold: true, color: 'yellow' }, 'Approval '),
|
|
725
|
-
h(Text, { color: 'gray' }, `[${pendingApproval.type}] `),
|
|
726
|
-
h(Text, { color: 'white' }, pendingApproval.label)
|
|
727
|
-
),
|
|
728
|
-
pendingApproval.preview && pendingApproval.preview !== pendingApproval.label && h(Box, null,
|
|
729
|
-
h(Text, { color: 'gray' }, pendingApproval.preview)
|
|
730
|
-
),
|
|
731
997
|
h(Box, null,
|
|
732
998
|
h(Text, {
|
|
733
999
|
color: approvalChoice === 'approve' ? 'black' : 'greenBright',
|
|
734
1000
|
backgroundColor: approvalChoice === 'approve' ? 'greenBright' : undefined,
|
|
735
1001
|
bold: true
|
|
736
|
-
}, ' Approve ')
|
|
737
|
-
|
|
1002
|
+
}, approvalChoice === 'approve' ? '▸ Approve' : ' Approve')
|
|
1003
|
+
),
|
|
1004
|
+
h(Box, null,
|
|
1005
|
+
h(Text, {
|
|
1006
|
+
color: approvalChoice === 'approve_session' ? 'black' : 'cyanBright',
|
|
1007
|
+
backgroundColor: approvalChoice === 'approve_session' ? 'cyanBright' : undefined,
|
|
1008
|
+
bold: true
|
|
1009
|
+
}, approvalChoice === 'approve_session' ? '▸ Approve Session' : ' Approve Session')
|
|
1010
|
+
),
|
|
1011
|
+
h(Box, null,
|
|
738
1012
|
h(Text, {
|
|
739
1013
|
color: approvalChoice === 'deny' ? 'white' : 'redBright',
|
|
740
1014
|
backgroundColor: approvalChoice === 'deny' ? 'redBright' : undefined,
|
|
741
1015
|
bold: true
|
|
742
|
-
}, ' Deny ')
|
|
743
|
-
|
|
1016
|
+
}, approvalChoice === 'deny' ? '▸ Deny' : ' Deny')
|
|
1017
|
+
),
|
|
1018
|
+
h(Box, null,
|
|
1019
|
+
h(Text, { color: 'gray', dimColor: true }, ' ↑/↓ Enter y/a/n')
|
|
744
1020
|
)
|
|
745
1021
|
),
|
|
746
1022
|
|
|
747
1023
|
// Compact Input Area
|
|
748
1024
|
h(Box, { borderStyle: 'round', borderColor: pendingApproval ? 'gray' : 'greenBright', paddingX: 1, flexDirection: 'column' },
|
|
749
1025
|
pendingImages.length > 0 && h(Box, null,
|
|
750
|
-
|
|
751
|
-
h(Text, { color: 'greenBright' }, pendingImages.map((_, index) => `[Image #${index + 1}]`).join(' ') + ' '),
|
|
1026
|
+
h(Text, { color: 'greenBright' }, `${pendingImages.length} image${pendingImages.length === 1 ? '' : 's'} attached `),
|
|
752
1027
|
h(Text, { color: 'gray' }, 'Enter to send, Ctrl+Backspace remove, Esc clear')
|
|
753
1028
|
),
|
|
754
1029
|
pendingPaste && h(Box, null,
|
|
@@ -759,6 +1034,7 @@ async function createChatUI(options) {
|
|
|
759
1034
|
h(Box, { flexDirection: 'row' },
|
|
760
1035
|
h(Text, { bold: true, color: 'greenBright' }, '› '),
|
|
761
1036
|
h(TextInput, {
|
|
1037
|
+
key: `input-${inputResetKey}`,
|
|
762
1038
|
value: input,
|
|
763
1039
|
onChange: pendingApproval ? () => {} : handleInputChange,
|
|
764
1040
|
onSubmit: pendingApproval ? () => {} : handleSubmit,
|
|
@@ -767,11 +1043,36 @@ async function createChatUI(options) {
|
|
|
767
1043
|
)
|
|
768
1044
|
),
|
|
769
1045
|
|
|
1046
|
+
// Suggestions Menu
|
|
1047
|
+
showSuggestions && suggestions.length > 0 && h(Box, {
|
|
1048
|
+
flexDirection: 'column',
|
|
1049
|
+
borderStyle: 'single',
|
|
1050
|
+
borderColor: 'gray',
|
|
1051
|
+
paddingX: 1,
|
|
1052
|
+
marginBottom: 0
|
|
1053
|
+
},
|
|
1054
|
+
h(Box, { justifyContent: 'space-between' },
|
|
1055
|
+
h(Text, { color: 'gray', dimColor: true }, 'Commands'),
|
|
1056
|
+
h(Text, { color: 'gray', dimColor: true }, `${visibleSuggestions.current}/${visibleSuggestions.total}`)
|
|
1057
|
+
),
|
|
1058
|
+
visibleSuggestions.visible.map((s, i) => {
|
|
1059
|
+
const actualIndex = visibleSuggestions.start + i;
|
|
1060
|
+
return h(Box, { key: s.cmd, flexDirection: 'row' },
|
|
1061
|
+
h(Text, {
|
|
1062
|
+
backgroundColor: actualIndex === selectedIndex ? 'green' : undefined,
|
|
1063
|
+
color: actualIndex === selectedIndex ? 'white' : 'greenBright'
|
|
1064
|
+
}, s.cmd.padEnd(12)),
|
|
1065
|
+
h(Text, { color: 'gray' }, ` ${s.desc}`)
|
|
1066
|
+
);
|
|
1067
|
+
})
|
|
1068
|
+
),
|
|
1069
|
+
|
|
770
1070
|
// Status Bar
|
|
771
1071
|
h(Box, { justifyContent: 'space-between' },
|
|
772
1072
|
h(Box, null,
|
|
773
1073
|
h(Text, { color: 'cyan' }, `[${fastMode ? 'Fast' : mode}] `),
|
|
774
|
-
h(Text, { color: 'magentaBright' }, (model || config.geminiModel || 'gemini').slice(0, 46))
|
|
1074
|
+
h(Text, { color: 'magentaBright' }, (model || config.geminiModel || 'gemini').slice(0, 46)),
|
|
1075
|
+
approvalSessionAutoApprove && h(Text, { color: 'greenBright' }, ' approvals:session')
|
|
775
1076
|
),
|
|
776
1077
|
h(Box, null,
|
|
777
1078
|
h(Text, { color: 'gray' }, `path: ...${workspace.slice(-20)}`)
|
|
@@ -789,9 +1090,10 @@ async function createChatUI(options) {
|
|
|
789
1090
|
console.log(`\x1b[90mType naturally to chat. Esc to exit.\x1b[0m\n`);
|
|
790
1091
|
|
|
791
1092
|
const ref = createRef();
|
|
792
|
-
render(h(App, { ref, ...options }), { exitOnCtrlC: false });
|
|
1093
|
+
const instance = render(h(App, { ref, ...options }), { exitOnCtrlC: false });
|
|
793
1094
|
|
|
794
1095
|
return {
|
|
1096
|
+
unmount: () => instance.unmount(),
|
|
795
1097
|
appendMessage: (role, text, metadata) => ref.current?.appendMessage(role, text, metadata),
|
|
796
1098
|
setThinking: (val, seconds) => ref.current?.setThinking(val, seconds),
|
|
797
1099
|
setMode: (val) => ref.current?.setMode(val),
|
|
@@ -822,4 +1124,23 @@ async function createChatUI(options) {
|
|
|
822
1124
|
};
|
|
823
1125
|
}
|
|
824
1126
|
|
|
825
|
-
module.exports = {
|
|
1127
|
+
module.exports = {
|
|
1128
|
+
createChatUI,
|
|
1129
|
+
_helpers: {
|
|
1130
|
+
cleanDisplayText,
|
|
1131
|
+
stripInlineMarkdown,
|
|
1132
|
+
compactPathLabel,
|
|
1133
|
+
formatActivityStep,
|
|
1134
|
+
formatDuration,
|
|
1135
|
+
splitDiffStatSegments,
|
|
1136
|
+
getNextApprovalChoice,
|
|
1137
|
+
getVisibleSuggestions,
|
|
1138
|
+
parseUnifiedDiffPreview,
|
|
1139
|
+
isUnifiedDiffPreview,
|
|
1140
|
+
getDiffLineStyle,
|
|
1141
|
+
shouldAppendMessage,
|
|
1142
|
+
appendInlineImageToken,
|
|
1143
|
+
removeImageToken,
|
|
1144
|
+
removeAllImageTokens
|
|
1145
|
+
}
|
|
1146
|
+
};
|