vg-coder-cli 2.0.45 → 2.0.47
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/INTEGRATION.md +418 -0
- package/debug.log +42 -0
- package/dist/vg-coder-bundle.js +50 -44
- package/package.json +2 -1
- package/src/server/api-server.js +355 -2
- package/src/server/task-queue.js +705 -0
- package/src/server/task-store.js +112 -0
- package/src/server/task-webhook.js +48 -0
- package/src/server/views/css/agent-panel.css +259 -3
- package/src/server/views/css/code-viewer.css +158 -3
- package/src/server/views/js/features/agent-panel.js +248 -12
- package/src/server/views/js/features/code-viewer.js +18 -3
- package/src/server/views/js/features/git-view.js +1 -1
- package/src/server/views/js/features/mermaid-viewer.js +494 -0
- package/src/server/views/js/features/resize.js +1 -1
- package/src/server/views/js/features/task-worker.js +448 -0
- package/src/server/views/js/main.js +4 -0
- package/src/server/views/vg-coder/background.js +17860 -11946
- package/src/server/views/vg-coder/controller.js +42 -10
- package/src/server/views/vg-coder/manifest.json +2 -1
- package/src/server/views/vg-coder/sidepanel.js +13 -7
- package/test-large/package.json +1 -0
- package/test-large/src/components/Button.tsx +1 -0
- package/test-large/src/index.ts +1 -0
- package/test-small/package.json +1 -0
- package/test-small/src/index.js +1 -0
|
@@ -4,9 +4,12 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { getById } from '../utils.js';
|
|
7
|
+
import { API_BASE } from '../config.js';
|
|
7
8
|
// Import markdown-it and mermaid from npm packages (bundled by webpack)
|
|
8
9
|
import markdownit from 'markdown-it';
|
|
9
10
|
import mermaid from 'mermaid';
|
|
11
|
+
// Import mermaid viewer for fullscreen functionality
|
|
12
|
+
import { createMermaidToolbar, openMermaidViewer, showToast as showMermaidToast } from './mermaid-viewer.js';
|
|
10
13
|
|
|
11
14
|
// State
|
|
12
15
|
let messages = [];
|
|
@@ -100,6 +103,19 @@ async function renderAgentPanel() {
|
|
|
100
103
|
<path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path>
|
|
101
104
|
</svg>
|
|
102
105
|
</button>
|
|
106
|
+
<button class="agent-btn" id="agent-history-btn" title="Chat history">
|
|
107
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
108
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
109
|
+
<polyline points="12 6 12 12 16 14"></polyline>
|
|
110
|
+
</svg>
|
|
111
|
+
</button>
|
|
112
|
+
<button class="agent-btn" id="agent-export-btn" title="Export current chat as .json">
|
|
113
|
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
114
|
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path>
|
|
115
|
+
<polyline points="7 10 12 15 17 10"></polyline>
|
|
116
|
+
<line x1="12" y1="15" x2="12" y2="3"></line>
|
|
117
|
+
</svg>
|
|
118
|
+
</button>
|
|
103
119
|
<button class="agent-btn" id="agent-clear-btn" title="Clear chat">
|
|
104
120
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
105
121
|
<polyline points="3 6 5 6 21 6"></polyline>
|
|
@@ -218,6 +234,18 @@ function attachEventListeners() {
|
|
|
218
234
|
clearBtn.addEventListener('click', handleClearChat);
|
|
219
235
|
}
|
|
220
236
|
|
|
237
|
+
// History button
|
|
238
|
+
const historyBtn = getById('agent-history-btn');
|
|
239
|
+
if (historyBtn) {
|
|
240
|
+
historyBtn.addEventListener('click', openHistoryModal);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Export button
|
|
244
|
+
const exportBtn = getById('agent-export-btn');
|
|
245
|
+
if (exportBtn) {
|
|
246
|
+
exportBtn.addEventListener('click', exportCurrentChat);
|
|
247
|
+
}
|
|
248
|
+
|
|
221
249
|
// Drag & drop
|
|
222
250
|
const dropZone = getById('agent-input-wrapper');
|
|
223
251
|
if (dropZone) {
|
|
@@ -473,17 +501,29 @@ async function processMermaidDiagrams(container) {
|
|
|
473
501
|
const id = `agent-mermaid-${Date.now()}-${i}`;
|
|
474
502
|
const { svg, bindFunctions } = await mermaid.render(id, code);
|
|
475
503
|
|
|
476
|
-
// Create wrapper div for mermaid
|
|
504
|
+
// Create wrapper div for mermaid with toolbar
|
|
477
505
|
const wrapper = document.createElement('div');
|
|
478
506
|
wrapper.className = 'agent-mermaid';
|
|
479
|
-
wrapper.
|
|
507
|
+
wrapper.style.cssText = 'position: relative; margin: 16px 0; background: #161b22; border-radius: 6px; overflow: hidden;';
|
|
508
|
+
|
|
509
|
+
// Create toolbar using mermaid-viewer module
|
|
510
|
+
const toolbar = createMermaidToolbar(code, svg);
|
|
511
|
+
|
|
512
|
+
// Create diagram container
|
|
513
|
+
const diagramContainer = document.createElement('div');
|
|
514
|
+
diagramContainer.className = 'agent-mermaid-diagram';
|
|
515
|
+
diagramContainer.style.cssText = 'padding: 20px; display: flex; justify-content: center; align-items: center; overflow-x: auto;';
|
|
516
|
+
diagramContainer.innerHTML = svg;
|
|
517
|
+
|
|
518
|
+
wrapper.appendChild(toolbar);
|
|
519
|
+
wrapper.appendChild(diagramContainer);
|
|
480
520
|
|
|
481
521
|
// Replace pre/code with wrapper
|
|
482
522
|
preElement.replaceWith(wrapper);
|
|
483
523
|
|
|
484
524
|
// Bind any interactive functions if present
|
|
485
525
|
if (bindFunctions) {
|
|
486
|
-
bindFunctions(
|
|
526
|
+
bindFunctions(diagramContainer);
|
|
487
527
|
}
|
|
488
528
|
|
|
489
529
|
console.log(`[AgentPanel] Rendered mermaid diagram ${i + 1}`);
|
|
@@ -613,7 +653,7 @@ function addMessage(role, content, status = 'done') {
|
|
|
613
653
|
timestamp: new Date().toLocaleTimeString('vi-VN')
|
|
614
654
|
});
|
|
615
655
|
renderMessages();
|
|
616
|
-
|
|
656
|
+
scheduleAutoSave();
|
|
617
657
|
}
|
|
618
658
|
|
|
619
659
|
/**
|
|
@@ -623,30 +663,225 @@ function updateLastMessage(updates) {
|
|
|
623
663
|
if (messages.length === 0) return;
|
|
624
664
|
Object.assign(messages[messages.length - 1], updates);
|
|
625
665
|
renderMessages();
|
|
626
|
-
|
|
666
|
+
scheduleAutoSave();
|
|
627
667
|
}
|
|
628
668
|
|
|
629
669
|
/**
|
|
630
|
-
*
|
|
631
|
-
* No manual save needed
|
|
670
|
+
* Debounced auto-save to .vg/chats/<id>.json
|
|
632
671
|
*/
|
|
672
|
+
function scheduleAutoSave() {
|
|
673
|
+
if (autoSaveTimeout) clearTimeout(autoSaveTimeout);
|
|
674
|
+
autoSaveTimeout = setTimeout(saveCurrentChat, 800);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
async function saveCurrentChat() {
|
|
678
|
+
if (messages.length === 0) return;
|
|
679
|
+
|
|
680
|
+
// Resolve chat ID: prefer URL-derived, fallback to local timestamp ID kept across saves
|
|
681
|
+
if (!currentChatId) {
|
|
682
|
+
currentChatId = window.AIChat?.getChatIdFromUrl?.() || `local-${Date.now()}`;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
const source = inferChatSource();
|
|
686
|
+
const title = deriveChatTitle();
|
|
687
|
+
|
|
688
|
+
try {
|
|
689
|
+
const res = await fetch(`${API_BASE}/api/chats/${encodeURIComponent(currentChatId)}`, {
|
|
690
|
+
method: 'POST',
|
|
691
|
+
headers: { 'Content-Type': 'application/json' },
|
|
692
|
+
body: JSON.stringify({ source, title, messages })
|
|
693
|
+
});
|
|
694
|
+
if (!res.ok) console.warn('[AgentPanel] Save failed:', res.status);
|
|
695
|
+
} catch (err) {
|
|
696
|
+
console.warn('[AgentPanel] Save error:', err);
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
function inferChatSource() {
|
|
701
|
+
try {
|
|
702
|
+
const host = window.location?.hostname || '';
|
|
703
|
+
if (host.includes('aistudio.google.com')) return 'aistudio';
|
|
704
|
+
if (host.includes('chatgpt.com') || host.includes('chat.openai.com')) return 'chatgpt';
|
|
705
|
+
if (host.includes('claude.ai')) return 'claude';
|
|
706
|
+
return 'unknown';
|
|
707
|
+
} catch (_) { return 'unknown'; }
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
function deriveChatTitle() {
|
|
711
|
+
const firstUser = messages.find(m => m.role === 'user');
|
|
712
|
+
const raw = (firstUser?.content || '').replace(/\n+/g, ' ').trim();
|
|
713
|
+
return raw.length > 80 ? raw.slice(0, 80) + '…' : (raw || '(untitled)');
|
|
714
|
+
}
|
|
633
715
|
|
|
634
716
|
/**
|
|
635
|
-
* Handle clear chat
|
|
717
|
+
* Handle clear chat — deletes server-side too
|
|
636
718
|
*/
|
|
637
|
-
function handleClearChat() {
|
|
719
|
+
async function handleClearChat() {
|
|
638
720
|
if (messages.length === 0) return;
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
721
|
+
if (!confirm('Clear chat history?')) return;
|
|
722
|
+
|
|
723
|
+
if (currentChatId) {
|
|
724
|
+
try {
|
|
725
|
+
await fetch(`${API_BASE}/api/chats/${encodeURIComponent(currentChatId)}`, { method: 'DELETE' });
|
|
726
|
+
console.log(`[AgentPanel] Deleted chat ${currentChatId}`);
|
|
727
|
+
} catch (err) {
|
|
728
|
+
console.warn('[AgentPanel] Delete error:', err);
|
|
729
|
+
}
|
|
642
730
|
}
|
|
643
731
|
|
|
732
|
+
currentChatId = null;
|
|
644
733
|
messages = [];
|
|
645
734
|
selectedFiles = [];
|
|
646
735
|
renderFileList();
|
|
647
736
|
renderMessages();
|
|
648
737
|
}
|
|
649
738
|
|
|
739
|
+
/**
|
|
740
|
+
* Export current chat to a downloadable .json file
|
|
741
|
+
*/
|
|
742
|
+
function exportCurrentChat() {
|
|
743
|
+
if (messages.length === 0) {
|
|
744
|
+
alert('Chưa có tin nhắn để export.');
|
|
745
|
+
return;
|
|
746
|
+
}
|
|
747
|
+
const data = {
|
|
748
|
+
id: currentChatId || `local-${Date.now()}`,
|
|
749
|
+
source: inferChatSource(),
|
|
750
|
+
title: deriveChatTitle(),
|
|
751
|
+
exportedAt: Date.now(),
|
|
752
|
+
messages
|
|
753
|
+
};
|
|
754
|
+
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
|
|
755
|
+
const url = URL.createObjectURL(blob);
|
|
756
|
+
const safeId = String(data.id).replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 80);
|
|
757
|
+
const a = document.createElement('a');
|
|
758
|
+
a.href = url;
|
|
759
|
+
a.download = `chat-${safeId}.json`;
|
|
760
|
+
document.body.appendChild(a);
|
|
761
|
+
a.click();
|
|
762
|
+
document.body.removeChild(a);
|
|
763
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* History modal — list saved chats, click row to load
|
|
768
|
+
*/
|
|
769
|
+
let _activeHistoryModal = null;
|
|
770
|
+
let _activeHistoryEscHandler = null;
|
|
771
|
+
|
|
772
|
+
async function openHistoryModal() {
|
|
773
|
+
closeHistoryModal();
|
|
774
|
+
|
|
775
|
+
// Mount inside the Agent tool panel so it sits within the panel layout
|
|
776
|
+
// and never gets covered by host-page UI or other shadow-root overlays.
|
|
777
|
+
const host = getById('tool-panel-agent') || getById('agent-panel-content')
|
|
778
|
+
|| window.__VG_CODER_ROOT__ || document.body;
|
|
779
|
+
|
|
780
|
+
const modal = document.createElement('div');
|
|
781
|
+
modal.id = 'agent-history-modal';
|
|
782
|
+
modal.className = 'agent-history-modal agent-history-modal-inline';
|
|
783
|
+
modal.innerHTML = `
|
|
784
|
+
<div class="agent-history-pane">
|
|
785
|
+
<div class="agent-history-header">
|
|
786
|
+
<span>Chat History</span>
|
|
787
|
+
<button class="agent-history-close" title="Back to chat" type="button">←</button>
|
|
788
|
+
</div>
|
|
789
|
+
<div class="agent-history-list" id="agent-history-list">
|
|
790
|
+
<div class="agent-history-empty">⏳ Loading…</div>
|
|
791
|
+
</div>
|
|
792
|
+
</div>
|
|
793
|
+
`;
|
|
794
|
+
host.appendChild(modal);
|
|
795
|
+
_activeHistoryModal = modal;
|
|
796
|
+
|
|
797
|
+
const close = (e) => {
|
|
798
|
+
if (e) { e.preventDefault(); e.stopPropagation(); }
|
|
799
|
+
closeHistoryModal();
|
|
800
|
+
};
|
|
801
|
+
|
|
802
|
+
modal.querySelector('.agent-history-close').addEventListener('click', close);
|
|
803
|
+
|
|
804
|
+
_activeHistoryEscHandler = (e) => { if (e.key === 'Escape') close(e); };
|
|
805
|
+
document.addEventListener('keydown', _activeHistoryEscHandler);
|
|
806
|
+
|
|
807
|
+
try {
|
|
808
|
+
const res = await fetch(`${API_BASE}/api/chats`);
|
|
809
|
+
const data = await res.json();
|
|
810
|
+
const list = data.chats || [];
|
|
811
|
+
const listEl = modal.querySelector('#agent-history-list');
|
|
812
|
+
|
|
813
|
+
if (!list.length) {
|
|
814
|
+
listEl.innerHTML = `<div class="agent-history-empty">No saved chats yet.</div>`;
|
|
815
|
+
return;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
listEl.innerHTML = list.map(c => `
|
|
819
|
+
<div class="agent-history-row" data-id="${escapeHtml(c.id)}">
|
|
820
|
+
<div class="agent-history-row-main">
|
|
821
|
+
<div class="agent-history-title">${escapeHtml(c.title)}</div>
|
|
822
|
+
<div class="agent-history-meta">
|
|
823
|
+
<span>${escapeHtml(c.source)}</span>
|
|
824
|
+
<span>${c.count} msgs</span>
|
|
825
|
+
<span>${new Date(c.updatedAt || 0).toLocaleString('vi-VN')}</span>
|
|
826
|
+
</div>
|
|
827
|
+
</div>
|
|
828
|
+
<button class="agent-history-delete" data-id="${escapeHtml(c.id)}" title="Delete" type="button">×</button>
|
|
829
|
+
</div>
|
|
830
|
+
`).join('');
|
|
831
|
+
|
|
832
|
+
listEl.querySelectorAll('.agent-history-row').forEach(row => {
|
|
833
|
+
row.addEventListener('click', async (e) => {
|
|
834
|
+
if (e.target.classList.contains('agent-history-delete')) return;
|
|
835
|
+
await loadChatById(row.dataset.id);
|
|
836
|
+
closeHistoryModal();
|
|
837
|
+
});
|
|
838
|
+
});
|
|
839
|
+
listEl.querySelectorAll('.agent-history-delete').forEach(btn => {
|
|
840
|
+
btn.addEventListener('click', async (e) => {
|
|
841
|
+
e.stopPropagation();
|
|
842
|
+
if (!confirm('Delete this chat?')) return;
|
|
843
|
+
await fetch(`${API_BASE}/api/chats/${encodeURIComponent(btn.dataset.id)}`, { method: 'DELETE' });
|
|
844
|
+
openHistoryModal();
|
|
845
|
+
});
|
|
846
|
+
});
|
|
847
|
+
} catch (err) {
|
|
848
|
+
console.error('[AgentPanel] Failed to load history list:', err);
|
|
849
|
+
const listEl = modal.querySelector('#agent-history-list');
|
|
850
|
+
if (listEl) {
|
|
851
|
+
listEl.innerHTML = `<div class="agent-history-empty">❌ ${escapeHtml(err.message)}</div>`;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function closeHistoryModal() {
|
|
857
|
+
if (_activeHistoryEscHandler) {
|
|
858
|
+
document.removeEventListener('keydown', _activeHistoryEscHandler);
|
|
859
|
+
_activeHistoryEscHandler = null;
|
|
860
|
+
}
|
|
861
|
+
if (_activeHistoryModal && _activeHistoryModal.parentNode) {
|
|
862
|
+
_activeHistoryModal.parentNode.removeChild(_activeHistoryModal);
|
|
863
|
+
}
|
|
864
|
+
_activeHistoryModal = null;
|
|
865
|
+
// Defensive cleanup in case ref was lost
|
|
866
|
+
const stale = (window.__VG_CODER_ROOT__ || document).querySelector('#agent-history-modal');
|
|
867
|
+
if (stale) stale.remove();
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
async function loadChatById(id) {
|
|
871
|
+
try {
|
|
872
|
+
const res = await fetch(`${API_BASE}/api/chats/${encodeURIComponent(id)}`);
|
|
873
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
874
|
+
const data = await res.json();
|
|
875
|
+
currentChatId = data.id;
|
|
876
|
+
messages = Array.isArray(data.messages) ? data.messages : [];
|
|
877
|
+
renderMessages();
|
|
878
|
+
console.log(`[AgentPanel] Loaded chat ${id} (${messages.length} msgs)`);
|
|
879
|
+
} catch (err) {
|
|
880
|
+
console.error('[AgentPanel] Load chat failed:', err);
|
|
881
|
+
alert(`Load chat failed: ${err.message}`);
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
|
|
650
885
|
/**
|
|
651
886
|
* Handle add files
|
|
652
887
|
*/
|
|
@@ -756,3 +991,4 @@ function escapeHtml(text) {
|
|
|
756
991
|
div.textContent = text;
|
|
757
992
|
return div.innerHTML;
|
|
758
993
|
}
|
|
994
|
+
|
|
@@ -6,6 +6,8 @@ import hljs from 'highlight.js/lib/core';
|
|
|
6
6
|
import markdownit from 'markdown-it';
|
|
7
7
|
// Import mermaid for rendering diagrams
|
|
8
8
|
import mermaid from 'mermaid';
|
|
9
|
+
// Import mermaid viewer for fullscreen functionality
|
|
10
|
+
import { createMermaidToolbar, openMermaidViewer, showToast as showMermaidToast } from './mermaid-viewer.js';
|
|
9
11
|
|
|
10
12
|
// Import common languages to reduce bundle size
|
|
11
13
|
import javascript from 'highlight.js/lib/languages/javascript';
|
|
@@ -80,17 +82,29 @@ async function renderMermaidDiagrams(container) {
|
|
|
80
82
|
const id = `mermaid-diagram-${Date.now()}-${i}`;
|
|
81
83
|
const { svg, bindFunctions } = await mermaid.render(id, code);
|
|
82
84
|
|
|
83
|
-
// Create wrapper div for mermaid
|
|
85
|
+
// Create wrapper div for mermaid with toolbar
|
|
84
86
|
const wrapper = document.createElement('div');
|
|
85
87
|
wrapper.className = 'mermaid-diagram';
|
|
86
|
-
wrapper.
|
|
88
|
+
wrapper.style.cssText = 'position: relative; margin: 16px 0; background: #161b22; border-radius: 6px; overflow: hidden;';
|
|
89
|
+
|
|
90
|
+
// Create toolbar using mermaid-viewer module
|
|
91
|
+
const toolbar = createMermaidToolbar(code, svg);
|
|
92
|
+
|
|
93
|
+
// Create diagram container
|
|
94
|
+
const diagramContainer = document.createElement('div');
|
|
95
|
+
diagramContainer.className = 'mermaid-diagram-content';
|
|
96
|
+
diagramContainer.style.cssText = 'padding: 20px; display: flex; justify-content: center; align-items: center; overflow-x: auto;';
|
|
97
|
+
diagramContainer.innerHTML = svg;
|
|
98
|
+
|
|
99
|
+
wrapper.appendChild(toolbar);
|
|
100
|
+
wrapper.appendChild(diagramContainer);
|
|
87
101
|
|
|
88
102
|
// Replace pre/code with wrapper
|
|
89
103
|
pre.replaceWith(wrapper);
|
|
90
104
|
|
|
91
105
|
// Bind any interactive functions if present
|
|
92
106
|
if (bindFunctions) {
|
|
93
|
-
bindFunctions(
|
|
107
|
+
bindFunctions(diagramContainer);
|
|
94
108
|
}
|
|
95
109
|
|
|
96
110
|
console.log(`[Mermaid] Successfully rendered diagram ${i + 1}`);
|
|
@@ -184,3 +198,4 @@ function escapeHtml(text) {
|
|
|
184
198
|
.replace(/"/g, """)
|
|
185
199
|
.replace(/'/g, "'");
|
|
186
200
|
}
|
|
201
|
+
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getById, qsa, showToast } from '../utils.js';
|
|
2
2
|
import { getGitDiff } from '../api.js';
|
|
3
|
-
import { Diff2HtmlUI } from 'diff2html/lib-esm/
|
|
3
|
+
import { Diff2HtmlUI } from 'diff2html/lib-esm/ui/js/diff2html-ui';
|
|
4
4
|
|
|
5
5
|
let isGitMode = false;
|
|
6
6
|
|