vg-coder-cli 2.0.42 → 2.0.43
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/package.json
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
justify-content: center;
|
|
38
38
|
height: 100%;
|
|
39
39
|
color: #52525b;
|
|
40
|
-
gap:
|
|
40
|
+
gap: 16px;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
.agent-chat-empty svg {
|
|
@@ -49,6 +49,34 @@
|
|
|
49
49
|
font-size: 13px;
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
+
.agent-reload-btn {
|
|
53
|
+
margin-top: 8px;
|
|
54
|
+
padding: 10px 20px;
|
|
55
|
+
background: #3b82f6;
|
|
56
|
+
color: white;
|
|
57
|
+
border: none;
|
|
58
|
+
border-radius: 6px;
|
|
59
|
+
font-size: 14px;
|
|
60
|
+
font-weight: 500;
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
transition: all 0.2s;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
.agent-reload-btn:hover {
|
|
66
|
+
background: #2563eb;
|
|
67
|
+
transform: translateY(-1px);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.agent-reload-btn:active {
|
|
71
|
+
transform: translateY(0);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
.agent-reload-btn:disabled {
|
|
75
|
+
opacity: 0.5;
|
|
76
|
+
cursor: not-allowed;
|
|
77
|
+
transform: none;
|
|
78
|
+
}
|
|
79
|
+
|
|
52
80
|
/* Message Container */
|
|
53
81
|
.agent-message {
|
|
54
82
|
display: flex;
|
|
@@ -258,6 +286,41 @@
|
|
|
258
286
|
cursor: not-allowed;
|
|
259
287
|
}
|
|
260
288
|
|
|
289
|
+
/* Code Block Wrapper for Run Bash Button */
|
|
290
|
+
.agent-code-block-wrapper {
|
|
291
|
+
position: relative;
|
|
292
|
+
margin: 8px 0;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
.agent-run-bash-btn {
|
|
296
|
+
position: absolute;
|
|
297
|
+
top: 8px;
|
|
298
|
+
right: 8px;
|
|
299
|
+
background: #3b82f6;
|
|
300
|
+
color: white;
|
|
301
|
+
border: none;
|
|
302
|
+
padding: 4px 12px;
|
|
303
|
+
border-radius: 4px;
|
|
304
|
+
font-size: 11px;
|
|
305
|
+
font-weight: 500;
|
|
306
|
+
cursor: pointer;
|
|
307
|
+
transition: all 0.2s;
|
|
308
|
+
z-index: 10;
|
|
309
|
+
display: flex;
|
|
310
|
+
align-items: center;
|
|
311
|
+
gap: 4px;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.agent-run-bash-btn:hover {
|
|
315
|
+
background: #2563eb;
|
|
316
|
+
transform: translateY(-1px);
|
|
317
|
+
box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.agent-run-bash-btn:active {
|
|
321
|
+
transform: translateY(0);
|
|
322
|
+
}
|
|
323
|
+
|
|
261
324
|
/* Mermaid Diagrams */
|
|
262
325
|
.agent-mermaid {
|
|
263
326
|
margin: 16px 0;
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { getById } from '../utils.js';
|
|
7
|
-
import * as ChatHistory from '../utils/chat-history-manager.js';
|
|
8
7
|
// Import markdown-it and mermaid from npm packages (bundled by webpack)
|
|
9
8
|
import markdownit from 'markdown-it';
|
|
10
9
|
import mermaid from 'mermaid';
|
|
@@ -122,10 +121,10 @@ async function renderAgentPanel() {
|
|
|
122
121
|
// Attach event listeners
|
|
123
122
|
attachEventListeners();
|
|
124
123
|
|
|
125
|
-
//
|
|
126
|
-
|
|
124
|
+
// Don't auto-load - let user click reload
|
|
125
|
+
console.log('[AgentPanel] Panel ready, waiting for user action');
|
|
127
126
|
|
|
128
|
-
// Render
|
|
127
|
+
// Render empty state
|
|
129
128
|
renderMessages();
|
|
130
129
|
|
|
131
130
|
console.log('[AgentPanel] Rendered successfully');
|
|
@@ -209,24 +208,44 @@ function attachEventListeners() {
|
|
|
209
208
|
* Initialize chat session
|
|
210
209
|
*/
|
|
211
210
|
async function initializeChatSession() {
|
|
212
|
-
//
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
211
|
+
// Check if AIChat is available
|
|
212
|
+
if (!window.AIChat) {
|
|
213
|
+
console.warn('[AgentPanel] AIChat not available yet');
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
try {
|
|
218
|
+
// Load from adapter cache (single source of truth)
|
|
219
|
+
console.log('[AgentPanel] Loading conversation history from adapter...');
|
|
220
|
+
const historyMessages = await window.AIChat.getCurrentMessages();
|
|
218
221
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
222
|
+
if (historyMessages && historyMessages.length > 0) {
|
|
223
|
+
console.log(`[AgentPanel] Loaded ${historyMessages.length} messages from adapter cache`);
|
|
224
|
+
|
|
225
|
+
// Convert history format to agent panel format
|
|
226
|
+
messages = historyMessages.map(msg => ({
|
|
227
|
+
role: msg.role,
|
|
228
|
+
content: msg.content,
|
|
229
|
+
timestamp: new Date(msg.timestamp).toLocaleTimeString(),
|
|
230
|
+
status: 'done'
|
|
231
|
+
}));
|
|
232
|
+
|
|
233
|
+
// Get chat ID from URL
|
|
234
|
+
const chatId = window.AIChat.getChatIdFromUrl?.();
|
|
235
|
+
if (chatId) {
|
|
236
|
+
currentChatId = chatId;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
renderMessages();
|
|
223
240
|
} else {
|
|
241
|
+
console.log('[AgentPanel] No conversation history found');
|
|
224
242
|
messages = [];
|
|
243
|
+
renderMessages();
|
|
225
244
|
}
|
|
226
|
-
}
|
|
227
|
-
console.
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error('[AgentPanel] Failed to load history:', error);
|
|
228
247
|
messages = [];
|
|
229
|
-
|
|
248
|
+
renderMessages();
|
|
230
249
|
}
|
|
231
250
|
}
|
|
232
251
|
|
|
@@ -243,9 +262,25 @@ function renderMessages() {
|
|
|
243
262
|
<svg width="40" height="40" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
244
263
|
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"></path>
|
|
245
264
|
</svg>
|
|
246
|
-
<span>
|
|
265
|
+
<span>No conversation loaded</span>
|
|
266
|
+
<button class="agent-reload-btn" id="agent-reload-btn">
|
|
267
|
+
🔄 Load Conversation History
|
|
268
|
+
</button>
|
|
247
269
|
</div>
|
|
248
270
|
`;
|
|
271
|
+
|
|
272
|
+
// Attach reload button listener
|
|
273
|
+
const reloadBtn = getById('agent-reload-btn');
|
|
274
|
+
if (reloadBtn) {
|
|
275
|
+
reloadBtn.addEventListener('click', async () => {
|
|
276
|
+
reloadBtn.disabled = true;
|
|
277
|
+
reloadBtn.textContent = '⏳ Loading...';
|
|
278
|
+
await initializeChatSession();
|
|
279
|
+
reloadBtn.disabled = false;
|
|
280
|
+
reloadBtn.textContent = '🔄 Load Conversation History';
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
|
|
249
284
|
return;
|
|
250
285
|
}
|
|
251
286
|
|
|
@@ -269,10 +304,111 @@ function renderMessages() {
|
|
|
269
304
|
// Process mermaid diagrams
|
|
270
305
|
processMermaidDiagrams(container);
|
|
271
306
|
|
|
307
|
+
// Add run bash buttons
|
|
308
|
+
addRunBashButtons(container);
|
|
309
|
+
|
|
272
310
|
// Scroll to bottom
|
|
273
311
|
container.scrollTop = container.scrollHeight;
|
|
274
312
|
}
|
|
275
313
|
|
|
314
|
+
/**
|
|
315
|
+
* Add "Run Bash" buttons to bash code blocks
|
|
316
|
+
*/
|
|
317
|
+
function addRunBashButtons(container) {
|
|
318
|
+
const bashCodeBlocks = container.querySelectorAll('code.language-bash');
|
|
319
|
+
if (bashCodeBlocks.length === 0) return;
|
|
320
|
+
|
|
321
|
+
console.log(`[AgentPanel] Found ${bashCodeBlocks.length} bash code block(s)`);
|
|
322
|
+
|
|
323
|
+
bashCodeBlocks.forEach((codeBlock, index) => {
|
|
324
|
+
const preElement = codeBlock.parentElement;
|
|
325
|
+
if (!preElement || preElement.tagName !== 'PRE') return;
|
|
326
|
+
|
|
327
|
+
// Check if button already exists
|
|
328
|
+
if (preElement.querySelector('.agent-run-bash-btn')) return;
|
|
329
|
+
|
|
330
|
+
// Create button
|
|
331
|
+
const button = document.createElement('button');
|
|
332
|
+
button.className = 'agent-run-bash-btn';
|
|
333
|
+
button.innerHTML = '▶ Run Bash';
|
|
334
|
+
button.title = 'Run bash command in terminal';
|
|
335
|
+
|
|
336
|
+
// Click handler
|
|
337
|
+
button.addEventListener('click', async (e) => {
|
|
338
|
+
e.preventDefault();
|
|
339
|
+
e.stopPropagation();
|
|
340
|
+
|
|
341
|
+
const code = codeBlock.textContent || '';
|
|
342
|
+
if (!code.trim()) {
|
|
343
|
+
console.warn('[AgentPanel] No bash code found');
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
console.log('[AgentPanel] Run bash triggered');
|
|
348
|
+
|
|
349
|
+
// Copy to clipboard
|
|
350
|
+
try {
|
|
351
|
+
await navigator.clipboard.writeText(code);
|
|
352
|
+
} catch (err) {
|
|
353
|
+
console.error('[AgentPanel] Clipboard copy failed:', err);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Dispatch event
|
|
358
|
+
dispatchPasteRun(code);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// Wrap pre in container if not already
|
|
362
|
+
if (!preElement.parentElement.classList.contains('agent-code-block-wrapper')) {
|
|
363
|
+
const wrapper = document.createElement('div');
|
|
364
|
+
wrapper.className = 'agent-code-block-wrapper';
|
|
365
|
+
preElement.parentNode.insertBefore(wrapper, preElement);
|
|
366
|
+
wrapper.appendChild(preElement);
|
|
367
|
+
wrapper.appendChild(button);
|
|
368
|
+
} else {
|
|
369
|
+
preElement.parentElement.appendChild(button);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
console.log(`[AgentPanel] Run bash button added (${index + 1})`);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Dispatch paste-run event for bash execution
|
|
378
|
+
*/
|
|
379
|
+
function dispatchPasteRun(code) {
|
|
380
|
+
const EVENT_TYPE = 'vg:paste-run';
|
|
381
|
+
|
|
382
|
+
// Use global dispatcher if available
|
|
383
|
+
const dispatcher = window.__VG_EVENT_DISPATCHER__ || window.globalDispatcher || null;
|
|
384
|
+
|
|
385
|
+
const eventPayload = {
|
|
386
|
+
type: EVENT_TYPE,
|
|
387
|
+
source: 'agent-panel',
|
|
388
|
+
target: 'bubble-runner',
|
|
389
|
+
payload: {
|
|
390
|
+
code,
|
|
391
|
+
from: 'run-bash-button',
|
|
392
|
+
},
|
|
393
|
+
context: 'window',
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
if (dispatcher?.dispatchCrossContext) {
|
|
397
|
+
dispatcher.dispatchCrossContext(eventPayload);
|
|
398
|
+
console.log('[AgentPanel] Dispatched via globalDispatcher:', EVENT_TYPE);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Fallback: CustomEvent
|
|
403
|
+
window.dispatchEvent(
|
|
404
|
+
new CustomEvent(EVENT_TYPE, {
|
|
405
|
+
detail: eventPayload,
|
|
406
|
+
})
|
|
407
|
+
);
|
|
408
|
+
|
|
409
|
+
console.log('[AgentPanel] Dispatched via CustomEvent:', EVENT_TYPE);
|
|
410
|
+
}
|
|
411
|
+
|
|
276
412
|
/**
|
|
277
413
|
* Process mermaid diagrams
|
|
278
414
|
* Uses mermaid.render() which is more compatible with shadow DOM
|
|
@@ -364,10 +500,13 @@ async function sendMessage() {
|
|
|
364
500
|
const aiResponse = await window.AIChat.copyLastTurnAsMarkdown();
|
|
365
501
|
updateLastMessage({ content: aiResponse || '(AI không trả về nội dung)', status: 'done' });
|
|
366
502
|
|
|
367
|
-
//
|
|
503
|
+
// Get chat ID from URL (adapter manages chat ID)
|
|
368
504
|
if (isFirstMessage && !currentChatId) {
|
|
369
|
-
|
|
370
|
-
|
|
505
|
+
const chatId = window.AIChat?.getChatIdFromUrl?.();
|
|
506
|
+
if (chatId) {
|
|
507
|
+
currentChatId = chatId;
|
|
508
|
+
console.log(`[AgentPanel] Using chat ID from URL: ${currentChatId}`);
|
|
509
|
+
}
|
|
371
510
|
}
|
|
372
511
|
} catch (error) {
|
|
373
512
|
console.error('[AgentPanel] Error sending message:', error);
|
|
@@ -434,7 +573,7 @@ function addMessage(role, content, status = 'done') {
|
|
|
434
573
|
timestamp: new Date().toLocaleTimeString('vi-VN')
|
|
435
574
|
});
|
|
436
575
|
renderMessages();
|
|
437
|
-
|
|
576
|
+
// Adapter auto-saves, no manual save needed
|
|
438
577
|
}
|
|
439
578
|
|
|
440
579
|
/**
|
|
@@ -444,41 +583,26 @@ function updateLastMessage(updates) {
|
|
|
444
583
|
if (messages.length === 0) return;
|
|
445
584
|
Object.assign(messages[messages.length - 1], updates);
|
|
446
585
|
renderMessages();
|
|
447
|
-
|
|
586
|
+
// Adapter handles storage
|
|
448
587
|
}
|
|
449
588
|
|
|
450
589
|
/**
|
|
451
|
-
* Auto-save
|
|
590
|
+
* Auto-save is handled by adapter
|
|
591
|
+
* No manual save needed
|
|
452
592
|
*/
|
|
453
|
-
function autoSaveChat() {
|
|
454
|
-
if (!currentChatId) return;
|
|
455
|
-
|
|
456
|
-
if (autoSaveTimeout) {
|
|
457
|
-
clearTimeout(autoSaveTimeout);
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
autoSaveTimeout = setTimeout(() => {
|
|
461
|
-
const success = ChatHistory.saveChat(currentChatId, messages);
|
|
462
|
-
if (success) {
|
|
463
|
-
console.log(`[AgentPanel] Auto-saved chat ${currentChatId}`);
|
|
464
|
-
}
|
|
465
|
-
}, 500);
|
|
466
|
-
}
|
|
467
593
|
|
|
468
594
|
/**
|
|
469
|
-
*
|
|
595
|
+
* Handle clear chat
|
|
470
596
|
*/
|
|
471
597
|
function handleClearChat() {
|
|
472
|
-
if (
|
|
473
|
-
|
|
474
|
-
if (
|
|
475
|
-
ChatHistory.deleteChat(currentChatId);
|
|
598
|
+
if (messages.length === 0) return;
|
|
599
|
+
|
|
600
|
+
if (confirm('Clear chat history?')) {
|
|
476
601
|
console.log(`[AgentPanel] Deleted chat ${currentChatId}`);
|
|
477
602
|
}
|
|
478
603
|
|
|
479
604
|
messages = [];
|
|
480
605
|
selectedFiles = [];
|
|
481
|
-
currentChatId = null;
|
|
482
606
|
renderFileList();
|
|
483
607
|
renderMessages();
|
|
484
608
|
}
|
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Chat History Manager
|
|
3
|
-
* Manages chat sessions in localStorage
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const STORAGE_PREFIX = 'vg-dashboard-chat-';
|
|
7
|
-
const METADATA_KEY = 'vg-dashboard-chat-metadata';
|
|
8
|
-
const MAX_CHAT_AGE_DAYS = 30;
|
|
9
|
-
const MAX_TITLE_LENGTH = 50;
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Generate unique chat ID
|
|
13
|
-
* @returns {string} Chat ID
|
|
14
|
-
*/
|
|
15
|
-
export function generateChatId() {
|
|
16
|
-
const timestamp = Date.now().toString(36);
|
|
17
|
-
const random = Math.random().toString(36).substring(2, 15) +
|
|
18
|
-
Math.random().toString(36).substring(2, 15);
|
|
19
|
-
return `${timestamp}${random}`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Generate chat title from messages
|
|
24
|
-
* @param {Array} messages - Chat messages
|
|
25
|
-
* @returns {string} Chat title
|
|
26
|
-
*/
|
|
27
|
-
function generateTitle(messages) {
|
|
28
|
-
if (!messages || messages.length === 0) return 'New Chat';
|
|
29
|
-
const firstUserMsg = messages.find(m => m.role === 'user');
|
|
30
|
-
if (!firstUserMsg) return 'New Chat';
|
|
31
|
-
|
|
32
|
-
let title = firstUserMsg.content.trim();
|
|
33
|
-
if (title.length > MAX_TITLE_LENGTH) {
|
|
34
|
-
title = title.substring(0, MAX_TITLE_LENGTH) + '...';
|
|
35
|
-
}
|
|
36
|
-
return title;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Get metadata
|
|
41
|
-
* @returns {object} Metadata object
|
|
42
|
-
*/
|
|
43
|
-
function getMetadata() {
|
|
44
|
-
try {
|
|
45
|
-
const data = localStorage.getItem(METADATA_KEY);
|
|
46
|
-
return data ? JSON.parse(data) : { allChatIds: [], lastAccessedChatId: null };
|
|
47
|
-
} catch (error) {
|
|
48
|
-
console.error('[ChatHistory] Failed to get metadata:', error);
|
|
49
|
-
return { allChatIds: [], lastAccessedChatId: null };
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Update metadata
|
|
55
|
-
* @param {object} updates - Updates to apply
|
|
56
|
-
*/
|
|
57
|
-
function updateMetadata(updates) {
|
|
58
|
-
const metadata = getMetadata();
|
|
59
|
-
Object.assign(metadata, updates);
|
|
60
|
-
try {
|
|
61
|
-
localStorage.setItem(METADATA_KEY, JSON.stringify(metadata));
|
|
62
|
-
} catch (error) {
|
|
63
|
-
console.error('[ChatHistory] Failed to update metadata:', error);
|
|
64
|
-
handleStorageError(error);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/**
|
|
69
|
-
* Handle storage errors
|
|
70
|
-
* @param {Error} error - Error object
|
|
71
|
-
*/
|
|
72
|
-
function handleStorageError(error) {
|
|
73
|
-
if (error.name === 'QuotaExceededError') {
|
|
74
|
-
console.warn('[ChatHistory] Storage quota exceeded, cleaning up...');
|
|
75
|
-
const deletedCount = cleanupOldChats(7);
|
|
76
|
-
if (deletedCount > 0) {
|
|
77
|
-
console.log(`[ChatHistory] Cleaned up ${deletedCount} old chats`);
|
|
78
|
-
} else {
|
|
79
|
-
console.error('[ChatHistory] Storage full and no old chats to cleanup!');
|
|
80
|
-
alert('⚠️ Bộ nhớ đã đầy! Vui lòng xóa bớt lịch sử chat cũ.');
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Save chat to localStorage
|
|
87
|
-
* @param {string} chatId - Chat ID
|
|
88
|
-
* @param {Array} messages - Messages array
|
|
89
|
-
* @param {object} options - Additional options
|
|
90
|
-
* @returns {boolean} Success status
|
|
91
|
-
*/
|
|
92
|
-
export function saveChat(chatId, messages, options = {}) {
|
|
93
|
-
if (!chatId) {
|
|
94
|
-
console.error('[ChatHistory] Cannot save chat without ID');
|
|
95
|
-
return false;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
const now = Date.now();
|
|
99
|
-
const key = STORAGE_PREFIX + chatId;
|
|
100
|
-
|
|
101
|
-
let chatData;
|
|
102
|
-
try {
|
|
103
|
-
const existing = localStorage.getItem(key);
|
|
104
|
-
chatData = existing ? JSON.parse(existing) : { id: chatId, createdAt: now };
|
|
105
|
-
} catch (error) {
|
|
106
|
-
console.error('[ChatHistory] Failed to load existing chat:', error);
|
|
107
|
-
chatData = { id: chatId, createdAt: now };
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
chatData.messages = messages;
|
|
111
|
-
chatData.updatedAt = now;
|
|
112
|
-
chatData.title = options.title || generateTitle(messages);
|
|
113
|
-
|
|
114
|
-
try {
|
|
115
|
-
localStorage.setItem(key, JSON.stringify(chatData));
|
|
116
|
-
const metadata = getMetadata();
|
|
117
|
-
if (!metadata.allChatIds.includes(chatId)) {
|
|
118
|
-
metadata.allChatIds.push(chatId);
|
|
119
|
-
}
|
|
120
|
-
metadata.lastAccessedChatId = chatId;
|
|
121
|
-
updateMetadata(metadata);
|
|
122
|
-
return true;
|
|
123
|
-
} catch (error) {
|
|
124
|
-
console.error('[ChatHistory] Failed to save chat:', error);
|
|
125
|
-
handleStorageError(error);
|
|
126
|
-
return false;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Load chat from localStorage
|
|
132
|
-
* @param {string} chatId - Chat ID
|
|
133
|
-
* @returns {object|null} Chat data
|
|
134
|
-
*/
|
|
135
|
-
export function loadChat(chatId) {
|
|
136
|
-
if (!chatId) return null;
|
|
137
|
-
|
|
138
|
-
const key = STORAGE_PREFIX + chatId;
|
|
139
|
-
try {
|
|
140
|
-
const data = localStorage.getItem(key);
|
|
141
|
-
if (!data) {
|
|
142
|
-
console.warn(`[ChatHistory] Chat not found: ${chatId}`);
|
|
143
|
-
return null;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
const chatData = JSON.parse(data);
|
|
147
|
-
if (!chatData.messages || !Array.isArray(chatData.messages)) {
|
|
148
|
-
console.error('[ChatHistory] Invalid chat data structure');
|
|
149
|
-
return null;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
updateMetadata({ lastAccessedChatId: chatId });
|
|
153
|
-
|
|
154
|
-
return {
|
|
155
|
-
messages: chatData.messages,
|
|
156
|
-
metadata: {
|
|
157
|
-
title: chatData.title,
|
|
158
|
-
createdAt: chatData.createdAt,
|
|
159
|
-
updatedAt: chatData.updatedAt,
|
|
160
|
-
}
|
|
161
|
-
};
|
|
162
|
-
} catch (error) {
|
|
163
|
-
console.error('[ChatHistory] Failed to load chat:', error);
|
|
164
|
-
return null;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Delete chat from localStorage
|
|
170
|
-
* @param {string} chatId - Chat ID
|
|
171
|
-
* @returns {boolean} Success status
|
|
172
|
-
*/
|
|
173
|
-
export function deleteChat(chatId) {
|
|
174
|
-
if (!chatId) return false;
|
|
175
|
-
|
|
176
|
-
const key = STORAGE_PREFIX + chatId;
|
|
177
|
-
try {
|
|
178
|
-
localStorage.removeItem(key);
|
|
179
|
-
const metadata = getMetadata();
|
|
180
|
-
metadata.allChatIds = metadata.allChatIds.filter(id => id !== chatId);
|
|
181
|
-
if (metadata.lastAccessedChatId === chatId) {
|
|
182
|
-
metadata.lastAccessedChatId = null;
|
|
183
|
-
}
|
|
184
|
-
updateMetadata(metadata);
|
|
185
|
-
return true;
|
|
186
|
-
} catch (error) {
|
|
187
|
-
console.error('[ChatHistory] Failed to delete chat:', error);
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Cleanup old chats
|
|
194
|
-
* @param {number} maxAgeDays - Max age in days
|
|
195
|
-
* @returns {number} Number of chats deleted
|
|
196
|
-
*/
|
|
197
|
-
export function cleanupOldChats(maxAgeDays = MAX_CHAT_AGE_DAYS) {
|
|
198
|
-
const cutoffTime = Date.now() - (maxAgeDays * 24 * 60 * 60 * 1000);
|
|
199
|
-
const metadata = getMetadata();
|
|
200
|
-
let deletedCount = 0;
|
|
201
|
-
|
|
202
|
-
for (const chatId of [...metadata.allChatIds]) {
|
|
203
|
-
const key = STORAGE_PREFIX + chatId;
|
|
204
|
-
try {
|
|
205
|
-
const data = localStorage.getItem(key);
|
|
206
|
-
if (data) {
|
|
207
|
-
const chatData = JSON.parse(data);
|
|
208
|
-
if (chatData.updatedAt < cutoffTime) {
|
|
209
|
-
deleteChat(chatId);
|
|
210
|
-
deletedCount++;
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
} catch (error) {
|
|
214
|
-
console.error(`[ChatHistory] Failed to check chat ${chatId}:`, error);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
return deletedCount;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Get all chat IDs
|
|
223
|
-
* @returns {Array<string>} Array of chat IDs
|
|
224
|
-
*/
|
|
225
|
-
export function getAllChatIds() {
|
|
226
|
-
const metadata = getMetadata();
|
|
227
|
-
return metadata.allChatIds;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/**
|
|
231
|
-
* Get last accessed chat ID
|
|
232
|
-
* @returns {string|null} Last chat ID
|
|
233
|
-
*/
|
|
234
|
-
export function getLastChatId() {
|
|
235
|
-
const metadata = getMetadata();
|
|
236
|
-
return metadata.lastAccessedChatId;
|
|
237
|
-
}
|