vg-coder-cli 2.0.31 → 2.0.32
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/ARCHITECTURE.md +255 -0
- package/README.md +0 -11
- package/change.sh +0 -0
- package/dist/vg-coder-bundle.js +42 -0
- package/gulpfile.js +111 -0
- package/package.json +19 -11
- package/src/index.js +28 -220
- package/src/server/api-server.js +120 -428
- package/src/server/views/css/bubble.css +81 -0
- package/src/server/views/css/code-viewer.css +58 -0
- package/src/server/views/css/terminal.css +59 -155
- package/src/server/views/dashboard.css +78 -678
- package/src/server/views/dashboard.html +39 -278
- package/src/server/views/js/api.js +2 -22
- package/src/server/views/js/config.js +27 -15
- package/src/server/views/js/event-protocol.js +263 -0
- package/src/server/views/js/features/bubble-features/index.js +125 -0
- package/src/server/views/js/features/bubble-features/paste-run-feature.js +16 -0
- package/src/server/views/js/features/bubble-features/terminal-feature.js +16 -0
- package/src/server/views/js/features/bubble.js +175 -0
- package/src/server/views/js/features/code-viewer.js +90 -0
- package/src/server/views/js/features/commands.js +34 -81
- package/src/server/views/js/features/editor-tabs.js +19 -46
- package/src/server/views/js/features/git-view.js +63 -81
- package/src/server/views/js/features/iframe-manager.js +3 -97
- package/src/server/views/js/features/monaco-manager.js +19 -39
- package/src/server/views/js/features/project-switcher.js +7 -63
- package/src/server/views/js/features/resize.js +5 -16
- package/src/server/views/js/features/structure.js +38 -106
- package/src/server/views/js/features/terminal.js +102 -418
- package/src/server/views/js/handlers.js +60 -43
- package/src/server/views/js/main.js +75 -179
- package/src/server/views/js/shadow-entry.js +21 -0
- package/src/server/views/js/utils.js +48 -28
- package/src/server/views/vg-coder/_metadata/generated_indexed_rulesets/_ruleset1 +0 -0
- package/src/server/views/vg-coder/controller.js +33 -258
- package/vetgo-auto/chrome/src/utils/injector-script.ts +33 -258
- package/vetgo-auto/vg-coder.zip +0 -0
- package/src/server/views/dashboard.js +0 -457
- package/test-pty.js +0 -31
|
@@ -1,26 +1,19 @@
|
|
|
1
|
-
|
|
1
|
+
import { io } from 'socket.io-client';
|
|
2
|
+
import { Terminal } from 'xterm';
|
|
3
|
+
import { FitAddon } from 'xterm-addon-fit';
|
|
4
|
+
import { getById, showToast } from '../utils.js';
|
|
2
5
|
|
|
3
6
|
let socket;
|
|
4
|
-
const activeTerminals = new Map();
|
|
5
|
-
|
|
6
|
-
// Z-Index Management
|
|
7
|
+
const activeTerminals = new Map();
|
|
7
8
|
let maxZIndex = 10001;
|
|
8
9
|
|
|
9
10
|
export function initTerminal() {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
return;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
// 1. Init Socket
|
|
16
|
-
socket = io();
|
|
11
|
+
// Force connect to localhost
|
|
12
|
+
socket = io('http://localhost:6868');
|
|
17
13
|
|
|
18
|
-
// 2. Global Event Listeners
|
|
19
14
|
socket.on('terminal:data', ({ termId, data }) => {
|
|
20
15
|
const session = activeTerminals.get(termId);
|
|
21
|
-
if (session)
|
|
22
|
-
session.term.write(data);
|
|
23
|
-
}
|
|
16
|
+
if (session) session.term.write(data);
|
|
24
17
|
});
|
|
25
18
|
|
|
26
19
|
socket.on('terminal:exit', ({ termId }) => {
|
|
@@ -28,497 +21,188 @@ export function initTerminal() {
|
|
|
28
21
|
});
|
|
29
22
|
}
|
|
30
23
|
|
|
31
|
-
/**
|
|
32
|
-
* Tạo một cửa sổ terminal mới
|
|
33
|
-
*/
|
|
34
24
|
export function createNewTerminal() {
|
|
35
25
|
const termId = 'term_' + Date.now();
|
|
36
|
-
const layer =
|
|
26
|
+
const layer = getById('floating-terminals-layer');
|
|
37
27
|
|
|
38
|
-
|
|
28
|
+
if (!layer) {
|
|
29
|
+
console.error('Terminal layer not found');
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
39
33
|
const wrapper = document.createElement('div');
|
|
40
34
|
wrapper.className = 'floating-terminal';
|
|
41
35
|
wrapper.id = `wrapper-${termId}`;
|
|
42
36
|
|
|
43
|
-
//
|
|
37
|
+
// Position offset
|
|
44
38
|
const offset = (activeTerminals.size % 10) * 30;
|
|
45
39
|
wrapper.style.top = `${100 + offset}px`;
|
|
46
40
|
wrapper.style.left = `${400 + offset}px`;
|
|
47
41
|
wrapper.style.zIndex = ++maxZIndex;
|
|
48
42
|
|
|
49
|
-
// HTML
|
|
43
|
+
// --- HTML STRUCTURE: Header + Body + Separate Input ---
|
|
50
44
|
wrapper.innerHTML = `
|
|
51
|
-
<div class="terminal-header" id="header-${termId}"
|
|
52
|
-
<div class="terminal-title-group">
|
|
53
|
-
<span>>_</span> Terminal (${activeTerminals.size + 1})
|
|
54
|
-
</div>
|
|
55
|
-
|
|
56
|
-
<!-- Copy Button Group -->
|
|
57
|
-
<div class="terminal-copy-group" id="copy-group-${termId}">
|
|
58
|
-
<button class="copy-btn copy-smart" onclick="window.copyTerminalLog('${termId}', 'smart')" title="Smart Copy (optimized for 3000 tokens)">
|
|
59
|
-
🧠 <span class="token-badge" id="badge-smart-${termId}">0</span>
|
|
60
|
-
</button>
|
|
61
|
-
<button class="copy-btn copy-errors" onclick="window.copyTerminalLog('${termId}', 'errors')" title="Errors Only (with context)">
|
|
62
|
-
⚠️ <span class="token-badge" id="badge-errors-${termId}">0</span>
|
|
63
|
-
</button>
|
|
64
|
-
<button class="copy-btn copy-recent" onclick="window.copyTerminalLog('${termId}', 'recent')" title="Recent 200 lines">
|
|
65
|
-
📄 <span class="token-badge" id="badge-recent-${termId}">0</span>
|
|
66
|
-
</button>
|
|
67
|
-
<button class="copy-btn copy-all" onclick="window.copyTerminalLog('${termId}', 'all')" title="Copy All">
|
|
68
|
-
📦 <span class="token-badge" id="badge-all-${termId}">0</span>
|
|
69
|
-
</button>
|
|
70
|
-
</div>
|
|
71
|
-
|
|
72
|
-
<!-- Clear Button -->
|
|
73
|
-
<button class="term-btn-clear" onclick="window.clearTerminal('${termId}')" title="Clear Terminal">
|
|
74
|
-
🗑️
|
|
75
|
-
</button>
|
|
76
|
-
|
|
45
|
+
<div class="terminal-header" id="header-${termId}">
|
|
46
|
+
<div class="terminal-title-group"><span>>_</span> Terminal (${activeTerminals.size + 1})</div>
|
|
77
47
|
<div class="terminal-controls">
|
|
78
|
-
<button class="term-btn minimize" onclick="window.toggleMinimize('${termId}')"
|
|
79
|
-
<button class="term-btn maximize" onclick="window.toggleMaximize('${termId}')"
|
|
80
|
-
<button class="term-btn close" onclick="window.closeTerminal('${termId}')"
|
|
48
|
+
<button class="term-btn minimize" onclick="window.toggleMinimize('${termId}')">-</button>
|
|
49
|
+
<button class="term-btn maximize" onclick="window.toggleMaximize('${termId}')">+</button>
|
|
50
|
+
<button class="term-btn close" onclick="window.closeTerminal('${termId}')">x</button>
|
|
81
51
|
</div>
|
|
82
52
|
</div>
|
|
83
53
|
<div class="terminal-body" id="body-${termId}"></div>
|
|
54
|
+
<div class="terminal-input-row">
|
|
55
|
+
<span class="terminal-prompt">➜</span>
|
|
56
|
+
<input type="text" class="terminal-input" id="input-${termId}" placeholder="Type command here..." autocomplete="off" spellcheck="false" />
|
|
57
|
+
</div>
|
|
84
58
|
`;
|
|
85
59
|
|
|
86
60
|
layer.appendChild(wrapper);
|
|
87
61
|
|
|
88
|
-
//
|
|
62
|
+
// --- XTERM CONFIG: Disable Direct Input ---
|
|
89
63
|
const term = new Terminal({
|
|
90
64
|
cursorBlink: true,
|
|
91
65
|
fontSize: 13,
|
|
92
66
|
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
|
|
93
|
-
theme: {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
67
|
+
theme: { background: '#1e1e1e', foreground: '#f0f0f0' },
|
|
68
|
+
disableStdin: true, // CRITICAL: Stop typing directly into xterm
|
|
69
|
+
rows: 18 // Slightly less to make room for input row
|
|
97
70
|
});
|
|
98
71
|
|
|
99
|
-
const fitAddon = new FitAddon
|
|
72
|
+
const fitAddon = new FitAddon();
|
|
100
73
|
term.loadAddon(fitAddon);
|
|
101
74
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
// 3. Register Events
|
|
75
|
+
const bodyEl = wrapper.querySelector(`#body-${termId}`);
|
|
76
|
+
term.open(bodyEl);
|
|
106
77
|
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
78
|
+
// Fit Logic
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
try {
|
|
81
|
+
fitAddon.fit();
|
|
82
|
+
const cols = term.cols || 80;
|
|
83
|
+
const rows = term.rows || 18;
|
|
84
|
+
|
|
85
|
+
fetch('http://localhost:6868/api/projects')
|
|
86
|
+
.then(res => res.json())
|
|
87
|
+
.then(data => {
|
|
88
|
+
socket.emit('terminal:init', { termId, cols, rows, projectId: data.activeProjectId });
|
|
89
|
+
})
|
|
90
|
+
.catch(() => {
|
|
91
|
+
socket.emit('terminal:init', { termId, cols, rows });
|
|
92
|
+
});
|
|
93
|
+
} catch(e) {}
|
|
94
|
+
}, 100);
|
|
95
|
+
|
|
96
|
+
// --- INPUT HANDLING ---
|
|
97
|
+
const inputEl = wrapper.querySelector(`#input-${termId}`);
|
|
98
|
+
|
|
99
|
+
inputEl.addEventListener('keydown', (e) => {
|
|
100
|
+
// Handle ENTER: Send command
|
|
101
|
+
if (e.key === 'Enter') {
|
|
102
|
+
const command = inputEl.value;
|
|
103
|
+
// Send to server with carriage return
|
|
104
|
+
socket.emit('terminal:input', { termId, data: command + '\r' });
|
|
105
|
+
|
|
106
|
+
// Clear input
|
|
107
|
+
inputEl.value = '';
|
|
108
|
+
|
|
109
|
+
// Scroll xterm to bottom to see result
|
|
110
|
+
term.scrollToBottom();
|
|
129
111
|
}
|
|
130
|
-
});
|
|
131
|
-
resizeObserver.observe(document.getElementById(`body-${termId}`));
|
|
132
|
-
|
|
133
|
-
// 4. Make Draggable
|
|
134
|
-
makeDraggable(wrapper, document.getElementById(`header-${termId}`));
|
|
135
|
-
|
|
136
|
-
// 5. Store Session with log buffer
|
|
137
|
-
activeTerminals.set(termId, {
|
|
138
|
-
term,
|
|
139
|
-
fitAddon,
|
|
140
|
-
element: wrapper,
|
|
141
|
-
logBuffer: [], // Local buffer for quick access
|
|
142
|
-
partialLine: '' // Buffer for incomplete lines
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
// 6. Setup log buffer capture and token count updates
|
|
146
|
-
// Listen to terminal's onData event (this fires for user input)
|
|
147
|
-
// We need to listen to the actual output from the backend
|
|
148
|
-
socket.on('terminal:data', ({ termId: dataTermId, data }) => {
|
|
149
|
-
if (dataTermId !== termId) return;
|
|
150
|
-
|
|
151
|
-
const session = activeTerminals.get(termId);
|
|
152
|
-
if (!session) return;
|
|
153
|
-
|
|
154
|
-
// Strip ANSI codes
|
|
155
|
-
const cleanData = data.replace(
|
|
156
|
-
// eslint-disable-next-line no-control-regex
|
|
157
|
-
/\x1b\[[0-9;]*[a-zA-Z]|\x1b\][^\x07]*\x07|\x1b\][^\x1b]*\x1b\\/g,
|
|
158
|
-
''
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
// Accumulate partial line
|
|
162
|
-
session.partialLine += cleanData;
|
|
163
|
-
|
|
164
|
-
// Split by newlines and process complete lines
|
|
165
|
-
const lines = session.partialLine.split(/\r?\n/);
|
|
166
112
|
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if (line.trim().length > 0) {
|
|
173
|
-
session.logBuffer.push(line);
|
|
174
|
-
// Maintain max 10000 lines
|
|
175
|
-
if (session.logBuffer.length > 10000) {
|
|
176
|
-
session.logBuffer.shift();
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
});
|
|
113
|
+
// Handle CTRL+C: Send interrupt signal
|
|
114
|
+
if (e.ctrlKey && e.key === 'c') {
|
|
115
|
+
socket.emit('terminal:input', { termId, data: '\x03' }); // ASCII ETX
|
|
116
|
+
e.preventDefault();
|
|
117
|
+
}
|
|
180
118
|
|
|
181
|
-
//
|
|
182
|
-
|
|
183
|
-
|
|
119
|
+
// Handle Arrow Up/Down for history could be implemented here locally if needed,
|
|
120
|
+
// but typically shells handle history. Since we send raw chars, remote shell history works
|
|
121
|
+
// IF we were typing in xterm. With separate input, we lose shell history navigation
|
|
122
|
+
// unless we implement a local history buffer here.
|
|
123
|
+
// For now, let's keep it simple.
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Auto-focus input when clicking anywhere on the wrapper
|
|
127
|
+
wrapper.addEventListener('mousedown', (e) => {
|
|
128
|
+
wrapper.style.zIndex = ++maxZIndex;
|
|
129
|
+
// Don't focus if clicking buttons
|
|
130
|
+
if (e.target.tagName !== 'BUTTON') {
|
|
131
|
+
// Delay focus slightly to ensure drag didn't start
|
|
132
|
+
setTimeout(() => inputEl.focus(), 10);
|
|
184
133
|
}
|
|
185
134
|
});
|
|
186
135
|
|
|
187
|
-
//
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
// Store in session
|
|
195
|
-
activeTerminals.get(termId).projectId = currentProjectId;
|
|
196
|
-
|
|
197
|
-
// Init Backend Process with projectId
|
|
198
|
-
socket.emit('terminal:init', {
|
|
199
|
-
termId,
|
|
200
|
-
cols: term.cols,
|
|
201
|
-
rows: term.rows,
|
|
202
|
-
projectId: currentProjectId
|
|
203
|
-
});
|
|
204
|
-
})
|
|
205
|
-
.catch(err => {
|
|
206
|
-
console.error('Failed to get project info:', err);
|
|
207
|
-
// Fallback without projectId
|
|
208
|
-
socket.emit('terminal:init', {
|
|
209
|
-
termId,
|
|
210
|
-
cols: term.cols,
|
|
211
|
-
rows: term.rows
|
|
212
|
-
});
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
// 8. Initial token count update
|
|
216
|
-
setTimeout(() => updateTokenCounts(termId), 100);
|
|
136
|
+
// Window Controls
|
|
137
|
+
const header = wrapper.querySelector(`#header-${termId}`);
|
|
138
|
+
header.addEventListener('dblclick', () => window.toggleMinimize(termId));
|
|
139
|
+
makeDraggable(wrapper, header);
|
|
140
|
+
|
|
141
|
+
activeTerminals.set(termId, { term, fitAddon, element: wrapper });
|
|
217
142
|
|
|
218
|
-
//
|
|
143
|
+
// Focus input immediately
|
|
144
|
+
inputEl.focus();
|
|
145
|
+
|
|
219
146
|
return termId;
|
|
220
147
|
}
|
|
221
148
|
|
|
222
|
-
/**
|
|
223
|
-
* Đóng terminal UI & Process
|
|
224
|
-
*/
|
|
225
149
|
export function closeTerminalUI(termId) {
|
|
226
150
|
const session = activeTerminals.get(termId);
|
|
227
151
|
if (session) {
|
|
228
|
-
// Remove from DOM
|
|
229
152
|
session.element.remove();
|
|
230
|
-
// Clean map
|
|
231
153
|
activeTerminals.delete(termId);
|
|
232
|
-
// Tell server to kill
|
|
233
154
|
socket.emit('terminal:kill', { termId });
|
|
234
155
|
}
|
|
235
156
|
}
|
|
236
157
|
|
|
237
|
-
/**
|
|
238
|
-
* Toggle Minimize/Restore
|
|
239
|
-
*/
|
|
240
158
|
export function toggleMinimize(termId) {
|
|
241
159
|
const session = activeTerminals.get(termId);
|
|
242
|
-
if (
|
|
243
|
-
|
|
244
|
-
const el = session.element;
|
|
245
|
-
const isMinimized = el.classList.contains('minimized');
|
|
246
|
-
|
|
247
|
-
if (isMinimized) {
|
|
248
|
-
// Restore
|
|
249
|
-
el.classList.remove('minimized');
|
|
250
|
-
|
|
251
|
-
// Restore size logic if needed, but CSS transition handles visualization
|
|
252
|
-
// Need to refit xterm after transition
|
|
253
|
-
setTimeout(() => {
|
|
254
|
-
try { session.fitAddon.fit(); } catch(e){}
|
|
255
|
-
}, 250);
|
|
256
|
-
} else {
|
|
257
|
-
// Minimize
|
|
258
|
-
// Store current width/height if we want to restore exact pixel values later
|
|
259
|
-
// (CSS handles the visual hiding)
|
|
260
|
-
el.classList.add('minimized');
|
|
261
|
-
}
|
|
160
|
+
if (session) session.element.classList.toggle('minimized');
|
|
262
161
|
}
|
|
263
162
|
|
|
264
|
-
/**
|
|
265
|
-
* Toggle Maximize (Simple Fullscreen simulation)
|
|
266
|
-
*/
|
|
267
163
|
export function toggleMaximize(termId) {
|
|
268
164
|
const session = activeTerminals.get(termId);
|
|
269
|
-
if (
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
el.style.width = session.prevSize?.width || '600px';
|
|
278
|
-
el.style.height = session.prevSize?.height || '400px';
|
|
279
|
-
el.style.top = session.prevSize?.top || '100px';
|
|
280
|
-
el.style.left = session.prevSize?.left || '400px';
|
|
281
|
-
} else {
|
|
282
|
-
// Save current state
|
|
283
|
-
session.prevSize = {
|
|
284
|
-
width: el.style.width,
|
|
285
|
-
height: el.style.height,
|
|
286
|
-
top: el.style.top,
|
|
287
|
-
left: el.style.left
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
// Go full "floating" screen
|
|
291
|
-
el.classList.add('maximized');
|
|
292
|
-
el.style.width = '90vw';
|
|
293
|
-
el.style.height = '80vh';
|
|
294
|
-
el.style.top = '10vh';
|
|
295
|
-
el.style.left = '5vw';
|
|
296
|
-
el.classList.remove('minimized'); // Ensure not minimized
|
|
165
|
+
if (session) {
|
|
166
|
+
session.element.classList.toggle('maximized');
|
|
167
|
+
setTimeout(() => {
|
|
168
|
+
try {
|
|
169
|
+
session.fitAddon.fit();
|
|
170
|
+
socket.emit('terminal:resize', { termId, cols: session.term.cols, rows: session.term.rows });
|
|
171
|
+
} catch(e){}
|
|
172
|
+
}, 250);
|
|
297
173
|
}
|
|
298
|
-
|
|
299
|
-
setTimeout(() => {
|
|
300
|
-
try { session.fitAddon.fit(); } catch(e){}
|
|
301
|
-
}, 250);
|
|
302
174
|
}
|
|
303
175
|
|
|
304
|
-
|
|
305
|
-
/**
|
|
306
|
-
* Logic Drag & Drop
|
|
307
|
-
*/
|
|
308
176
|
function makeDraggable(element, handle) {
|
|
309
177
|
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
|
|
310
|
-
|
|
311
178
|
handle.onmousedown = dragMouseDown;
|
|
312
179
|
|
|
313
180
|
function dragMouseDown(e) {
|
|
314
|
-
// Don't drag if clicking buttons
|
|
315
181
|
if(e.target.tagName === 'BUTTON') return;
|
|
316
|
-
|
|
317
182
|
e.preventDefault();
|
|
318
|
-
// Get mouse cursor position at startup
|
|
319
183
|
pos3 = e.clientX;
|
|
320
184
|
pos4 = e.clientY;
|
|
321
185
|
document.onmouseup = closeDragElement;
|
|
322
|
-
// Call function whenever cursor moves
|
|
323
186
|
document.onmousemove = elementDrag;
|
|
324
187
|
}
|
|
325
188
|
|
|
326
189
|
function elementDrag(e) {
|
|
327
190
|
e.preventDefault();
|
|
328
|
-
// Calculate new cursor position
|
|
329
191
|
pos1 = pos3 - e.clientX;
|
|
330
192
|
pos2 = pos4 - e.clientY;
|
|
331
193
|
pos3 = e.clientX;
|
|
332
194
|
pos4 = e.clientY;
|
|
333
|
-
// Set element's new position
|
|
334
195
|
element.style.top = (element.offsetTop - pos2) + "px";
|
|
335
196
|
element.style.left = (element.offsetLeft - pos1) + "px";
|
|
336
197
|
}
|
|
337
198
|
|
|
338
199
|
function closeDragElement() {
|
|
339
|
-
// Stop moving when mouse button is released
|
|
340
200
|
document.onmouseup = null;
|
|
341
201
|
document.onmousemove = null;
|
|
342
202
|
}
|
|
343
203
|
}
|
|
344
204
|
|
|
345
|
-
/**
|
|
346
|
-
* Copy terminal log with specified strategy
|
|
347
|
-
* @param {string} termId - Terminal ID
|
|
348
|
-
* @param {string} strategy - 'smart' | 'errors' | 'recent' | 'all'
|
|
349
|
-
*/
|
|
350
|
-
async function copyTerminalLog(termId, strategy) {
|
|
351
|
-
const session = activeTerminals.get(termId);
|
|
352
|
-
if (!session || !session.logBuffer || session.logBuffer.length === 0) {
|
|
353
|
-
showToast('⚠️ No logs to copy', 'warning');
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
try {
|
|
358
|
-
// Use SmartCopyEngine to generate content
|
|
359
|
-
let result;
|
|
360
|
-
const lines = session.logBuffer;
|
|
361
|
-
|
|
362
|
-
switch (strategy) {
|
|
363
|
-
case 'smart':
|
|
364
|
-
result = window.SmartCopyEngine.generateSmartCopy(lines, 3000);
|
|
365
|
-
break;
|
|
366
|
-
case 'errors':
|
|
367
|
-
result = window.SmartCopyEngine.generateErrorsOnly(lines);
|
|
368
|
-
break;
|
|
369
|
-
case 'recent':
|
|
370
|
-
result = window.SmartCopyEngine.generateRecent(lines, 200);
|
|
371
|
-
break;
|
|
372
|
-
case 'all':
|
|
373
|
-
result = window.SmartCopyEngine.generateCopyAll(lines);
|
|
374
|
-
break;
|
|
375
|
-
default:
|
|
376
|
-
result = window.SmartCopyEngine.generateSmartCopy(lines, 3000);
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// Copy to clipboard
|
|
380
|
-
await navigator.clipboard.writeText(result.content);
|
|
381
|
-
|
|
382
|
-
// Show success toast with details
|
|
383
|
-
const strategyNames = {
|
|
384
|
-
smart: '🧠 Smart',
|
|
385
|
-
errors: '⚠️ Errors',
|
|
386
|
-
recent: '📄 Recent',
|
|
387
|
-
all: '📦 All'
|
|
388
|
-
};
|
|
389
|
-
|
|
390
|
-
let message = `✅ Copied ${result.tokens} tokens (${strategyNames[strategy]})`;
|
|
391
|
-
|
|
392
|
-
if (result.stats && result.stats.message) {
|
|
393
|
-
message += `\n${result.stats.message}`;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
if (result.warning) {
|
|
397
|
-
message += `\n${result.warning}`;
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
showToast(message, 'success');
|
|
401
|
-
|
|
402
|
-
} catch (error) {
|
|
403
|
-
console.error('Copy failed:', error);
|
|
404
|
-
showToast('❌ Failed to copy logs', 'error');
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
/**
|
|
409
|
-
* Update token count badges for a terminal
|
|
410
|
-
* Debounced to avoid excessive updates
|
|
411
|
-
*/
|
|
412
|
-
let updateTokenCountsTimeout = null;
|
|
413
|
-
function updateTokenCounts(termId) {
|
|
414
|
-
// Debounce updates
|
|
415
|
-
clearTimeout(updateTokenCountsTimeout);
|
|
416
|
-
updateTokenCountsTimeout = setTimeout(() => {
|
|
417
|
-
const session = activeTerminals.get(termId);
|
|
418
|
-
if (!session || !session.logBuffer) return;
|
|
419
|
-
|
|
420
|
-
const analysis = window.SmartCopyEngine.analyzeLogBuffer(session.logBuffer);
|
|
421
|
-
|
|
422
|
-
// Update badges
|
|
423
|
-
updateBadge(`badge-smart-${termId}`, analysis.smart.tokens);
|
|
424
|
-
updateBadge(`badge-errors-${termId}`, analysis.errors.tokens);
|
|
425
|
-
updateBadge(`badge-recent-${termId}`, analysis.recent.tokens);
|
|
426
|
-
updateBadge(`badge-all-${termId}`, analysis.all.tokens);
|
|
427
|
-
}, 500); // 500ms debounce
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* Update a single badge element
|
|
432
|
-
*/
|
|
433
|
-
function updateBadge(badgeId, tokens) {
|
|
434
|
-
const badge = document.getElementById(badgeId);
|
|
435
|
-
if (badge) {
|
|
436
|
-
badge.textContent = formatTokenCount(tokens);
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Format token count for display
|
|
442
|
-
*/
|
|
443
|
-
function formatTokenCount(tokens) {
|
|
444
|
-
if (tokens === 0) return '0';
|
|
445
|
-
if (tokens < 1000) return tokens.toString();
|
|
446
|
-
if (tokens < 10000) return (tokens / 1000).toFixed(1) + 'k';
|
|
447
|
-
return Math.floor(tokens / 1000) + 'k';
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
/**
|
|
451
|
-
* Show toast notification
|
|
452
|
-
*/
|
|
453
|
-
function showToast(message, type = 'info') {
|
|
454
|
-
const toast = document.getElementById('toast');
|
|
455
|
-
if (!toast) return;
|
|
456
|
-
|
|
457
|
-
toast.textContent = message;
|
|
458
|
-
toast.className = 'toast show';
|
|
459
|
-
|
|
460
|
-
if (type === 'success') {
|
|
461
|
-
toast.style.background = '#28a745';
|
|
462
|
-
} else if (type === 'error') {
|
|
463
|
-
toast.style.background = '#dc3545';
|
|
464
|
-
} else if (type === 'warning') {
|
|
465
|
-
toast.style.background = '#ffc107';
|
|
466
|
-
toast.style.color = '#000';
|
|
467
|
-
} else {
|
|
468
|
-
toast.style.background = '#007bff';
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
setTimeout(() => {
|
|
472
|
-
toast.classList.remove('show');
|
|
473
|
-
}, 3000);
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/**
|
|
477
|
-
* Clear terminal display and log buffer
|
|
478
|
-
* @param {string} termId - Terminal ID
|
|
479
|
-
*/
|
|
480
|
-
function clearTerminal(termId) {
|
|
481
|
-
const session = activeTerminals.get(termId);
|
|
482
|
-
if (!session) return;
|
|
483
|
-
|
|
484
|
-
// Clear the xterm display
|
|
485
|
-
session.term.clear();
|
|
486
|
-
|
|
487
|
-
// Clear the log buffer
|
|
488
|
-
session.logBuffer = [];
|
|
489
|
-
session.partialLine = '';
|
|
490
|
-
|
|
491
|
-
// Reset token counts to 0
|
|
492
|
-
updateBadge(`badge-smart-${termId}`, 0);
|
|
493
|
-
updateBadge(`badge-errors-${termId}`, 0);
|
|
494
|
-
updateBadge(`badge-recent-${termId}`, 0);
|
|
495
|
-
updateBadge(`badge-all-${termId}`, 0);
|
|
496
|
-
|
|
497
|
-
// Show toast notification
|
|
498
|
-
showToast('🗑️ Terminal cleared', 'info');
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/**
|
|
502
|
-
* Update terminal visibility based on active project
|
|
503
|
-
* @param {string} activeProjectId - Active project ID
|
|
504
|
-
*/
|
|
505
|
-
function updateTerminalVisibility(activeProjectId) {
|
|
506
|
-
activeTerminals.forEach((session, termId) => {
|
|
507
|
-
const shouldShow = !session.projectId || session.projectId === activeProjectId;
|
|
508
|
-
session.element.style.display = shouldShow ? 'block' : 'none';
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
const visibleCount = Array.from(activeTerminals.values())
|
|
512
|
-
.filter(s => s.element.style.display !== 'none').length;
|
|
513
|
-
|
|
514
|
-
console.log(`Updated terminal visibility: ${visibleCount} visible for project ${activeProjectId}`);
|
|
515
|
-
}
|
|
516
|
-
|
|
517
|
-
// Global Exports for HTML onclick
|
|
518
205
|
window.createNewTerminal = createNewTerminal;
|
|
519
206
|
window.closeTerminal = closeTerminalUI;
|
|
520
207
|
window.toggleMinimize = toggleMinimize;
|
|
521
208
|
window.toggleMaximize = toggleMaximize;
|
|
522
|
-
window.copyTerminalLog = copyTerminalLog;
|
|
523
|
-
window.clearTerminal = clearTerminal;
|
|
524
|
-
window.updateTerminalVisibility = updateTerminalVisibility;
|