@pheem49/mint 1.5.1 → 1.5.3
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 +7 -7
- package/README.md +140 -66
- package/assets/Agent_Mint.png +0 -0
- package/assets/Settings.png +0 -0
- package/main.js +12 -0
- package/mint-cli.js +148 -921
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//345/221/206/347/214/253.exp3.json +31 -1
- package/models/Shiroko_Model/Shiroko/Shiroko_Core//347/202/271/344/270/200/344/270/213.exp3.json +6 -1
- package/package.json +20 -21
- package/preload.js +2 -0
- package/scripts/install_linux_desktop_entry.js +48 -0
- package/src/AI_Brain/Gemini_API.js +194 -491
- package/src/AI_Brain/autonomous_brain.js +46 -19
- package/src/AI_Brain/headless_agent.js +21 -2
- package/src/AI_Brain/proactive_engine.js +12 -2
- package/src/AI_Brain/provider_adapter.js +358 -0
- package/src/Automation_Layer/browser_automation.js +26 -24
- package/src/CLI/approval_handler.js +47 -0
- package/src/CLI/chat_router.js +7 -0
- package/src/CLI/chat_ui.js +586 -80
- package/src/CLI/cli_colors.js +115 -0
- package/src/CLI/cli_formatters.js +94 -0
- package/src/CLI/code_agent.js +825 -283
- package/src/CLI/intent_detectors.js +181 -0
- package/src/CLI/interactive_chat.js +641 -0
- package/src/CLI/list_features.js +3 -0
- 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 +21 -1
- 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/chat_history_manager.js +20 -12
- package/src/System/config_manager.js +4 -1
- package/src/System/ipc_handlers.js +10 -0
- package/src/System/optional_require.js +23 -0
- package/src/System/picture_store.js +109 -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 +246 -14
- package/src/UI/renderer.js +620 -45
- package/src/UI/settings.css +738 -439
- package/src/UI/settings.html +487 -432
- package/src/UI/settings.js +44 -10
- package/src/UI/styles.css +1403 -106
- package/privacy.txt +0 -1
package/src/CLI/chat_ui.js
CHANGED
|
@@ -32,6 +32,245 @@ const SLASH_COMMANDS = [
|
|
|
32
32
|
{ cmd: '/exit', desc: 'Exit Mint' }
|
|
33
33
|
];
|
|
34
34
|
|
|
35
|
+
const MAX_BLANK_LINES = 1;
|
|
36
|
+
|
|
37
|
+
function compactPathLabel(value) {
|
|
38
|
+
const text = String(value || '').trim();
|
|
39
|
+
if (!text) return '';
|
|
40
|
+
return path.basename(text) || text;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function formatActivityStep(info = {}) {
|
|
44
|
+
if (!info || typeof info !== 'object') return null;
|
|
45
|
+
|
|
46
|
+
const { action, phase, target, message } = info;
|
|
47
|
+
const rawText = String(target || message || '').trim();
|
|
48
|
+
const kind = action || phase || 'activity';
|
|
49
|
+
if (!rawText) return null;
|
|
50
|
+
|
|
51
|
+
switch (kind) {
|
|
52
|
+
case 'list_files':
|
|
53
|
+
return { title: 'Explored', detail: `List ${rawText}` };
|
|
54
|
+
case 'find_path':
|
|
55
|
+
return { title: 'Explored', detail: `Find ${rawText}` };
|
|
56
|
+
case 'read_file':
|
|
57
|
+
return { title: 'Explored', detail: `Read ${compactPathLabel(rawText)}` };
|
|
58
|
+
case 'search_code':
|
|
59
|
+
return { title: 'Explored', detail: `Search ${rawText}` };
|
|
60
|
+
case 'web_search':
|
|
61
|
+
return { title: 'Searched', detail: rawText };
|
|
62
|
+
case 'warn':
|
|
63
|
+
return { title: '⚠ Notice', detail: rawText };
|
|
64
|
+
case 'run_shell':
|
|
65
|
+
return { title: 'Ran', detail: rawText };
|
|
66
|
+
case 'plan':
|
|
67
|
+
return { title: 'Plan', detail: rawText };
|
|
68
|
+
case 'apply_patch':
|
|
69
|
+
case 'write_file':
|
|
70
|
+
return { title: 'Edited', detail: rawText };
|
|
71
|
+
case 'evaluator':
|
|
72
|
+
return { title: 'Checked', detail: rawText };
|
|
73
|
+
case 'reviewer_start':
|
|
74
|
+
return { title: 'Reviewing', detail: rawText };
|
|
75
|
+
case 'ask_user':
|
|
76
|
+
return { title: 'Ask User', detail: rawText };
|
|
77
|
+
default:
|
|
78
|
+
return { title: kind, detail: rawText };
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function stripInlineMarkdown(value) {
|
|
83
|
+
return String(value || '')
|
|
84
|
+
.replace(/\*\*([^*]+)\*\*/g, '$1')
|
|
85
|
+
.replace(/\*([^*\n]+)\*/g, '$1')
|
|
86
|
+
.replace(/__([^_]+)__/g, '$1')
|
|
87
|
+
.replace(/`([^`\n]+)`/g, '$1');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function cleanDisplayText(text, role = 'assistant') {
|
|
91
|
+
const raw = String(text || '').replace(/\r\n/g, '\n').trim();
|
|
92
|
+
if (!raw) return '';
|
|
93
|
+
|
|
94
|
+
const shouldPolishMarkdown = role === 'assistant' || role === 'system';
|
|
95
|
+
const lines = raw.split('\n');
|
|
96
|
+
const cleaned = [];
|
|
97
|
+
let inCodeBlock = false;
|
|
98
|
+
let blankCount = 0;
|
|
99
|
+
|
|
100
|
+
for (const sourceLine of lines) {
|
|
101
|
+
let line = sourceLine.replace(/\s+$/g, '');
|
|
102
|
+
const fence = line.match(/^\s*```(.*)$/);
|
|
103
|
+
|
|
104
|
+
if (fence) {
|
|
105
|
+
inCodeBlock = !inCodeBlock;
|
|
106
|
+
const label = fence[1] ? `code: ${fence[1].trim()}` : 'code';
|
|
107
|
+
line = inCodeBlock ? label : '';
|
|
108
|
+
} else if (inCodeBlock) {
|
|
109
|
+
line = line ? ` ${line}` : '';
|
|
110
|
+
} else if (shouldPolishMarkdown) {
|
|
111
|
+
const heading = line.match(/^\s{0,3}#{1,6}\s+(.+)$/);
|
|
112
|
+
const bullet = line.match(/^(\s*)[-*]\s+(.+)$/);
|
|
113
|
+
const numbered = line.match(/^(\s*)\d+[.)]\s+(.+)$/);
|
|
114
|
+
|
|
115
|
+
if (heading) {
|
|
116
|
+
if (cleaned.length > 0 && cleaned[cleaned.length - 1] !== '') cleaned.push('');
|
|
117
|
+
line = stripInlineMarkdown(heading[1]).trim();
|
|
118
|
+
} else if (bullet) {
|
|
119
|
+
line = `${bullet[1]}• ${stripInlineMarkdown(bullet[2]).trim()}`;
|
|
120
|
+
} else if (numbered) {
|
|
121
|
+
line = `${numbered[1]}${stripInlineMarkdown(line).trim()}`;
|
|
122
|
+
} else {
|
|
123
|
+
line = stripInlineMarkdown(line);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!line.trim()) {
|
|
128
|
+
blankCount++;
|
|
129
|
+
if (blankCount <= MAX_BLANK_LINES && cleaned.length > 0) cleaned.push('');
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
blankCount = 0;
|
|
134
|
+
cleaned.push(line);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
while (cleaned[0] === '') cleaned.shift();
|
|
138
|
+
while (cleaned[cleaned.length - 1] === '') cleaned.pop();
|
|
139
|
+
return cleaned.join('\n');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function formatDuration(totalSeconds) {
|
|
143
|
+
const seconds = Math.max(0, Math.floor(Number(totalSeconds) || 0));
|
|
144
|
+
const minutes = Math.floor(seconds / 60);
|
|
145
|
+
const remainingSeconds = seconds % 60;
|
|
146
|
+
|
|
147
|
+
if (minutes <= 0) return `${remainingSeconds}s`;
|
|
148
|
+
return `${minutes}m ${remainingSeconds}s`;
|
|
149
|
+
}
|
|
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
|
+
|
|
252
|
+
function shouldAppendMessage(role, text) {
|
|
253
|
+
if (role === 'assistant' || role === 'system') {
|
|
254
|
+
return String(text || '').trim().length > 0;
|
|
255
|
+
}
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
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
|
+
|
|
35
274
|
/**
|
|
36
275
|
* We wrap everything in an async function to load ESM modules
|
|
37
276
|
*/
|
|
@@ -48,30 +287,34 @@ async function createChatUI(options) {
|
|
|
48
287
|
const [history, setHistory] = useState(initialHistory);
|
|
49
288
|
const [liveAssistant, setLiveAssistant] = useState(null);
|
|
50
289
|
const [thinking, setThinking] = useState(false);
|
|
290
|
+
const [workingSeconds, setWorkingSeconds] = useState(0);
|
|
51
291
|
const [fastMode, setFastMode] = useState(false);
|
|
52
292
|
const [mode, setMode] = useState('Agent');
|
|
53
293
|
const [model, setModel] = useState('');
|
|
54
294
|
const [workspace, setWorkspace] = useState(process.cwd());
|
|
55
295
|
const [pendingImages, setPendingImages] = useState([]);
|
|
56
|
-
const [pendingImagePrefix, setPendingImagePrefix] = useState('');
|
|
57
296
|
const [pendingPaste, setPendingPaste] = useState(null);
|
|
58
297
|
const [pendingPastePrefix, setPendingPastePrefix] = useState('');
|
|
59
298
|
const [pendingApproval, setPendingApproval] = useState(null);
|
|
60
299
|
const [approvalChoice, setApprovalChoice] = useState('approve');
|
|
300
|
+
const [approvalSessionAutoApprove, setApprovalSessionAutoApprove] = useState(false);
|
|
301
|
+
const [inputResetKey, setInputResetKey] = useState(0);
|
|
61
302
|
|
|
62
303
|
// Suggestions State
|
|
63
304
|
const [selectedIndex, setSelectedIndex] = useState(0);
|
|
64
305
|
const inputRef = React.useRef(input);
|
|
65
306
|
const pendingImagesRef = React.useRef(pendingImages);
|
|
66
|
-
const pendingImagePrefixRef = React.useRef(pendingImagePrefix);
|
|
67
307
|
const pendingPasteRef = React.useRef(pendingPaste);
|
|
68
308
|
const pendingPastePrefixRef = React.useRef(pendingPastePrefix);
|
|
69
309
|
const liveAssistantRef = React.useRef(liveAssistant);
|
|
310
|
+
const thinkingStartedAtRef = React.useRef(null);
|
|
70
311
|
const fastModeRef = React.useRef(fastMode);
|
|
71
312
|
const suppressPasteCharRef = React.useRef(false);
|
|
313
|
+
const suppressPasteBurstRef = React.useRef(false);
|
|
72
314
|
const selectedIndexRef = React.useRef(selectedIndex);
|
|
73
315
|
const pendingApprovalRef = React.useRef(null);
|
|
74
316
|
const approvalChoiceRef = React.useRef('approve');
|
|
317
|
+
const approvalSessionAutoApproveRef = React.useRef(false);
|
|
75
318
|
|
|
76
319
|
const removePasteArtifact = (value) => {
|
|
77
320
|
const text = String(value || '');
|
|
@@ -86,6 +329,10 @@ async function createChatUI(options) {
|
|
|
86
329
|
const text = String(value || '');
|
|
87
330
|
return text.length > 500 || /[\r\n]/.test(text);
|
|
88
331
|
};
|
|
332
|
+
|
|
333
|
+
const resetInputCursorToEnd = () => {
|
|
334
|
+
setInputResetKey(key => key + 1);
|
|
335
|
+
};
|
|
89
336
|
|
|
90
337
|
useEffect(() => {
|
|
91
338
|
inputRef.current = input;
|
|
@@ -95,10 +342,6 @@ async function createChatUI(options) {
|
|
|
95
342
|
pendingImagesRef.current = pendingImages;
|
|
96
343
|
}, [pendingImages]);
|
|
97
344
|
|
|
98
|
-
useEffect(() => {
|
|
99
|
-
pendingImagePrefixRef.current = pendingImagePrefix;
|
|
100
|
-
}, [pendingImagePrefix]);
|
|
101
|
-
|
|
102
345
|
useEffect(() => {
|
|
103
346
|
pendingPasteRef.current = pendingPaste;
|
|
104
347
|
}, [pendingPaste]);
|
|
@@ -111,6 +354,17 @@ async function createChatUI(options) {
|
|
|
111
354
|
liveAssistantRef.current = liveAssistant;
|
|
112
355
|
}, [liveAssistant]);
|
|
113
356
|
|
|
357
|
+
useEffect(() => {
|
|
358
|
+
if (!thinking) return undefined;
|
|
359
|
+
|
|
360
|
+
const timer = setInterval(() => {
|
|
361
|
+
if (!thinkingStartedAtRef.current) return;
|
|
362
|
+
setWorkingSeconds(Math.floor((Date.now() - thinkingStartedAtRef.current) / 1000));
|
|
363
|
+
}, 1000);
|
|
364
|
+
|
|
365
|
+
return () => clearInterval(timer);
|
|
366
|
+
}, [thinking]);
|
|
367
|
+
|
|
114
368
|
useEffect(() => {
|
|
115
369
|
fastModeRef.current = fastMode;
|
|
116
370
|
}, [fastMode]);
|
|
@@ -131,12 +385,20 @@ async function createChatUI(options) {
|
|
|
131
385
|
approvalChoiceRef.current = approvalChoice;
|
|
132
386
|
}, [approvalChoice]);
|
|
133
387
|
|
|
388
|
+
useEffect(() => {
|
|
389
|
+
approvalSessionAutoApproveRef.current = approvalSessionAutoApprove;
|
|
390
|
+
}, [approvalSessionAutoApprove]);
|
|
391
|
+
|
|
134
392
|
const showSuggestions = input.startsWith('/') && !input.includes(' ');
|
|
135
393
|
const suggestions = useMemo(() => {
|
|
136
394
|
if (!showSuggestions) return [];
|
|
137
395
|
const query = input.toLowerCase();
|
|
138
396
|
return SLASH_COMMANDS.filter(s => s.cmd.startsWith(query));
|
|
139
397
|
}, [input, showSuggestions]);
|
|
398
|
+
const visibleSuggestions = useMemo(
|
|
399
|
+
() => getVisibleSuggestions(suggestions, selectedIndex),
|
|
400
|
+
[suggestions, selectedIndex]
|
|
401
|
+
);
|
|
140
402
|
|
|
141
403
|
// Reset index when suggestions change
|
|
142
404
|
useEffect(() => {
|
|
@@ -148,6 +410,7 @@ async function createChatUI(options) {
|
|
|
148
410
|
// Export methods to the outside world via ref
|
|
149
411
|
useImperativeHandle(ref, () => ({
|
|
150
412
|
appendMessage: (role, text, metadata = {}) => {
|
|
413
|
+
if (!shouldAppendMessage(role, text)) return;
|
|
151
414
|
setHistory(prev => [...prev, { role, text, time: new Date(), ...metadata }]);
|
|
152
415
|
if (metadata.providerInfo) {
|
|
153
416
|
const { provider, model } = metadata.providerInfo;
|
|
@@ -164,6 +427,7 @@ async function createChatUI(options) {
|
|
|
164
427
|
}
|
|
165
428
|
},
|
|
166
429
|
appendAssistantStreamChunk: (chunk) => {
|
|
430
|
+
if (!String(chunk || '').trim()) return;
|
|
167
431
|
const current = liveAssistantRef.current || { role: 'assistant', text: '', time: new Date() };
|
|
168
432
|
const next = { ...current, text: `${current.text || ''}${chunk}` };
|
|
169
433
|
liveAssistantRef.current = next;
|
|
@@ -177,7 +441,21 @@ async function createChatUI(options) {
|
|
|
177
441
|
setHistory(prev => [...prev, current]);
|
|
178
442
|
}
|
|
179
443
|
},
|
|
180
|
-
setThinking: (val) =>
|
|
444
|
+
setThinking: (val, seconds = 0) => {
|
|
445
|
+
if (val) {
|
|
446
|
+
const elapsed = Number.isFinite(seconds) ? Math.max(0, seconds) : 0;
|
|
447
|
+
if (!thinkingStartedAtRef.current) {
|
|
448
|
+
thinkingStartedAtRef.current = Date.now() - (elapsed * 1000);
|
|
449
|
+
}
|
|
450
|
+
setWorkingSeconds(Math.floor((Date.now() - thinkingStartedAtRef.current) / 1000));
|
|
451
|
+
setThinking(true);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
thinkingStartedAtRef.current = null;
|
|
456
|
+
setWorkingSeconds(0);
|
|
457
|
+
setThinking(false);
|
|
458
|
+
},
|
|
181
459
|
setMode: (val) => setMode(val),
|
|
182
460
|
setFastMode: (val) => {
|
|
183
461
|
const next = Boolean(val);
|
|
@@ -192,7 +470,12 @@ async function createChatUI(options) {
|
|
|
192
470
|
return next;
|
|
193
471
|
},
|
|
194
472
|
getFastMode: () => fastModeRef.current,
|
|
195
|
-
setInputText: (val) =>
|
|
473
|
+
setInputText: (val) => {
|
|
474
|
+
const next = val || '';
|
|
475
|
+
inputRef.current = next;
|
|
476
|
+
setInput(next);
|
|
477
|
+
resetInputCursorToEnd();
|
|
478
|
+
},
|
|
196
479
|
setPendingPasteText: (text) => {
|
|
197
480
|
const normalized = normalizeInputText(text);
|
|
198
481
|
setPendingPaste({ text: normalized, label: `[Pasted Content ${normalized.length} chars]` });
|
|
@@ -203,12 +486,13 @@ async function createChatUI(options) {
|
|
|
203
486
|
updateWorkspace: (val) => setWorkspace(val),
|
|
204
487
|
attachImage: (image) => {
|
|
205
488
|
setPendingImages(prev => {
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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
|
+
});
|
|
212
496
|
return [...prev, image];
|
|
213
497
|
});
|
|
214
498
|
},
|
|
@@ -229,14 +513,39 @@ async function createChatUI(options) {
|
|
|
229
513
|
if (action === 'memory_context' && process.env.MINT_SHOW_MEMORY_TRACE !== '1') {
|
|
230
514
|
return;
|
|
231
515
|
}
|
|
516
|
+
if (phase === 'tool_call') {
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
232
519
|
if (thought) {
|
|
520
|
+
if (process.env.MINT_HIDE_AGENT_NOTES === '1') {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
233
523
|
text = thought;
|
|
234
|
-
label = '
|
|
524
|
+
label = 'Working';
|
|
235
525
|
labelColor = 'gray';
|
|
236
526
|
isThought = true;
|
|
237
527
|
} else if (action === 'thinking' || phase === 'thinking') {
|
|
238
528
|
return;
|
|
239
529
|
} else {
|
|
530
|
+
const activity = formatActivityStep(info);
|
|
531
|
+
if (activity) {
|
|
532
|
+
const fullText = `[${activity.title}] ${activity.detail}`;
|
|
533
|
+
if (fullText === lastSystemMessage.current) return;
|
|
534
|
+
lastSystemMessage.current = fullText;
|
|
535
|
+
|
|
536
|
+
setHistory(prev => [...prev, {
|
|
537
|
+
role: 'system',
|
|
538
|
+
label: activity.title,
|
|
539
|
+
labelColor: 'blueBright',
|
|
540
|
+
text: activity.detail,
|
|
541
|
+
isActivity: true,
|
|
542
|
+
activityTitle: activity.title,
|
|
543
|
+
activityDetail: activity.detail,
|
|
544
|
+
time: new Date()
|
|
545
|
+
}]);
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
|
|
240
549
|
label = action || phase || 'Action';
|
|
241
550
|
text = target || message || '';
|
|
242
551
|
if (!text) return;
|
|
@@ -263,11 +572,18 @@ async function createChatUI(options) {
|
|
|
263
572
|
}]);
|
|
264
573
|
},
|
|
265
574
|
requestApproval: (request = {}) => {
|
|
575
|
+
if (approvalSessionAutoApproveRef.current) {
|
|
576
|
+
return Promise.resolve(true);
|
|
577
|
+
}
|
|
578
|
+
|
|
266
579
|
return new Promise((resolve) => {
|
|
267
580
|
const approval = {
|
|
268
581
|
type: request.type || 'action',
|
|
269
582
|
label: request.label || 'Requested action',
|
|
270
583
|
preview: request.preview || '',
|
|
584
|
+
summary: request.summary || '',
|
|
585
|
+
openPath: request.openPath || '',
|
|
586
|
+
warnings: Array.isArray(request.warnings) ? request.warnings.filter(Boolean) : [],
|
|
271
587
|
resolve
|
|
272
588
|
};
|
|
273
589
|
pendingApprovalRef.current = approval;
|
|
@@ -280,21 +596,41 @@ async function createChatUI(options) {
|
|
|
280
596
|
useInput((inputStr, key) => {
|
|
281
597
|
const approval = pendingApprovalRef.current;
|
|
282
598
|
if (approval) {
|
|
283
|
-
const resolveApproval = (approved) => {
|
|
599
|
+
const resolveApproval = (approved, approveForSession = false) => {
|
|
600
|
+
if (approveForSession) {
|
|
601
|
+
approvalSessionAutoApproveRef.current = true;
|
|
602
|
+
setApprovalSessionAutoApprove(true);
|
|
603
|
+
}
|
|
284
604
|
pendingApprovalRef.current = null;
|
|
285
605
|
setPendingApproval(null);
|
|
286
|
-
setHistory(prev =>
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
+
});
|
|
293
626
|
approval.resolve(approved);
|
|
294
627
|
};
|
|
295
628
|
|
|
296
|
-
if (key.leftArrow || key.rightArrow || key.tab) {
|
|
297
|
-
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
|
+
);
|
|
298
634
|
approvalChoiceRef.current = next;
|
|
299
635
|
setApprovalChoice(next);
|
|
300
636
|
return;
|
|
@@ -302,13 +638,17 @@ async function createChatUI(options) {
|
|
|
302
638
|
|
|
303
639
|
const answer = String(inputStr || '').toLowerCase();
|
|
304
640
|
if (key.return) {
|
|
305
|
-
resolveApproval(approvalChoiceRef.current === '
|
|
641
|
+
resolveApproval(approvalChoiceRef.current !== 'deny', approvalChoiceRef.current === 'approve_session');
|
|
306
642
|
return;
|
|
307
643
|
}
|
|
308
644
|
if (answer === 'y') {
|
|
309
645
|
resolveApproval(true);
|
|
310
646
|
return;
|
|
311
647
|
}
|
|
648
|
+
if (answer === 'a') {
|
|
649
|
+
resolveApproval(true, true);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
312
652
|
if (answer === 'n' || key.escape || (key.ctrl && inputStr === 'c')) {
|
|
313
653
|
resolveApproval(false);
|
|
314
654
|
return;
|
|
@@ -319,8 +659,12 @@ async function createChatUI(options) {
|
|
|
319
659
|
if (key.escape && pendingImagesRef.current.length > 0) {
|
|
320
660
|
setPendingImages([]);
|
|
321
661
|
pendingImagesRef.current = [];
|
|
322
|
-
|
|
323
|
-
|
|
662
|
+
setInput(current => {
|
|
663
|
+
const next = removeAllImageTokens(current);
|
|
664
|
+
inputRef.current = next;
|
|
665
|
+
resetInputCursorToEnd();
|
|
666
|
+
return next;
|
|
667
|
+
});
|
|
324
668
|
return;
|
|
325
669
|
}
|
|
326
670
|
|
|
@@ -329,16 +673,24 @@ async function createChatUI(options) {
|
|
|
329
673
|
pendingPasteRef.current = null;
|
|
330
674
|
setPendingPastePrefix('');
|
|
331
675
|
pendingPastePrefixRef.current = '';
|
|
676
|
+
suppressPasteBurstRef.current = false;
|
|
677
|
+
inputRef.current = '';
|
|
678
|
+
setInput('');
|
|
679
|
+
resetInputCursorToEnd();
|
|
332
680
|
return;
|
|
333
681
|
}
|
|
334
682
|
|
|
335
683
|
if (key.ctrl && key.backspace && pendingImagesRef.current.length > 0) {
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
+
});
|
|
342
694
|
return;
|
|
343
695
|
}
|
|
344
696
|
|
|
@@ -358,11 +710,14 @@ async function createChatUI(options) {
|
|
|
358
710
|
.then((image) => {
|
|
359
711
|
if (image) {
|
|
360
712
|
setPendingImages(prev => {
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
+
});
|
|
366
721
|
return [...prev, image];
|
|
367
722
|
});
|
|
368
723
|
}
|
|
@@ -410,14 +765,13 @@ async function createChatUI(options) {
|
|
|
410
765
|
const handleSubmit = (value) => {
|
|
411
766
|
const text = normalizeInputText(value).trim();
|
|
412
767
|
const images = pendingImagesRef.current;
|
|
413
|
-
const imagePrefix = normalizeInputText(pendingImagePrefixRef.current).trim();
|
|
414
768
|
const imageLabels = images.map((_, index) => `[Image #${index + 1}]`).join(' ');
|
|
415
769
|
const pasted = pendingPasteRef.current;
|
|
416
770
|
const pastePrefix = normalizeInputText(pendingPastePrefixRef.current).trim();
|
|
417
771
|
const submittedText = pasted
|
|
418
772
|
? [pastePrefix, pasted.text, text].filter(Boolean).join('\n\n')
|
|
419
773
|
: images.length > 0
|
|
420
|
-
?
|
|
774
|
+
? (text || imageLabels)
|
|
421
775
|
: text;
|
|
422
776
|
if (!submittedText && images.length === 0) return;
|
|
423
777
|
|
|
@@ -431,23 +785,35 @@ async function createChatUI(options) {
|
|
|
431
785
|
|
|
432
786
|
setInput('');
|
|
433
787
|
setPendingImages([]);
|
|
434
|
-
setPendingImagePrefix('');
|
|
435
788
|
setPendingPaste(null);
|
|
436
789
|
setPendingPastePrefix('');
|
|
437
790
|
pendingImagesRef.current = [];
|
|
438
|
-
pendingImagePrefixRef.current = '';
|
|
439
791
|
pendingPasteRef.current = null;
|
|
440
792
|
pendingPastePrefixRef.current = '';
|
|
793
|
+
suppressPasteBurstRef.current = false;
|
|
441
794
|
onSubmit(submittedText, { images, pasted });
|
|
442
795
|
};
|
|
443
796
|
|
|
444
797
|
const handleInputChange = (value) => {
|
|
798
|
+
if (suppressPasteBurstRef.current && pendingPasteRef.current) {
|
|
799
|
+
inputRef.current = '';
|
|
800
|
+
setInput('');
|
|
801
|
+
resetInputCursorToEnd();
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
|
|
445
805
|
if (shouldStoreAsPastedContent(value)) {
|
|
446
806
|
const normalized = normalizeInputText(value);
|
|
447
807
|
const previous = normalizeInputText(inputRef.current).trim();
|
|
448
|
-
|
|
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);
|
|
449
813
|
setPendingPastePrefix(previous);
|
|
814
|
+
inputRef.current = '';
|
|
450
815
|
setInput('');
|
|
816
|
+
resetInputCursorToEnd();
|
|
451
817
|
return;
|
|
452
818
|
}
|
|
453
819
|
|
|
@@ -464,9 +830,73 @@ async function createChatUI(options) {
|
|
|
464
830
|
return;
|
|
465
831
|
}
|
|
466
832
|
}
|
|
833
|
+
inputRef.current = normalizedValue;
|
|
467
834
|
setInput(normalizedValue);
|
|
468
835
|
};
|
|
469
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
|
+
|
|
470
900
|
const renderMessage = (msg, index, keyPrefix = 'msg') => {
|
|
471
901
|
if (msg.isThought) {
|
|
472
902
|
return h(Box, { key: `${keyPrefix}-${index}`, flexDirection: 'row', marginBottom: 0, paddingLeft: 2 },
|
|
@@ -474,6 +904,25 @@ async function createChatUI(options) {
|
|
|
474
904
|
);
|
|
475
905
|
}
|
|
476
906
|
|
|
907
|
+
if (msg.isActivity) {
|
|
908
|
+
return h(Box, { key: `${keyPrefix}-${index}`, flexDirection: 'column', marginBottom: 0 },
|
|
909
|
+
h(Box, null,
|
|
910
|
+
h(Text, { color: 'greenBright' }, '• '),
|
|
911
|
+
h(Text, { bold: true, color: msg.labelColor || 'blueBright' }, msg.activityTitle || msg.label || 'Activity')
|
|
912
|
+
),
|
|
913
|
+
h(Box, { paddingLeft: 2, marginBottom: 1 },
|
|
914
|
+
h(Text, { color: 'gray' }, '└ '),
|
|
915
|
+
...renderActivityDetail(msg.activityDetail || msg.text)
|
|
916
|
+
)
|
|
917
|
+
);
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (msg.isDiffPreview) {
|
|
921
|
+
return h(Box, { key: `${keyPrefix}-${index}`, flexDirection: 'column', marginBottom: 0 },
|
|
922
|
+
renderDiffPreview(msg.preview || '')
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
|
|
477
926
|
let name = 'Mint';
|
|
478
927
|
let nameColor = 'greenBright';
|
|
479
928
|
|
|
@@ -494,7 +943,7 @@ async function createChatUI(options) {
|
|
|
494
943
|
h(Text, { color: 'gray' }, ` ${msg.time instanceof Date ? msg.time.toLocaleTimeString() : ''}`)
|
|
495
944
|
),
|
|
496
945
|
h(Box, { paddingLeft: 2, marginBottom: 1 },
|
|
497
|
-
h(Text,
|
|
946
|
+
h(Text, { wrap: 'wrap' }, cleanDisplayText(msg.text, msg.role))
|
|
498
947
|
)
|
|
499
948
|
);
|
|
500
949
|
};
|
|
@@ -506,63 +955,75 @@ async function createChatUI(options) {
|
|
|
506
955
|
|
|
507
956
|
// Floating (Persistent) UI part
|
|
508
957
|
h(Box, { flexDirection: 'column' },
|
|
509
|
-
thinking && h(Box, { marginBottom: 1 },
|
|
958
|
+
thinking && h(Box, { flexDirection: 'column', marginBottom: 1 },
|
|
959
|
+
h(Text, { color: 'gray', dimColor: true }, `─ Working for ${formatDuration(workingSeconds)} ─────────────────────────────────────────────────────────`),
|
|
510
960
|
h(Text, { color: 'yellow' }, '● Mint is thinking...')
|
|
511
961
|
),
|
|
512
962
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
borderColor: 'gray',
|
|
963
|
+
pendingApproval && h(Box, {
|
|
964
|
+
flexDirection: 'column',
|
|
965
|
+
borderStyle: 'single',
|
|
966
|
+
borderColor: 'cyanBright',
|
|
518
967
|
paddingX: 1,
|
|
519
968
|
marginBottom: 0
|
|
520
969
|
},
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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)
|
|
528
988
|
),
|
|
529
989
|
|
|
530
990
|
pendingApproval && h(Box, {
|
|
531
991
|
flexDirection: 'column',
|
|
532
992
|
borderStyle: 'single',
|
|
533
|
-
borderColor: '
|
|
993
|
+
borderColor: approvalChoice === 'deny' ? 'redBright' : 'greenBright',
|
|
534
994
|
paddingX: 1,
|
|
535
|
-
marginBottom:
|
|
995
|
+
marginBottom: 0
|
|
536
996
|
},
|
|
537
|
-
h(Box, null,
|
|
538
|
-
h(Text, { bold: true, color: 'yellow' }, 'Approval '),
|
|
539
|
-
h(Text, { color: 'gray' }, `[${pendingApproval.type}] `),
|
|
540
|
-
h(Text, { color: 'white' }, pendingApproval.label)
|
|
541
|
-
),
|
|
542
|
-
pendingApproval.preview && pendingApproval.preview !== pendingApproval.label && h(Box, null,
|
|
543
|
-
h(Text, { color: 'gray' }, pendingApproval.preview)
|
|
544
|
-
),
|
|
545
997
|
h(Box, null,
|
|
546
998
|
h(Text, {
|
|
547
999
|
color: approvalChoice === 'approve' ? 'black' : 'greenBright',
|
|
548
1000
|
backgroundColor: approvalChoice === 'approve' ? 'greenBright' : undefined,
|
|
549
1001
|
bold: true
|
|
550
|
-
}, ' Approve ')
|
|
551
|
-
|
|
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,
|
|
552
1012
|
h(Text, {
|
|
553
1013
|
color: approvalChoice === 'deny' ? 'white' : 'redBright',
|
|
554
1014
|
backgroundColor: approvalChoice === 'deny' ? 'redBright' : undefined,
|
|
555
1015
|
bold: true
|
|
556
|
-
}, ' Deny ')
|
|
557
|
-
|
|
1016
|
+
}, approvalChoice === 'deny' ? '▸ Deny' : ' Deny')
|
|
1017
|
+
),
|
|
1018
|
+
h(Box, null,
|
|
1019
|
+
h(Text, { color: 'gray', dimColor: true }, ' ↑/↓ Enter y/a/n')
|
|
558
1020
|
)
|
|
559
1021
|
),
|
|
560
1022
|
|
|
561
1023
|
// Compact Input Area
|
|
562
1024
|
h(Box, { borderStyle: 'round', borderColor: pendingApproval ? 'gray' : 'greenBright', paddingX: 1, flexDirection: 'column' },
|
|
563
1025
|
pendingImages.length > 0 && h(Box, null,
|
|
564
|
-
|
|
565
|
-
h(Text, { color: 'greenBright' }, pendingImages.map((_, index) => `[Image #${index + 1}]`).join(' ') + ' '),
|
|
1026
|
+
h(Text, { color: 'greenBright' }, `${pendingImages.length} image${pendingImages.length === 1 ? '' : 's'} attached `),
|
|
566
1027
|
h(Text, { color: 'gray' }, 'Enter to send, Ctrl+Backspace remove, Esc clear')
|
|
567
1028
|
),
|
|
568
1029
|
pendingPaste && h(Box, null,
|
|
@@ -573,6 +1034,7 @@ async function createChatUI(options) {
|
|
|
573
1034
|
h(Box, { flexDirection: 'row' },
|
|
574
1035
|
h(Text, { bold: true, color: 'greenBright' }, '› '),
|
|
575
1036
|
h(TextInput, {
|
|
1037
|
+
key: `input-${inputResetKey}`,
|
|
576
1038
|
value: input,
|
|
577
1039
|
onChange: pendingApproval ? () => {} : handleInputChange,
|
|
578
1040
|
onSubmit: pendingApproval ? () => {} : handleSubmit,
|
|
@@ -581,11 +1043,36 @@ async function createChatUI(options) {
|
|
|
581
1043
|
)
|
|
582
1044
|
),
|
|
583
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
|
+
|
|
584
1070
|
// Status Bar
|
|
585
1071
|
h(Box, { justifyContent: 'space-between' },
|
|
586
1072
|
h(Box, null,
|
|
587
1073
|
h(Text, { color: 'cyan' }, `[${fastMode ? 'Fast' : mode}] `),
|
|
588
|
-
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')
|
|
589
1076
|
),
|
|
590
1077
|
h(Box, null,
|
|
591
1078
|
h(Text, { color: 'gray' }, `path: ...${workspace.slice(-20)}`)
|
|
@@ -603,11 +1090,12 @@ async function createChatUI(options) {
|
|
|
603
1090
|
console.log(`\x1b[90mType naturally to chat. Esc to exit.\x1b[0m\n`);
|
|
604
1091
|
|
|
605
1092
|
const ref = createRef();
|
|
606
|
-
render(h(App, { ref, ...options }), { exitOnCtrlC: false });
|
|
1093
|
+
const instance = render(h(App, { ref, ...options }), { exitOnCtrlC: false });
|
|
607
1094
|
|
|
608
1095
|
return {
|
|
1096
|
+
unmount: () => instance.unmount(),
|
|
609
1097
|
appendMessage: (role, text, metadata) => ref.current?.appendMessage(role, text, metadata),
|
|
610
|
-
setThinking: (val) => ref.current?.setThinking(val),
|
|
1098
|
+
setThinking: (val, seconds) => ref.current?.setThinking(val, seconds),
|
|
611
1099
|
setMode: (val) => ref.current?.setMode(val),
|
|
612
1100
|
setFastMode: (val) => ref.current?.setFastMode(val),
|
|
613
1101
|
toggleFastMode: () => ref.current?.toggleFastMode(),
|
|
@@ -619,11 +1107,10 @@ async function createChatUI(options) {
|
|
|
619
1107
|
attachImage: (image) => ref.current?.attachImage(image),
|
|
620
1108
|
appendCodeStep: (info) => ref.current?.appendCodeStep(info),
|
|
621
1109
|
streamMessage: (metadata = {}) => {
|
|
622
|
-
let fullText = '';
|
|
623
1110
|
ref.current?.beginAssistantStream(metadata);
|
|
624
1111
|
return {
|
|
625
1112
|
appendChunk: (chunk) => {
|
|
626
|
-
|
|
1113
|
+
if (!String(chunk || '').trim()) return;
|
|
627
1114
|
ref.current?.appendAssistantStreamChunk(chunk);
|
|
628
1115
|
},
|
|
629
1116
|
finalize: () => {
|
|
@@ -637,4 +1124,23 @@ async function createChatUI(options) {
|
|
|
637
1124
|
};
|
|
638
1125
|
}
|
|
639
1126
|
|
|
640
|
-
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
|
+
};
|