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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vg-coder-cli",
3
- "version": "2.0.42",
3
+ "version": "2.0.43",
4
4
  "description": "🚀 CLI tool to analyze projects, concatenate source files, count tokens, and export HTML with syntax highlighting and copy functionality",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -37,7 +37,7 @@
37
37
  justify-content: center;
38
38
  height: 100%;
39
39
  color: #52525b;
40
- gap: 10px;
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
- // Load last chat or start fresh
126
- await initializeChatSession();
124
+ // Don't auto-load - let user click reload
125
+ console.log('[AgentPanel] Panel ready, waiting for user action');
127
126
 
128
- // Render messages
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
- // Try to load last chat
213
- const lastChatId = ChatHistory.getLastChatId();
214
-
215
- if (lastChatId) {
216
- console.log(`[AgentPanel] Loading last chat: ${lastChatId}`);
217
- currentChatId = lastChatId;
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
- const chatData = ChatHistory.loadChat(currentChatId);
220
- if (chatData && chatData.messages) {
221
- messages = chatData.messages;
222
- console.log(`[AgentPanel] Loaded ${messages.length} messages`);
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
- } else {
227
- console.log('[AgentPanel] Starting fresh chat');
245
+ } catch (error) {
246
+ console.error('[AgentPanel] Failed to load history:', error);
228
247
  messages = [];
229
- currentChatId = null;
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>Kéo thả file hoặc nhập tin nhắn</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
- // Create chat ID after first message
503
+ // Get chat ID from URL (adapter manages chat ID)
368
504
  if (isFirstMessage && !currentChatId) {
369
- currentChatId = ChatHistory.generateChatId();
370
- console.log(`[AgentPanel] Created new chat: ${currentChatId}`);
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
- autoSaveChat();
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
- autoSaveChat();
586
+ // Adapter handles storage
448
587
  }
449
588
 
450
589
  /**
451
- * Auto-save chat
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
- * Clear chat
595
+ * Handle clear chat
470
596
  */
471
597
  function handleClearChat() {
472
- if (!confirm('Xóa toàn bộ lịch sử chat?')) return;
473
-
474
- if (currentChatId) {
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
- }