jinzd-ai-cli 0.1.90 → 0.1.91
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/dist/{chunk-TVXTIA3P.js → chunk-CXWWAWE7.js} +1 -1
- package/dist/{chunk-N22QAXWR.js → chunk-KYHUMNQO.js} +1 -1
- package/dist/index.js +4 -4
- package/dist/{run-tests-RDW3AQXJ.js → run-tests-TLJ53CV5.js} +1 -1
- package/dist/{server-DKCUXFJR.js → server-QG62ZDQI.js} +13 -4
- package/dist/web/client/app.js +268 -4
- package/dist/web/client/index.html +49 -2
- package/dist/web/client/style.css +106 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
theme,
|
|
36
36
|
truncateOutput,
|
|
37
37
|
undoStack
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-CXWWAWE7.js";
|
|
39
39
|
import {
|
|
40
40
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
41
41
|
AUTHOR,
|
|
@@ -55,7 +55,7 @@ import {
|
|
|
55
55
|
REPO_URL,
|
|
56
56
|
SKILLS_DIR_NAME,
|
|
57
57
|
VERSION
|
|
58
|
-
} from "./chunk-
|
|
58
|
+
} from "./chunk-KYHUMNQO.js";
|
|
59
59
|
|
|
60
60
|
// src/index.ts
|
|
61
61
|
import { program } from "commander";
|
|
@@ -1904,7 +1904,7 @@ ${hint}` : "")
|
|
|
1904
1904
|
description: "Run project tests and show structured report",
|
|
1905
1905
|
usage: "/test [command|filter]",
|
|
1906
1906
|
async execute(args, _ctx) {
|
|
1907
|
-
const { executeTests } = await import("./run-tests-
|
|
1907
|
+
const { executeTests } = await import("./run-tests-TLJ53CV5.js");
|
|
1908
1908
|
const argStr = args.join(" ").trim();
|
|
1909
1909
|
let testArgs = {};
|
|
1910
1910
|
if (argStr) {
|
|
@@ -5292,7 +5292,7 @@ program.command("web").description("Start Web UI server with browser-based chat
|
|
|
5292
5292
|
console.error("Error: Invalid port number. Must be between 1 and 65535.");
|
|
5293
5293
|
process.exit(1);
|
|
5294
5294
|
}
|
|
5295
|
-
const { startWebServer } = await import("./server-
|
|
5295
|
+
const { startWebServer } = await import("./server-QG62ZDQI.js");
|
|
5296
5296
|
await startWebServer({ port, host: options.host });
|
|
5297
5297
|
});
|
|
5298
5298
|
program.command("sessions").description("List recent conversation sessions").action(async () => {
|
|
@@ -23,7 +23,7 @@ import {
|
|
|
23
23
|
setupProxy,
|
|
24
24
|
spawnAgentContext,
|
|
25
25
|
truncateOutput
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-CXWWAWE7.js";
|
|
27
27
|
import {
|
|
28
28
|
AGENTIC_BEHAVIOR_GUIDELINE,
|
|
29
29
|
CONTEXT_FILE_CANDIDATES,
|
|
@@ -35,7 +35,7 @@ import {
|
|
|
35
35
|
PLAN_MODE_SYSTEM_ADDON,
|
|
36
36
|
SKILLS_DIR_NAME,
|
|
37
37
|
VERSION
|
|
38
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-KYHUMNQO.js";
|
|
39
39
|
|
|
40
40
|
// src/web/server.ts
|
|
41
41
|
import express from "express";
|
|
@@ -63,6 +63,8 @@ var ToolExecutorWeb = class {
|
|
|
63
63
|
pendingBatchConfirms = /* @__PURE__ */ new Map();
|
|
64
64
|
/** Publicly readable by SessionHandler to check if confirm is active */
|
|
65
65
|
confirming = false;
|
|
66
|
+
/** Track tool start times for duration calculation */
|
|
67
|
+
toolStartTimes = /* @__PURE__ */ new Map();
|
|
66
68
|
setRoundInfo(current, total) {
|
|
67
69
|
this.round = current;
|
|
68
70
|
this.totalRounds = total;
|
|
@@ -109,6 +111,8 @@ var ToolExecutorWeb = class {
|
|
|
109
111
|
}
|
|
110
112
|
sendToolCallStart(call) {
|
|
111
113
|
const dangerLevel = getDangerLevel(call.name, call.arguments);
|
|
114
|
+
const startTime = Date.now();
|
|
115
|
+
this.toolStartTimes.set(call.id, startTime);
|
|
112
116
|
const msg = {
|
|
113
117
|
type: "tool_call_start",
|
|
114
118
|
callId: call.id,
|
|
@@ -116,17 +120,22 @@ var ToolExecutorWeb = class {
|
|
|
116
120
|
args: call.arguments,
|
|
117
121
|
dangerLevel,
|
|
118
122
|
round: this.round,
|
|
119
|
-
totalRounds: this.totalRounds
|
|
123
|
+
totalRounds: this.totalRounds,
|
|
124
|
+
startTime
|
|
120
125
|
};
|
|
121
126
|
this.send(msg);
|
|
122
127
|
}
|
|
123
128
|
sendToolCallResult(call, content, isError) {
|
|
129
|
+
const startTime = this.toolStartTimes.get(call.id);
|
|
130
|
+
const durationMs = startTime ? Date.now() - startTime : void 0;
|
|
131
|
+
this.toolStartTimes.delete(call.id);
|
|
124
132
|
const msg = {
|
|
125
133
|
type: "tool_call_result",
|
|
126
134
|
callId: call.id,
|
|
127
135
|
toolName: call.name,
|
|
128
136
|
content: content.length > 500 ? content.slice(0, 500) + "..." : content,
|
|
129
|
-
isError
|
|
137
|
+
isError,
|
|
138
|
+
durationMs
|
|
130
139
|
};
|
|
131
140
|
this.send(msg);
|
|
132
141
|
}
|
package/dist/web/client/app.js
CHANGED
|
@@ -17,6 +17,7 @@ let pendingImages = []; // { name, data (base64), mime }
|
|
|
17
17
|
let inputHistory = []; // Previous user inputs for ↑/↓ navigation
|
|
18
18
|
let historyIndex = -1; // -1 = not browsing history
|
|
19
19
|
let savedInputDraft = ''; // Saved current input when entering history mode
|
|
20
|
+
let toolTimers = new Map(); // callId → { startTime, intervalId }
|
|
20
21
|
|
|
21
22
|
// ── DOM refs ───────────────────────────────────────────────────────
|
|
22
23
|
|
|
@@ -153,8 +154,8 @@ function handleServerMessage(msg) {
|
|
|
153
154
|
case 'export_data': handleExportData(msg); break;
|
|
154
155
|
case 'memory_content': handleMemoryContent(msg); break;
|
|
155
156
|
case 'info': addInfoMessage(msg.message); break;
|
|
156
|
-
case 'error': addErrorMessage(msg.message); setProcessing(false); break;
|
|
157
|
-
case 'round_progress': break;
|
|
157
|
+
case 'error': addErrorMessage(msg.message); hideRoundProgress(); clearAllToolTimers(); setProcessing(false); break;
|
|
158
|
+
case 'round_progress': handleRoundProgress(msg); break;
|
|
158
159
|
}
|
|
159
160
|
}
|
|
160
161
|
|
|
@@ -186,6 +187,8 @@ function handleResponseDone(msg) {
|
|
|
186
187
|
|
|
187
188
|
currentAssistantEl = null;
|
|
188
189
|
currentAssistantContent = '';
|
|
190
|
+
hideRoundProgress();
|
|
191
|
+
clearAllToolTimers();
|
|
189
192
|
setProcessing(false);
|
|
190
193
|
scrollToBottom();
|
|
191
194
|
}
|
|
@@ -208,6 +211,7 @@ function handleToolCallStart(msg) {
|
|
|
208
211
|
<summary class="flex items-center gap-2 w-full cursor-pointer select-none py-1">
|
|
209
212
|
<span class="badge ${levelBadge} badge-sm gap-1">${levelIcon} ${escapeHtml(msg.toolName)}</span>
|
|
210
213
|
<span class="text-xs opacity-50">${msg.round}/${msg.totalRounds}</span>
|
|
214
|
+
<span class="tool-timer timing" data-call-id="${msg.callId}">⏱ 0.0s</span>
|
|
211
215
|
<span class="tool-result-badge text-xs ml-auto"></span>
|
|
212
216
|
</summary>
|
|
213
217
|
<div class="tool-details-body pt-1">
|
|
@@ -215,12 +219,42 @@ function handleToolCallStart(msg) {
|
|
|
215
219
|
</div>
|
|
216
220
|
`;
|
|
217
221
|
messagesEl.appendChild(el);
|
|
222
|
+
|
|
223
|
+
// Start live timer
|
|
224
|
+
const startTime = msg.startTime || Date.now();
|
|
225
|
+
const timerEl = el.querySelector(`[data-call-id="${msg.callId}"]`);
|
|
226
|
+
const intervalId = setInterval(() => {
|
|
227
|
+
const elapsed = (Date.now() - startTime) / 1000;
|
|
228
|
+
if (timerEl) timerEl.textContent = `⏱ ${elapsed.toFixed(1)}s`;
|
|
229
|
+
}, 100);
|
|
230
|
+
toolTimers.set(msg.callId, { startTime, intervalId });
|
|
231
|
+
|
|
218
232
|
scrollToBottom();
|
|
219
233
|
}
|
|
220
234
|
|
|
221
235
|
function handleToolCallResult(msg) {
|
|
236
|
+
// Stop live timer
|
|
237
|
+
const timer = toolTimers.get(msg.callId);
|
|
238
|
+
if (timer) {
|
|
239
|
+
clearInterval(timer.intervalId);
|
|
240
|
+
toolTimers.delete(msg.callId);
|
|
241
|
+
}
|
|
242
|
+
|
|
222
243
|
const el = document.getElementById(`tool-${msg.callId}`);
|
|
223
244
|
if (el) {
|
|
245
|
+
// Replace timer with final duration
|
|
246
|
+
const timerEl = el.querySelector(`[data-call-id="${msg.callId}"]`);
|
|
247
|
+
if (timerEl && msg.durationMs != null) {
|
|
248
|
+
const secs = msg.durationMs / 1000;
|
|
249
|
+
const speedClass = secs < 1 ? 'fast' : secs < 5 ? 'medium' : 'slow';
|
|
250
|
+
timerEl.className = `tool-duration ${speedClass}`;
|
|
251
|
+
timerEl.textContent = formatDuration(msg.durationMs);
|
|
252
|
+
} else if (timerEl) {
|
|
253
|
+
// No duration data — just clear the timer
|
|
254
|
+
timerEl.className = 'tool-duration';
|
|
255
|
+
timerEl.textContent = '';
|
|
256
|
+
}
|
|
257
|
+
|
|
224
258
|
// Add result inside the details body
|
|
225
259
|
const body = el.querySelector('.tool-details-body');
|
|
226
260
|
if (body) {
|
|
@@ -308,6 +342,38 @@ function handleAskUserRequest(msg) {
|
|
|
308
342
|
setTimeout(() => document.getElementById(`ask-input-${msg.requestId}`)?.focus(), 100);
|
|
309
343
|
}
|
|
310
344
|
|
|
345
|
+
function handleRoundProgress(msg) {
|
|
346
|
+
const progressBar = document.getElementById('round-progress');
|
|
347
|
+
const progressBarEl = document.getElementById('round-progress-bar');
|
|
348
|
+
const progressLabel = document.getElementById('round-progress-label');
|
|
349
|
+
if (!progressBar || !progressBarEl || !progressLabel) return;
|
|
350
|
+
|
|
351
|
+
progressBar.classList.remove('hidden');
|
|
352
|
+
progressLabel.textContent = `Round ${msg.current}/${msg.total}`;
|
|
353
|
+
progressBarEl.value = Math.round((msg.current / msg.total) * 100);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function hideRoundProgress() {
|
|
357
|
+
const progressBar = document.getElementById('round-progress');
|
|
358
|
+
if (progressBar) progressBar.classList.add('hidden');
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function formatDuration(ms) {
|
|
362
|
+
if (ms < 1000) return `${ms}ms`;
|
|
363
|
+
if (ms < 10000) return `${(ms / 1000).toFixed(1)}s`;
|
|
364
|
+
if (ms < 60000) return `${(ms / 1000).toFixed(0)}s`;
|
|
365
|
+
const mins = Math.floor(ms / 60000);
|
|
366
|
+
const secs = Math.round((ms % 60000) / 1000);
|
|
367
|
+
return `${mins}m${secs}s`;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function clearAllToolTimers() {
|
|
371
|
+
for (const { intervalId } of toolTimers.values()) {
|
|
372
|
+
clearInterval(intervalId);
|
|
373
|
+
}
|
|
374
|
+
toolTimers.clear();
|
|
375
|
+
}
|
|
376
|
+
|
|
311
377
|
function handleThinkingStart() {
|
|
312
378
|
const details = document.createElement('details');
|
|
313
379
|
details.className = 'thinking-block my-1 collapse collapse-arrow bg-base-200';
|
|
@@ -421,9 +487,21 @@ window.respondAskUser = function(requestId) {
|
|
|
421
487
|
}
|
|
422
488
|
};
|
|
423
489
|
|
|
490
|
+
// DaisyUI light themes → highlight.js light stylesheet; others → dark
|
|
491
|
+
const LIGHT_DAISYUI_THEMES = new Set(['light', 'cupcake', 'bumblebee', 'emerald', 'corporate', 'garden', 'lofi', 'pastel', 'fantasy', 'wireframe', 'cmyk', 'autumn', 'acid', 'lemonade', 'winter', 'nord']);
|
|
492
|
+
const HLJS_CDN = 'https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles';
|
|
493
|
+
|
|
494
|
+
function updateCodeTheme(daisyTheme) {
|
|
495
|
+
const isLight = LIGHT_DAISYUI_THEMES.has(daisyTheme);
|
|
496
|
+
const hljsFile = isLight ? 'github.min.css' : 'github-dark.min.css';
|
|
497
|
+
const link = document.getElementById('hljs-theme');
|
|
498
|
+
if (link) link.href = `${HLJS_CDN}/${hljsFile}`;
|
|
499
|
+
}
|
|
500
|
+
|
|
424
501
|
window.setTheme = function(theme) {
|
|
425
502
|
document.documentElement.setAttribute('data-theme', theme);
|
|
426
503
|
localStorage.setItem('aicli-theme', theme);
|
|
504
|
+
updateCodeTheme(theme);
|
|
427
505
|
};
|
|
428
506
|
|
|
429
507
|
// ── UI helpers ─────────────────────────────────────────────────────
|
|
@@ -1178,6 +1256,189 @@ function handleMemoryContent(msg) {
|
|
|
1178
1256
|
scrollToBottom();
|
|
1179
1257
|
}
|
|
1180
1258
|
|
|
1259
|
+
// ── Prompt Templates ───────────────────────────────────────────────
|
|
1260
|
+
|
|
1261
|
+
const TEMPLATES_KEY = 'aicli-templates';
|
|
1262
|
+
|
|
1263
|
+
function loadTemplates() {
|
|
1264
|
+
try {
|
|
1265
|
+
return JSON.parse(localStorage.getItem(TEMPLATES_KEY) || '[]');
|
|
1266
|
+
} catch { return []; }
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
function saveTemplatesToStorage(templates) {
|
|
1270
|
+
localStorage.setItem(TEMPLATES_KEY, JSON.stringify(templates));
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
function openTemplatesModal() {
|
|
1274
|
+
const modal = document.getElementById('templates-modal');
|
|
1275
|
+
if (!modal) return;
|
|
1276
|
+
cancelTemplateForm();
|
|
1277
|
+
renderTemplateList();
|
|
1278
|
+
document.getElementById('template-search').value = '';
|
|
1279
|
+
modal.showModal();
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
function renderTemplateList(filter) {
|
|
1283
|
+
const listEl = document.getElementById('template-list');
|
|
1284
|
+
if (!listEl) return;
|
|
1285
|
+
const templates = loadTemplates();
|
|
1286
|
+
const q = (filter || '').toLowerCase();
|
|
1287
|
+
const filtered = q
|
|
1288
|
+
? templates.filter(t => t.name.toLowerCase().includes(q) || (t.tags || []).some(tag => tag.toLowerCase().includes(q)) || t.content.toLowerCase().includes(q))
|
|
1289
|
+
: templates;
|
|
1290
|
+
|
|
1291
|
+
if (filtered.length === 0) {
|
|
1292
|
+
listEl.innerHTML = `<div class="template-empty">${q ? 'No matching templates' : 'No templates yet. Click + New to create one.'}</div>`;
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
listEl.innerHTML = filtered.map(t => {
|
|
1297
|
+
const preview = t.content.length > 80 ? t.content.slice(0, 80) + '...' : t.content;
|
|
1298
|
+
const tagsHtml = (t.tags || []).map(tag => `<span class="template-item-tag">${escapeHtml(tag)}</span>`).join('');
|
|
1299
|
+
return `
|
|
1300
|
+
<div class="template-item" data-tpl-id="${t.id}" onclick="useTemplate('${t.id}')">
|
|
1301
|
+
<div class="template-item-body">
|
|
1302
|
+
<div class="template-item-name">${escapeHtml(t.name)}</div>
|
|
1303
|
+
<div class="template-item-preview">${escapeHtml(preview)}</div>
|
|
1304
|
+
${tagsHtml ? `<div class="template-item-tags">${tagsHtml}</div>` : ''}
|
|
1305
|
+
</div>
|
|
1306
|
+
<div class="template-item-actions">
|
|
1307
|
+
<button class="btn btn-xs btn-ghost" onclick="event.stopPropagation(); editTemplate('${t.id}')" title="Edit">✏️</button>
|
|
1308
|
+
<button class="btn btn-xs btn-ghost" onclick="event.stopPropagation(); deleteTemplate('${t.id}')" title="Delete">🗑️</button>
|
|
1309
|
+
</div>
|
|
1310
|
+
</div>`;
|
|
1311
|
+
}).join('');
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function useTemplate(id) {
|
|
1315
|
+
const templates = loadTemplates();
|
|
1316
|
+
const tpl = templates.find(t => t.id === id);
|
|
1317
|
+
if (!tpl) return;
|
|
1318
|
+
userInput.value = tpl.content;
|
|
1319
|
+
userInput.focus();
|
|
1320
|
+
userInput.style.height = 'auto';
|
|
1321
|
+
userInput.style.height = Math.min(userInput.scrollHeight, 200) + 'px';
|
|
1322
|
+
document.getElementById('templates-modal')?.close();
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
function showAddTemplate() {
|
|
1326
|
+
const form = document.getElementById('template-form');
|
|
1327
|
+
form.classList.remove('hidden');
|
|
1328
|
+
document.getElementById('tpl-edit-id').value = '';
|
|
1329
|
+
document.getElementById('tpl-name').value = '';
|
|
1330
|
+
document.getElementById('tpl-content').value = '';
|
|
1331
|
+
document.getElementById('tpl-tags').value = '';
|
|
1332
|
+
document.getElementById('tpl-name').focus();
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
function editTemplate(id) {
|
|
1336
|
+
const templates = loadTemplates();
|
|
1337
|
+
const tpl = templates.find(t => t.id === id);
|
|
1338
|
+
if (!tpl) return;
|
|
1339
|
+
const form = document.getElementById('template-form');
|
|
1340
|
+
form.classList.remove('hidden');
|
|
1341
|
+
document.getElementById('tpl-edit-id').value = tpl.id;
|
|
1342
|
+
document.getElementById('tpl-name').value = tpl.name;
|
|
1343
|
+
document.getElementById('tpl-content').value = tpl.content;
|
|
1344
|
+
document.getElementById('tpl-tags').value = (tpl.tags || []).join(', ');
|
|
1345
|
+
document.getElementById('tpl-name').focus();
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
function cancelTemplateForm() {
|
|
1349
|
+
document.getElementById('template-form')?.classList.add('hidden');
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
function saveTemplateForm() {
|
|
1353
|
+
const name = document.getElementById('tpl-name').value.trim();
|
|
1354
|
+
const content = document.getElementById('tpl-content').value.trim();
|
|
1355
|
+
const tagsRaw = document.getElementById('tpl-tags').value.trim();
|
|
1356
|
+
const editId = document.getElementById('tpl-edit-id').value;
|
|
1357
|
+
|
|
1358
|
+
if (!name || !content) return;
|
|
1359
|
+
|
|
1360
|
+
const tags = tagsRaw ? tagsRaw.split(',').map(s => s.trim()).filter(Boolean) : [];
|
|
1361
|
+
const templates = loadTemplates();
|
|
1362
|
+
|
|
1363
|
+
if (editId) {
|
|
1364
|
+
const idx = templates.findIndex(t => t.id === editId);
|
|
1365
|
+
if (idx >= 0) {
|
|
1366
|
+
templates[idx].name = name;
|
|
1367
|
+
templates[idx].content = content;
|
|
1368
|
+
templates[idx].tags = tags;
|
|
1369
|
+
}
|
|
1370
|
+
} else {
|
|
1371
|
+
templates.unshift({
|
|
1372
|
+
id: 'tpl-' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6),
|
|
1373
|
+
name,
|
|
1374
|
+
content,
|
|
1375
|
+
tags,
|
|
1376
|
+
createdAt: new Date().toISOString(),
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
saveTemplatesToStorage(templates);
|
|
1381
|
+
cancelTemplateForm();
|
|
1382
|
+
renderTemplateList();
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
function deleteTemplate(id) {
|
|
1386
|
+
const templates = loadTemplates().filter(t => t.id !== id);
|
|
1387
|
+
saveTemplatesToStorage(templates);
|
|
1388
|
+
renderTemplateList(document.getElementById('template-search')?.value);
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
function exportTemplates() {
|
|
1392
|
+
const templates = loadTemplates();
|
|
1393
|
+
if (templates.length === 0) return;
|
|
1394
|
+
const blob = new Blob([JSON.stringify(templates, null, 2)], { type: 'application/json' });
|
|
1395
|
+
const url = URL.createObjectURL(blob);
|
|
1396
|
+
const a = document.createElement('a');
|
|
1397
|
+
a.href = url;
|
|
1398
|
+
a.download = `aicli-templates-${new Date().toISOString().slice(0, 10)}.json`;
|
|
1399
|
+
a.click();
|
|
1400
|
+
URL.revokeObjectURL(url);
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
function importTemplates() {
|
|
1404
|
+
const input = document.createElement('input');
|
|
1405
|
+
input.type = 'file';
|
|
1406
|
+
input.accept = '.json';
|
|
1407
|
+
input.onchange = () => {
|
|
1408
|
+
const file = input.files[0];
|
|
1409
|
+
if (!file) return;
|
|
1410
|
+
const reader = new FileReader();
|
|
1411
|
+
reader.onload = () => {
|
|
1412
|
+
try {
|
|
1413
|
+
const imported = JSON.parse(reader.result);
|
|
1414
|
+
if (!Array.isArray(imported)) throw new Error('Not an array');
|
|
1415
|
+
const existing = loadTemplates();
|
|
1416
|
+
const existingIds = new Set(existing.map(t => t.id));
|
|
1417
|
+
let added = 0;
|
|
1418
|
+
for (const t of imported) {
|
|
1419
|
+
if (t.id && t.name && t.content && !existingIds.has(t.id)) {
|
|
1420
|
+
existing.push(t);
|
|
1421
|
+
added++;
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
saveTemplatesToStorage(existing);
|
|
1425
|
+
renderTemplateList();
|
|
1426
|
+
addInfoMessage(`📥 Imported ${added} template(s).`);
|
|
1427
|
+
} catch {
|
|
1428
|
+
addErrorMessage('Failed to import templates: invalid JSON format.');
|
|
1429
|
+
}
|
|
1430
|
+
};
|
|
1431
|
+
reader.readAsText(file);
|
|
1432
|
+
};
|
|
1433
|
+
input.click();
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
// Template button + search binding
|
|
1437
|
+
document.getElementById('btn-templates')?.addEventListener('click', openTemplatesModal);
|
|
1438
|
+
document.getElementById('template-search')?.addEventListener('input', (e) => {
|
|
1439
|
+
renderTemplateList(e.target.value);
|
|
1440
|
+
});
|
|
1441
|
+
|
|
1181
1442
|
// ── File Tree ──────────────────────────────────────────────────────
|
|
1182
1443
|
|
|
1183
1444
|
const fileTreeEl = document.getElementById('file-tree');
|
|
@@ -1355,9 +1616,12 @@ if (btnFileTreeRefresh) {
|
|
|
1355
1616
|
|
|
1356
1617
|
// ── Initialize ─────────────────────────────────────────────────────
|
|
1357
1618
|
|
|
1358
|
-
// Restore theme
|
|
1619
|
+
// Restore theme + sync code highlight
|
|
1359
1620
|
const savedTheme = localStorage.getItem('aicli-theme');
|
|
1360
|
-
if (savedTheme)
|
|
1621
|
+
if (savedTheme) {
|
|
1622
|
+
document.documentElement.setAttribute('data-theme', savedTheme);
|
|
1623
|
+
updateCodeTheme(savedTheme);
|
|
1624
|
+
}
|
|
1361
1625
|
|
|
1362
1626
|
connect();
|
|
1363
1627
|
userInput.focus();
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
10
10
|
<!-- Markdown + Code highlighting -->
|
|
11
11
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
12
|
-
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css">
|
|
12
|
+
<link id="hljs-theme" rel="stylesheet" href="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/styles/github-dark.min.css">
|
|
13
13
|
<script src="https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.9.0/build/highlight.min.js"></script>
|
|
14
14
|
<link rel="stylesheet" href="style.css">
|
|
15
15
|
</head>
|
|
@@ -88,7 +88,14 @@
|
|
|
88
88
|
</aside>
|
|
89
89
|
|
|
90
90
|
<!-- Chat Area -->
|
|
91
|
-
<main id="chat-area" class="flex-1 overflow-y-auto px-4 py-4">
|
|
91
|
+
<main id="chat-area" class="flex-1 overflow-y-auto px-4 py-4 relative">
|
|
92
|
+
<!-- Round progress bar (sticky top, hidden by default) -->
|
|
93
|
+
<div id="round-progress" class="round-progress-bar hidden">
|
|
94
|
+
<div class="round-progress-inner">
|
|
95
|
+
<span id="round-progress-label" class="round-progress-label">Round 1/25</span>
|
|
96
|
+
<progress id="round-progress-bar" class="progress progress-primary progress-sm flex-1" value="0" max="100"></progress>
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
92
99
|
<div id="messages" class="max-w-4xl mx-auto flex flex-col gap-3">
|
|
93
100
|
<!-- Welcome message -->
|
|
94
101
|
<div class="chat chat-start">
|
|
@@ -105,6 +112,9 @@
|
|
|
105
112
|
<!-- ── Input Area ─────────────────────────────────── -->
|
|
106
113
|
<footer class="bg-base-200 border-t border-base-content/10 px-4 py-3 flex-shrink-0">
|
|
107
114
|
<div class="max-w-4xl mx-auto flex gap-2 items-end">
|
|
115
|
+
<button id="btn-templates" class="btn btn-ghost btn-square btn-sm self-center" title="Prompt templates">
|
|
116
|
+
<span class="text-lg">📝</span>
|
|
117
|
+
</button>
|
|
108
118
|
<textarea id="user-input"
|
|
109
119
|
class="textarea textarea-bordered flex-1 min-h-[2.75rem] max-h-[200px] resize-none leading-relaxed"
|
|
110
120
|
placeholder="Type a message... (Shift+Enter for newline)"
|
|
@@ -130,6 +140,43 @@
|
|
|
130
140
|
|
|
131
141
|
</div>
|
|
132
142
|
|
|
143
|
+
<!-- ── Prompt Templates Modal ───────────────────────── -->
|
|
144
|
+
<dialog id="templates-modal" class="modal">
|
|
145
|
+
<div class="modal-box max-w-2xl bg-base-200">
|
|
146
|
+
<div class="flex items-center justify-between mb-3">
|
|
147
|
+
<h3 class="font-bold text-lg">📝 Prompt Templates</h3>
|
|
148
|
+
<div class="flex gap-1">
|
|
149
|
+
<button class="btn btn-xs btn-ghost" onclick="importTemplates()" title="Import">📥 Import</button>
|
|
150
|
+
<button class="btn btn-xs btn-ghost" onclick="exportTemplates()" title="Export">📤 Export</button>
|
|
151
|
+
<button class="btn btn-xs btn-ghost" onclick="showAddTemplate()" title="New template">+ New</button>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<!-- Add/Edit form (hidden by default) -->
|
|
156
|
+
<div id="template-form" class="hidden mb-3 p-3 bg-base-300 rounded-lg">
|
|
157
|
+
<input id="tpl-name" type="text" class="input input-sm input-bordered w-full mb-2" placeholder="Template name">
|
|
158
|
+
<textarea id="tpl-content" class="textarea textarea-bordered w-full text-sm mb-2" rows="4" placeholder="Prompt content..."></textarea>
|
|
159
|
+
<input id="tpl-tags" type="text" class="input input-sm input-bordered w-full mb-2" placeholder="Tags (comma-separated, optional)">
|
|
160
|
+
<div class="flex gap-2 justify-end">
|
|
161
|
+
<button class="btn btn-sm btn-ghost" onclick="cancelTemplateForm()">Cancel</button>
|
|
162
|
+
<button class="btn btn-sm btn-primary" onclick="saveTemplateForm()">Save</button>
|
|
163
|
+
</div>
|
|
164
|
+
<input type="hidden" id="tpl-edit-id">
|
|
165
|
+
</div>
|
|
166
|
+
|
|
167
|
+
<!-- Search -->
|
|
168
|
+
<input id="template-search" type="text" class="input input-sm input-bordered w-full mb-3" placeholder="Search templates...">
|
|
169
|
+
|
|
170
|
+
<!-- Template list -->
|
|
171
|
+
<div id="template-list" class="flex flex-col gap-1 max-h-[50vh] overflow-y-auto"></div>
|
|
172
|
+
|
|
173
|
+
<div class="modal-action mt-3">
|
|
174
|
+
<form method="dialog"><button class="btn btn-sm">Close</button></form>
|
|
175
|
+
</div>
|
|
176
|
+
</div>
|
|
177
|
+
<form method="dialog" class="modal-backdrop"><button>close</button></form>
|
|
178
|
+
</dialog>
|
|
179
|
+
|
|
133
180
|
<script src="app.js"></script>
|
|
134
181
|
</body>
|
|
135
182
|
</html>
|
|
@@ -453,6 +453,112 @@
|
|
|
453
453
|
font-family: 'Fira Code', 'JetBrains Mono', 'Consolas', monospace;
|
|
454
454
|
}
|
|
455
455
|
|
|
456
|
+
/* ── Round progress bar (sticky top of chat area) ──── */
|
|
457
|
+
.round-progress-bar {
|
|
458
|
+
position: sticky;
|
|
459
|
+
top: 0;
|
|
460
|
+
z-index: 10;
|
|
461
|
+
padding: 0.4rem 1rem;
|
|
462
|
+
background: oklch(var(--b2) / 0.92);
|
|
463
|
+
backdrop-filter: blur(8px);
|
|
464
|
+
border-bottom: 1px solid oklch(var(--bc) / 0.1);
|
|
465
|
+
max-width: 64rem;
|
|
466
|
+
margin: -1rem auto 0.5rem;
|
|
467
|
+
}
|
|
468
|
+
.round-progress-inner {
|
|
469
|
+
display: flex;
|
|
470
|
+
align-items: center;
|
|
471
|
+
gap: 0.75rem;
|
|
472
|
+
}
|
|
473
|
+
.round-progress-label {
|
|
474
|
+
font-size: 0.75rem;
|
|
475
|
+
font-weight: 600;
|
|
476
|
+
opacity: 0.7;
|
|
477
|
+
white-space: nowrap;
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/* ── Tool duration & timer ─────────────────────────── */
|
|
481
|
+
.tool-timer {
|
|
482
|
+
font-size: 0.7rem;
|
|
483
|
+
font-family: 'Fira Code', 'JetBrains Mono', 'Consolas', monospace;
|
|
484
|
+
opacity: 0.5;
|
|
485
|
+
white-space: nowrap;
|
|
486
|
+
}
|
|
487
|
+
.tool-timer.timing {
|
|
488
|
+
color: oklch(var(--wa));
|
|
489
|
+
opacity: 0.7;
|
|
490
|
+
}
|
|
491
|
+
.tool-duration {
|
|
492
|
+
font-size: 0.7rem;
|
|
493
|
+
font-family: 'Fira Code', 'JetBrains Mono', 'Consolas', monospace;
|
|
494
|
+
opacity: 0.6;
|
|
495
|
+
white-space: nowrap;
|
|
496
|
+
}
|
|
497
|
+
.tool-duration.fast { color: oklch(var(--su)); }
|
|
498
|
+
.tool-duration.medium { color: oklch(var(--wa)); }
|
|
499
|
+
.tool-duration.slow { color: oklch(var(--er)); }
|
|
500
|
+
|
|
501
|
+
/* ── Prompt templates ──────────────────────────────── */
|
|
502
|
+
.template-item {
|
|
503
|
+
display: flex;
|
|
504
|
+
align-items: flex-start;
|
|
505
|
+
gap: 0.5rem;
|
|
506
|
+
padding: 0.5rem 0.75rem;
|
|
507
|
+
border-radius: 0.375rem;
|
|
508
|
+
cursor: pointer;
|
|
509
|
+
transition: background 0.15s;
|
|
510
|
+
border: 1px solid oklch(var(--bc) / 0.08);
|
|
511
|
+
}
|
|
512
|
+
.template-item:hover {
|
|
513
|
+
background: oklch(var(--b3));
|
|
514
|
+
border-color: oklch(var(--p) / 0.3);
|
|
515
|
+
}
|
|
516
|
+
.template-item-body {
|
|
517
|
+
flex: 1;
|
|
518
|
+
min-width: 0;
|
|
519
|
+
}
|
|
520
|
+
.template-item-name {
|
|
521
|
+
font-weight: 600;
|
|
522
|
+
font-size: 0.9rem;
|
|
523
|
+
}
|
|
524
|
+
.template-item-preview {
|
|
525
|
+
font-size: 0.78rem;
|
|
526
|
+
opacity: 0.55;
|
|
527
|
+
white-space: nowrap;
|
|
528
|
+
overflow: hidden;
|
|
529
|
+
text-overflow: ellipsis;
|
|
530
|
+
max-width: 100%;
|
|
531
|
+
}
|
|
532
|
+
.template-item-tags {
|
|
533
|
+
display: flex;
|
|
534
|
+
gap: 0.25rem;
|
|
535
|
+
flex-wrap: wrap;
|
|
536
|
+
margin-top: 0.2rem;
|
|
537
|
+
}
|
|
538
|
+
.template-item-tag {
|
|
539
|
+
font-size: 0.65rem;
|
|
540
|
+
padding: 0.05rem 0.35rem;
|
|
541
|
+
border-radius: 0.25rem;
|
|
542
|
+
background: oklch(var(--p) / 0.12);
|
|
543
|
+
color: oklch(var(--p));
|
|
544
|
+
}
|
|
545
|
+
.template-item-actions {
|
|
546
|
+
display: flex;
|
|
547
|
+
gap: 0.25rem;
|
|
548
|
+
flex-shrink: 0;
|
|
549
|
+
opacity: 0;
|
|
550
|
+
transition: opacity 0.15s;
|
|
551
|
+
}
|
|
552
|
+
.template-item:hover .template-item-actions {
|
|
553
|
+
opacity: 1;
|
|
554
|
+
}
|
|
555
|
+
.template-empty {
|
|
556
|
+
text-align: center;
|
|
557
|
+
padding: 2rem;
|
|
558
|
+
opacity: 0.4;
|
|
559
|
+
font-size: 0.85rem;
|
|
560
|
+
}
|
|
561
|
+
|
|
456
562
|
/* ── Responsive ─────────────────────────────────────── */
|
|
457
563
|
@media (max-width: 768px) {
|
|
458
564
|
.sidebar { width: 0; padding: 0; border: none; }
|