glad-web 1.0.46 → 2.0.2
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/README.md +4 -192
- package/THIRD_PARTY_NOTICES.md +27 -0
- package/bin/glad.cjs +56 -0
- package/package.json +19 -61
- package/README.zh-CN.md +0 -198
- package/assets/logo.svg +0 -43
- package/bin/cli.js +0 -65
- package/lib/ai-tools/demo/enhanced-demo.js +0 -625
- package/lib/ai-tools/demo/index.js +0 -24
- package/lib/ai-tools/demo/responses.js +0 -88
- package/lib/ai-tools/detector.js +0 -76
- package/lib/ai-tools/registry.js +0 -300
- package/lib/claude/cli-usage.js +0 -95
- package/lib/claude/config.js +0 -82
- package/lib/claude/structured-session.js +0 -884
- package/lib/claude/transcript-repository.js +0 -216
- package/lib/codex/image-store.js +0 -174
- package/lib/codex/structured-session.js +0 -1590
- package/lib/commands/config.js +0 -78
- package/lib/commands/tools.js +0 -128
- package/lib/commands/web.js +0 -605
- package/lib/config/constants.js +0 -17
- package/lib/config/manager.js +0 -108
- package/lib/git/service.js +0 -83
- package/lib/notifications/message-formatter.js +0 -94
- package/lib/notifications/notification-service.js +0 -143
- package/lib/notifications/serverchan-client.js +0 -58
- package/lib/notifications/serverchan-settings-store.js +0 -115
- package/lib/schedule/job-runner.js +0 -162
- package/lib/schedule/job-store.js +0 -167
- package/lib/schedule/key-sequences.js +0 -49
- package/lib/schedule/scheduler-service.js +0 -39
- package/lib/server/routes/notifications.js +0 -52
- package/lib/server/routes/providers.js +0 -114
- package/lib/server/routes/schedules.js +0 -54
- package/lib/server/routes/skillhub.js +0 -104
- package/lib/server/routes/usage.js +0 -23
- package/lib/server/routes/workspace.js +0 -77
- package/lib/session/buffer.js +0 -102
- package/lib/session/file-attachment-store.js +0 -168
- package/lib/session/pty-manager.js +0 -255
- package/lib/session/rendered-history.js +0 -225
- package/lib/session/session-manager.js +0 -1032
- package/lib/session/text-history.js +0 -274
- package/lib/skillhub/client.js +0 -121
- package/lib/skillhub/settings-store.js +0 -168
- package/lib/skillhub/skill-installer.js +0 -320
- package/lib/usage/ccusage-runner.js +0 -128
- package/lib/usage/source-catalog.js +0 -26
- package/lib/usage/usage-service.js +0 -226
- package/lib/utils/logger.js +0 -74
- package/lib/utils/pid.js +0 -67
- package/lib/utils/validation.js +0 -53
- package/lib/web/bootstrap.js +0 -34
- package/lib/web/claude.js +0 -1150
- package/lib/web/codex.js +0 -1045
- package/lib/web/composer.js +0 -493
- package/lib/web/core.js +0 -385
- package/lib/web/git.js +0 -535
- package/lib/web/gitgraph.js +0 -293
- package/lib/web/index.html +0 -547
- package/lib/web/layout.js +0 -69
- package/lib/web/notifications.js +0 -164
- package/lib/web/schedules.js +0 -245
- package/lib/web/session.js +0 -361
- package/lib/web/shell.js +0 -74
- package/lib/web/skillhub.js +0 -197
- package/lib/web/styles.css +0 -932
- package/lib/web/terminal-scroll.js +0 -81
- package/lib/web/theme.js +0 -60
- package/lib/web/timed-inputs.js +0 -216
- package/lib/web/usage.js +0 -323
- package/lib/workspace/service.js +0 -77
- package/scripts/check-syntax.js +0 -26
package/lib/web/session.js
DELETED
|
@@ -1,361 +0,0 @@
|
|
|
1
|
-
function joinSession(id, sessionName, toolKey = null) {
|
|
2
|
-
if (typeof clearComposerAttachments === 'function' && (selectedImageAttachments.length || selectedFileAttachments.length)) {
|
|
3
|
-
void clearComposerAttachments();
|
|
4
|
-
}
|
|
5
|
-
stopTimedInputTimers();
|
|
6
|
-
activeSessionId = id;
|
|
7
|
-
window.activeSessionId = id;
|
|
8
|
-
activeToolKey = toolKey;
|
|
9
|
-
if (typeof resetComposerSendState === 'function') resetComposerSendState();
|
|
10
|
-
clearTimeout(sessionPollTimer);
|
|
11
|
-
sessionPollTimer = null;
|
|
12
|
-
markCompletionRead(id);
|
|
13
|
-
loadTimedInputs();
|
|
14
|
-
document.getElementById('session-title').innerText = sessionName;
|
|
15
|
-
updateToolShortcuts(activeToolKey);
|
|
16
|
-
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
|
|
17
|
-
document.getElementById('terminal-view').classList.add('active');
|
|
18
|
-
setClaudeModeEnabled(isClaudeSession());
|
|
19
|
-
if (currentSocket) { currentSocket.close(); currentSocket = null; }
|
|
20
|
-
initTerminal(id);
|
|
21
|
-
if (typeof isSplitLayout === 'function' && isSplitLayout()) refreshSessionsNow();
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function initTerminal(sessionId) {
|
|
25
|
-
if (isClaudeSession()) {
|
|
26
|
-
initClaudeSession(sessionId);
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
if (isCodexSession()) {
|
|
30
|
-
initCodexSession(sessionId);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
if (typeof Terminal === 'undefined') {
|
|
34
|
-
alert('Terminal library not loaded yet.');
|
|
35
|
-
showLobby();
|
|
36
|
-
return;
|
|
37
|
-
}
|
|
38
|
-
function isTerminalAutoResponse(data) {
|
|
39
|
-
return /^\x1b\[\?[\d;]*c$/.test(data) || /^\x1b\[>[\d;]*c$/.test(data);
|
|
40
|
-
}
|
|
41
|
-
if (!term) {
|
|
42
|
-
term = new Terminal({ theme: typeof getGladTerminalTheme === 'function' ? getGladTerminalTheme() : { background: '#000', foreground: '#fff', cursor: '#0f0' }, cursorBlink: true, fontSize: 14, cursorStyle: 'bar', scrollback: 10000 });
|
|
43
|
-
fitAddon = new FitAddon.FitAddon();
|
|
44
|
-
term.loadAddon(fitAddon);
|
|
45
|
-
term.open(document.getElementById('terminal'));
|
|
46
|
-
term.onData(data => {
|
|
47
|
-
if (isTerminalAutoResponse(data)) return;
|
|
48
|
-
let finalData = data;
|
|
49
|
-
if (modifiers.ctrl && data.length === 1) {
|
|
50
|
-
const code = data.toLowerCase().charCodeAt(0);
|
|
51
|
-
if (code >= 97 && code <= 122) finalData = String.fromCharCode(code - 96);
|
|
52
|
-
}
|
|
53
|
-
sendWS(finalData);
|
|
54
|
-
});
|
|
55
|
-
} else { term.clear(); }
|
|
56
|
-
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
57
|
-
currentSocket = new WebSocket(protocol + '//' + window.location.host + '?sessionId=' + sessionId);
|
|
58
|
-
currentSocket.onopen = () => {
|
|
59
|
-
syncLayout();
|
|
60
|
-
setTimeout(() => logViewSnapshot('ws-open'), 300);
|
|
61
|
-
};
|
|
62
|
-
currentSocket.onmessage = (e) => {
|
|
63
|
-
const msg = JSON.parse(e.data);
|
|
64
|
-
if (msg.type === 'output') term.write(msg.data);
|
|
65
|
-
if (msg.type === 'reset') term.reset();
|
|
66
|
-
if (msg.type === 'exit') showLobby();
|
|
67
|
-
};
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function initClaudeSession(sessionId) {
|
|
71
|
-
claudeMessages = [];
|
|
72
|
-
claudePendingPermissions = [];
|
|
73
|
-
claudeStatus = 'idle';
|
|
74
|
-
const runtimeConfig = await refreshClaudeRuntimeConfig();
|
|
75
|
-
claudeState = {
|
|
76
|
-
permissionMode: 'default',
|
|
77
|
-
model: (runtimeConfig && runtimeConfig.defaultModel) || 'default',
|
|
78
|
-
effort: (runtimeConfig && runtimeConfig.defaultEffort) || 'medium',
|
|
79
|
-
claudeSessionId: null,
|
|
80
|
-
resumeSessionId: null,
|
|
81
|
-
canAbort: false,
|
|
82
|
-
pendingPermissionCount: 0
|
|
83
|
-
};
|
|
84
|
-
claudeResumePanelOpen = false;
|
|
85
|
-
claudeForkPanelOpen = false;
|
|
86
|
-
claudePickerOpen = null;
|
|
87
|
-
claudeUsagePending = false;
|
|
88
|
-
claudeContextPending = false;
|
|
89
|
-
claudeResumeItemsLoaded = false;
|
|
90
|
-
document.getElementById('claude-resume-panel').classList.remove('active');
|
|
91
|
-
document.getElementById('claude-resume-panel').innerHTML = '';
|
|
92
|
-
document.getElementById('claude-fork-panel').classList.remove('active');
|
|
93
|
-
document.getElementById('claude-fork-panel').innerHTML = '';
|
|
94
|
-
closeClaudePicker();
|
|
95
|
-
updateTerminalControlsHeight();
|
|
96
|
-
applyClaudeRuntimeConfig(runtimeConfig);
|
|
97
|
-
applyClaudeState(claudeState);
|
|
98
|
-
renderClaudeChat();
|
|
99
|
-
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
100
|
-
currentSocket = new WebSocket(protocol + '//' + window.location.host + '?sessionId=' + sessionId);
|
|
101
|
-
currentSocket.onopen = () => logViewSnapshot('claude-ws-open');
|
|
102
|
-
currentSocket.onmessage = (e) => {
|
|
103
|
-
const msg = JSON.parse(e.data);
|
|
104
|
-
if (msg.type === 'claude-snapshot' && msg.snapshot) {
|
|
105
|
-
claudeMessages = msg.snapshot.messages || [];
|
|
106
|
-
claudePendingPermissions = msg.snapshot.pendingPermissions || [];
|
|
107
|
-
claudeStatus = msg.snapshot.status || 'idle';
|
|
108
|
-
applyClaudeState(msg.snapshot.state || {
|
|
109
|
-
status: claudeStatus,
|
|
110
|
-
pendingPermissionCount: claudePendingPermissions.filter(item => item.status === 'pending').length,
|
|
111
|
-
canAbort: claudeStatus === 'thinking'
|
|
112
|
-
}, { providerStateReceived: true });
|
|
113
|
-
renderClaudeChat();
|
|
114
|
-
}
|
|
115
|
-
if (msg.type === 'claude-event') applyClaudeEvent(msg.event);
|
|
116
|
-
if (msg.type === 'exit') showLobby();
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function initCodexSession(sessionId) {
|
|
121
|
-
codexMessages = [];
|
|
122
|
-
codexPendingPermissions = [];
|
|
123
|
-
codexModelPanelOpen = false;
|
|
124
|
-
codexModelCandidate = null;
|
|
125
|
-
codexResumePanelOpen = false;
|
|
126
|
-
codexForkPanelOpen = false;
|
|
127
|
-
codexPromptPanelOpen = false;
|
|
128
|
-
codexPromptItems = [];
|
|
129
|
-
codexPromptNextOffset = 0;
|
|
130
|
-
codexPromptHasMore = false;
|
|
131
|
-
codexPromptTotal = 0;
|
|
132
|
-
codexPromptLoading = false;
|
|
133
|
-
codexExpandedPrompts = new Set();
|
|
134
|
-
codexSkillPanelOpen = false;
|
|
135
|
-
codexSkillItems = [];
|
|
136
|
-
codexSkillLoading = false;
|
|
137
|
-
codexSkillQuery = '';
|
|
138
|
-
codexSkillError = '';
|
|
139
|
-
selectedCodexSkill = null;
|
|
140
|
-
codexDetailRequestSeq = 0;
|
|
141
|
-
codexDetailRequests = new Map();
|
|
142
|
-
codexDetailRevisions = new Map();
|
|
143
|
-
clearTimeout(codexDetailRefreshTimer);
|
|
144
|
-
codexDetailRefreshTimer = null;
|
|
145
|
-
codexDetailRefreshIds = new Set();
|
|
146
|
-
document.getElementById('codex-model-panel').classList.remove('active');
|
|
147
|
-
document.getElementById('codex-resume-panel').classList.remove('active');
|
|
148
|
-
document.getElementById('codex-fork-panel').classList.remove('active');
|
|
149
|
-
document.getElementById('codex-prompt-panel').classList.remove('active');
|
|
150
|
-
document.getElementById('codex-skill-panel').classList.remove('active');
|
|
151
|
-
document.getElementById('codex-control-rail').scrollLeft = 0;
|
|
152
|
-
codexState = createDefaultCodexState();
|
|
153
|
-
setClaudeModeEnabled(false);
|
|
154
|
-
applyCodexState(codexState);
|
|
155
|
-
installCodexLazyDetailHandler();
|
|
156
|
-
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
|
157
|
-
currentSocket = new WebSocket(protocol + '//' + window.location.host + '?sessionId=' + sessionId);
|
|
158
|
-
currentSocket.onmessage = (e) => {
|
|
159
|
-
const msg = JSON.parse(e.data);
|
|
160
|
-
if (msg.type === 'codex-snapshot' && msg.snapshot) {
|
|
161
|
-
codexMessages = msg.snapshot.messages || [];
|
|
162
|
-
codexPendingPermissions = msg.snapshot.pendingPermissions || [];
|
|
163
|
-
applyCodexState(msg.snapshot.state || {}, { providerStateReceived: true });
|
|
164
|
-
}
|
|
165
|
-
if (msg.type === 'codex-detail-response' && msg.detail) applyCodexDetailResponse(msg);
|
|
166
|
-
if (msg.type === 'codex-event') applyCodexEvent(msg.event);
|
|
167
|
-
if (msg.type === 'exit') showLobby();
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
function sendWS(data) {
|
|
172
|
-
if (currentSocket && currentSocket.readyState === 1) currentSocket.send(JSON.stringify({ type: 'input', data }));
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
let layoutTimer = null;
|
|
176
|
-
let layoutKeepAtBottom = false;
|
|
177
|
-
|
|
178
|
-
function isTerminalAtBottom() {
|
|
179
|
-
if (!term || !term.buffer || !term.buffer.active) return true;
|
|
180
|
-
const active = term.buffer.active;
|
|
181
|
-
return active.viewportY >= active.baseY - 1;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
function scrollTerminalViewportToBottom() {
|
|
185
|
-
if (!term) return;
|
|
186
|
-
term.scrollToBottom();
|
|
187
|
-
const viewport = document.querySelector('#terminal .xterm-viewport');
|
|
188
|
-
if (viewport) viewport.scrollTop = viewport.scrollHeight;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
function restoreTerminalBottomSoon() {
|
|
192
|
-
scrollTerminalViewportToBottom();
|
|
193
|
-
requestAnimationFrame(() => {
|
|
194
|
-
scrollTerminalViewportToBottom();
|
|
195
|
-
requestAnimationFrame(scrollTerminalViewportToBottom);
|
|
196
|
-
});
|
|
197
|
-
setTimeout(scrollTerminalViewportToBottom, 120);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function syncLayout(options = {}) {
|
|
201
|
-
if (!fitAddon || !currentSocket) return;
|
|
202
|
-
const keepAtBottom = typeof options.keepAtBottom === 'boolean' ? options.keepAtBottom : isTerminalAtBottom();
|
|
203
|
-
layoutKeepAtBottom = layoutKeepAtBottom || keepAtBottom;
|
|
204
|
-
clearTimeout(layoutTimer);
|
|
205
|
-
layoutTimer = setTimeout(() => {
|
|
206
|
-
const shouldRestoreBottom = layoutKeepAtBottom;
|
|
207
|
-
layoutKeepAtBottom = false;
|
|
208
|
-
fitAddon.fit();
|
|
209
|
-
if (shouldRestoreBottom) restoreTerminalBottomSoon();
|
|
210
|
-
if (currentSocket.readyState === 1) currentSocket.send(JSON.stringify({ type: 'resize', cols: term.cols, rows: term.rows }));
|
|
211
|
-
}, 50);
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
function previewText(text, maxChars = 320) {
|
|
215
|
-
if (!text) return '';
|
|
216
|
-
const normalized = String(text)
|
|
217
|
-
.replace(/\r/g, '\\r')
|
|
218
|
-
.replace(/\n/g, '\\n')
|
|
219
|
-
.replace(/\t/g, '\\t')
|
|
220
|
-
.replace(/\x1b/g, '\\x1b');
|
|
221
|
-
return normalized.length > maxChars ? normalized.slice(-maxChars) : normalized;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async function logClientDebug(event, payload) {
|
|
225
|
-
try {
|
|
226
|
-
await fetch('/api/debug/client-log', {
|
|
227
|
-
method: 'POST',
|
|
228
|
-
headers: { 'Content-Type': 'application/json' },
|
|
229
|
-
body: JSON.stringify({ sessionId: activeSessionId, event, payload })
|
|
230
|
-
});
|
|
231
|
-
} catch (_) {}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function snapshotTerminalBuffer(maxLines = 12) {
|
|
235
|
-
if (!term || !term.buffer || !term.buffer.active) return null;
|
|
236
|
-
const active = term.buffer.active;
|
|
237
|
-
const totalLines = active.length || 0;
|
|
238
|
-
const start = Math.max(0, totalLines - maxLines);
|
|
239
|
-
const tailLines = [];
|
|
240
|
-
for (let i = start; i < totalLines; i++) {
|
|
241
|
-
const line = active.getLine(i);
|
|
242
|
-
if (!line) continue;
|
|
243
|
-
tailLines.push(line.translateToString(true));
|
|
244
|
-
}
|
|
245
|
-
return {
|
|
246
|
-
cols: term.cols,
|
|
247
|
-
rows: term.rows,
|
|
248
|
-
baseY: active.baseY,
|
|
249
|
-
viewportY: active.viewportY,
|
|
250
|
-
cursorX: active.cursorX,
|
|
251
|
-
cursorY: active.cursorY,
|
|
252
|
-
totalLines,
|
|
253
|
-
tailPreview: previewText(tailLines.join('\n'))
|
|
254
|
-
};
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
function logViewSnapshot(event) {
|
|
258
|
-
logClientDebug(event, {
|
|
259
|
-
sessionId: activeSessionId,
|
|
260
|
-
terminalSnapshot: snapshotTerminalBuffer()
|
|
261
|
-
});
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Navigation
|
|
265
|
-
function showTerminal() {
|
|
266
|
-
clearTimeout(sessionPollTimer);
|
|
267
|
-
sessionPollTimer = null;
|
|
268
|
-
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
|
|
269
|
-
document.getElementById('terminal-view').classList.add('active');
|
|
270
|
-
logViewSnapshot('show-terminal');
|
|
271
|
-
syncLayout();
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
function showGitPreview() {
|
|
275
|
-
clearTimeout(sessionPollTimer);
|
|
276
|
-
sessionPollTimer = null;
|
|
277
|
-
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
|
|
278
|
-
document.getElementById('git-view').classList.add('active');
|
|
279
|
-
switchGitTab('changes');
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
let historyWrapEnabled = true;
|
|
283
|
-
|
|
284
|
-
function showHistory() {
|
|
285
|
-
clearTimeout(sessionPollTimer);
|
|
286
|
-
sessionPollTimer = null;
|
|
287
|
-
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
|
|
288
|
-
document.getElementById('history-view').classList.add('active');
|
|
289
|
-
logViewSnapshot('show-history-before-load');
|
|
290
|
-
loadHistory();
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
function toggleHistoryWrap() {
|
|
294
|
-
historyWrapEnabled = !historyWrapEnabled;
|
|
295
|
-
const contentEl = document.getElementById('history-content');
|
|
296
|
-
contentEl.classList.toggle('nowrap', !historyWrapEnabled);
|
|
297
|
-
document.getElementById('history-wrap-btn').style.color = historyWrapEnabled ? 'var(--primary)' : 'var(--text-dim)';
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
async function loadHistory() {
|
|
301
|
-
const contentEl = document.getElementById('history-content');
|
|
302
|
-
const metaEl = document.getElementById('history-meta');
|
|
303
|
-
if (!activeSessionId) {
|
|
304
|
-
metaEl.textContent = 'No active session.';
|
|
305
|
-
contentEl.textContent = '';
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
metaEl.textContent = 'Loading...';
|
|
310
|
-
try {
|
|
311
|
-
const res = await fetchWithTimeout(`/api/sessions/${activeSessionId}/history`, {}, 30000);
|
|
312
|
-
const data = await res.json();
|
|
313
|
-
if (!data.success) throw new Error(data.error || 'Failed to load history');
|
|
314
|
-
|
|
315
|
-
const updatedAt = data.updatedAt ? new Date(data.updatedAt).toLocaleTimeString() : 'unknown';
|
|
316
|
-
const size = typeof data.bytes === 'number' ? `${Math.round(data.bytes / 1024)} KB` : 'unknown size';
|
|
317
|
-
const truncated = data.truncated ? ' | truncated' : '';
|
|
318
|
-
metaEl.textContent = `${data.tool || 'Session'} | ${data.lines || 0} lines | ${size} | updated ${updatedAt}${truncated}`;
|
|
319
|
-
contentEl.textContent = data.text || 'No readable history yet.';
|
|
320
|
-
contentEl.classList.toggle('nowrap', !historyWrapEnabled);
|
|
321
|
-
document.getElementById('history-wrap-btn').style.color = historyWrapEnabled ? 'var(--primary)' : 'var(--text-dim)';
|
|
322
|
-
logClientDebug('history-loaded', {
|
|
323
|
-
meta: metaEl.textContent,
|
|
324
|
-
historyLines: data.lines || 0,
|
|
325
|
-
historyBytes: data.bytes || 0,
|
|
326
|
-
historyTail: previewText(data.text || '')
|
|
327
|
-
});
|
|
328
|
-
requestAnimationFrame(() => {
|
|
329
|
-
contentEl.scrollTop = contentEl.scrollHeight;
|
|
330
|
-
});
|
|
331
|
-
} catch (e) {
|
|
332
|
-
metaEl.textContent = 'Failed to load history.';
|
|
333
|
-
contentEl.textContent = e.message;
|
|
334
|
-
logClientDebug('history-load-failed', { message: e.message });
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
async function copyHistoryContent() {
|
|
339
|
-
const contentEl = document.getElementById('history-content');
|
|
340
|
-
const text = contentEl.textContent || '';
|
|
341
|
-
if (!text || text === 'Loading...' || text === 'No readable history yet.') {
|
|
342
|
-
alert('No history to copy.');
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
try {
|
|
347
|
-
if (navigator.clipboard && window.isSecureContext) {
|
|
348
|
-
await navigator.clipboard.writeText(text);
|
|
349
|
-
} else {
|
|
350
|
-
const range = document.createRange();
|
|
351
|
-
range.selectNode(contentEl);
|
|
352
|
-
window.getSelection().removeAllRanges();
|
|
353
|
-
window.getSelection().addRange(range);
|
|
354
|
-
document.execCommand('copy');
|
|
355
|
-
window.getSelection().removeAllRanges();
|
|
356
|
-
}
|
|
357
|
-
alert('History copied.');
|
|
358
|
-
} catch (e) {
|
|
359
|
-
alert('Copy failed. You can select the history text manually.');
|
|
360
|
-
}
|
|
361
|
-
}
|
package/lib/web/shell.js
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
async function deleteSession(id, e) {
|
|
2
|
-
e?.stopPropagation();
|
|
3
|
-
if (!confirm('Terminate session?')) return;
|
|
4
|
-
const button = e?.currentTarget;
|
|
5
|
-
if (button) button.disabled = true;
|
|
6
|
-
try {
|
|
7
|
-
const response = await fetchWithTimeout('/api/sessions/' + encodeURIComponent(id), { method: 'DELETE' }, 10000);
|
|
8
|
-
if (!response.ok) {
|
|
9
|
-
const data = await response.json().catch(() => ({}));
|
|
10
|
-
throw new Error(data.error || `HTTP ${response.status}`);
|
|
11
|
-
}
|
|
12
|
-
if (id === activeSessionId) showLobby();
|
|
13
|
-
else await refreshSessionsNow();
|
|
14
|
-
} catch (error) {
|
|
15
|
-
if (button?.isConnected) button.disabled = false;
|
|
16
|
-
alert(`Failed to delete session: ${error.message}`);
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function showLobby() {
|
|
21
|
-
if (currentSocket) { currentSocket.close(); currentSocket = null; }
|
|
22
|
-
closeTimedSendPanel();
|
|
23
|
-
stopTimedInputTimers();
|
|
24
|
-
activeSessionId = null;
|
|
25
|
-
window.activeSessionId = null;
|
|
26
|
-
activeToolKey = null;
|
|
27
|
-
if (typeof resetComposerSendState === 'function') resetComposerSendState();
|
|
28
|
-
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
|
|
29
|
-
document.getElementById('lobby-view').classList.add('active');
|
|
30
|
-
refreshSessionsNow();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function updateToolShortcuts(toolKey) {
|
|
34
|
-
const usesUsageCommand = ['antigravity', 'claude-code'].includes(toolKey);
|
|
35
|
-
const usageCommand = usesUsageCommand ? '/usage' : '/stat';
|
|
36
|
-
const defaultShortcuts = [
|
|
37
|
-
{ key: 'left', label: '←', special: true },
|
|
38
|
-
{ key: 'right', label: '→', special: true },
|
|
39
|
-
{ key: 'ctrl-c', label: 'Ctrl+C' },
|
|
40
|
-
{ command: '/model' },
|
|
41
|
-
{ command: '/resume' },
|
|
42
|
-
{ command: usageCommand }
|
|
43
|
-
];
|
|
44
|
-
const toolShortcuts = {
|
|
45
|
-
codex: [
|
|
46
|
-
{ key: 'ctrl-c', label: 'Ctrl+C' },
|
|
47
|
-
{ key: 'codex-perm', label: '/perm', title: 'Send /perm' },
|
|
48
|
-
{ command: '/model' },
|
|
49
|
-
{ command: '/resume' },
|
|
50
|
-
{ command: '/stat' }
|
|
51
|
-
]
|
|
52
|
-
};
|
|
53
|
-
const shortcuts = toolShortcuts[toolKey] || defaultShortcuts;
|
|
54
|
-
const shortcutGroup = document.querySelector('[data-role="tool-shortcuts"]');
|
|
55
|
-
if (!shortcutGroup) return;
|
|
56
|
-
|
|
57
|
-
shortcutGroup.style.gridTemplateColumns = `repeat(${shortcuts.length}, minmax(0, 1fr))`;
|
|
58
|
-
shortcutGroup.replaceChildren(...shortcuts.map(shortcut => {
|
|
59
|
-
const button = document.createElement(shortcut.command ? 'button' : 'div');
|
|
60
|
-
button.className = shortcut.command
|
|
61
|
-
? 'command-key'
|
|
62
|
-
: `key-btn${shortcut.special ? ' special-key' : ''}`;
|
|
63
|
-
if (shortcut.command) {
|
|
64
|
-
button.dataset.command = shortcut.command;
|
|
65
|
-
button.textContent = shortcut.command;
|
|
66
|
-
button.title = `Send ${shortcut.command}`;
|
|
67
|
-
} else {
|
|
68
|
-
button.dataset.key = shortcut.key;
|
|
69
|
-
button.textContent = shortcut.label;
|
|
70
|
-
button.title = shortcut.title || '';
|
|
71
|
-
}
|
|
72
|
-
return button;
|
|
73
|
-
}));
|
|
74
|
-
}
|
package/lib/web/skillhub.js
DELETED
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
let skillHubSettings = null;
|
|
2
|
-
let skillHallSkills = [];
|
|
3
|
-
let skillHallQuery = '';
|
|
4
|
-
|
|
5
|
-
function applySkillHubAvailability(available) {
|
|
6
|
-
const button = document.getElementById('skill-hall-button');
|
|
7
|
-
if (!button) return;
|
|
8
|
-
button.disabled = !available;
|
|
9
|
-
button.title = available ? 'Skill Hall' : 'Skill暂不可用';
|
|
10
|
-
button.setAttribute('aria-label', button.title);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
async function refreshSkillHubAvailability() {
|
|
14
|
-
try {
|
|
15
|
-
const response = await fetchWithTimeout('/api/skillhub/status', {}, 5000);
|
|
16
|
-
const data = await response.json();
|
|
17
|
-
applySkillHubAvailability(Boolean(response.ok && data.available));
|
|
18
|
-
} catch (_) {
|
|
19
|
-
applySkillHubAvailability(false);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function setSkillHubStatus(message, type = '') {
|
|
24
|
-
const target = document.getElementById('skillhub-settings-status');
|
|
25
|
-
if (!target) return;
|
|
26
|
-
target.textContent = message;
|
|
27
|
-
target.className = `serverchan-settings-status${type ? ` ${type}` : ''}`;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function setSkillHubBusy(busy) {
|
|
31
|
-
for (const id of ['skillhub-save-btn', 'skillhub-test-btn', 'skillhub-remove-btn']) {
|
|
32
|
-
const button = document.getElementById(id);
|
|
33
|
-
if (button) button.disabled = busy;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function currentSkillHubForm() {
|
|
38
|
-
return {
|
|
39
|
-
baseUrl: document.getElementById('skillhub-base-url').value.trim(),
|
|
40
|
-
token: document.getElementById('skillhub-token').value.trim()
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async function loadSkillHubSettings() {
|
|
45
|
-
setSkillHubStatus('Loading configuration…');
|
|
46
|
-
try {
|
|
47
|
-
const response = await fetchWithTimeout('/api/skillhub/settings', {}, 10000);
|
|
48
|
-
const data = await response.json();
|
|
49
|
-
if (!response.ok) throw new Error(data.error || 'Could not load SkillHub configuration');
|
|
50
|
-
skillHubSettings = data;
|
|
51
|
-
document.getElementById('skillhub-base-url').value = data.baseUrl || '';
|
|
52
|
-
const token = document.getElementById('skillhub-token');
|
|
53
|
-
token.value = '';
|
|
54
|
-
token.placeholder = data.configured ? data.maskedToken : 'clh_...';
|
|
55
|
-
document.getElementById('skillhub-token-hint').textContent = data.configured
|
|
56
|
-
? `Saved: ${data.maskedToken}. Leave blank to keep the current Token.`
|
|
57
|
-
: 'The Token is encrypted on this Glad host.';
|
|
58
|
-
document.getElementById('skillhub-remove-btn').style.display = data.configured ? 'inline-flex' : 'none';
|
|
59
|
-
setSkillHubStatus(data.configured ? 'Configuration saved.' : 'SkillHub is not configured.');
|
|
60
|
-
} catch (error) {
|
|
61
|
-
setSkillHubStatus(error.message || 'Could not load SkillHub configuration', 'error');
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async function saveSkillHubSettings() {
|
|
66
|
-
setSkillHubBusy(true);
|
|
67
|
-
setSkillHubStatus('Testing and saving…');
|
|
68
|
-
try {
|
|
69
|
-
const response = await fetchWithTimeout('/api/skillhub/settings', {
|
|
70
|
-
method: 'PUT',
|
|
71
|
-
headers: { 'Content-Type': 'application/json' },
|
|
72
|
-
body: JSON.stringify(currentSkillHubForm())
|
|
73
|
-
}, 20000);
|
|
74
|
-
const data = await response.json();
|
|
75
|
-
if (!response.ok) throw new Error(data.error || 'Could not save SkillHub configuration');
|
|
76
|
-
skillHubSettings = data.settings;
|
|
77
|
-
const token = document.getElementById('skillhub-token');
|
|
78
|
-
token.value = '';
|
|
79
|
-
token.placeholder = data.settings.maskedToken;
|
|
80
|
-
document.getElementById('skillhub-token-hint').textContent =
|
|
81
|
-
`Saved: ${data.settings.maskedToken}. Leave blank to keep the current Token.`;
|
|
82
|
-
document.getElementById('skillhub-remove-btn').style.display = 'inline-flex';
|
|
83
|
-
const handle = data.user?.handle || data.user?.data?.handle || '';
|
|
84
|
-
setSkillHubStatus(`Connected${handle ? ` as ${handle}` : ''}.`, 'success');
|
|
85
|
-
} catch (error) {
|
|
86
|
-
setSkillHubStatus(error.message || 'Could not save SkillHub configuration', 'error');
|
|
87
|
-
} finally {
|
|
88
|
-
setSkillHubBusy(false);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function testSkillHubSettings() {
|
|
93
|
-
setSkillHubBusy(true);
|
|
94
|
-
setSkillHubStatus('Testing connection…');
|
|
95
|
-
try {
|
|
96
|
-
const response = await fetchWithTimeout('/api/skillhub/settings/test', {
|
|
97
|
-
method: 'POST',
|
|
98
|
-
headers: { 'Content-Type': 'application/json' },
|
|
99
|
-
body: JSON.stringify(currentSkillHubForm())
|
|
100
|
-
}, 20000);
|
|
101
|
-
const data = await response.json();
|
|
102
|
-
if (!response.ok) throw new Error(data.error || 'SkillHub connection failed');
|
|
103
|
-
const handle = data.user?.handle || data.user?.data?.handle || '';
|
|
104
|
-
setSkillHubStatus(`Connection successful${handle ? ` · ${handle}` : ''}.`, 'success');
|
|
105
|
-
} catch (error) {
|
|
106
|
-
setSkillHubStatus(error.message || 'SkillHub connection failed', 'error');
|
|
107
|
-
} finally {
|
|
108
|
-
setSkillHubBusy(false);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
async function removeSkillHubSettings() {
|
|
113
|
-
if (!confirm('Remove the SkillHub connection?')) return;
|
|
114
|
-
setSkillHubBusy(true);
|
|
115
|
-
try {
|
|
116
|
-
const response = await fetchWithTimeout('/api/skillhub/settings', { method: 'DELETE' }, 10000);
|
|
117
|
-
const data = await response.json();
|
|
118
|
-
if (!response.ok) throw new Error(data.error || 'Could not remove SkillHub configuration');
|
|
119
|
-
skillHubSettings = data.settings;
|
|
120
|
-
document.getElementById('skillhub-base-url').value = '';
|
|
121
|
-
document.getElementById('skillhub-token').value = '';
|
|
122
|
-
document.getElementById('skillhub-token').placeholder = 'clh_...';
|
|
123
|
-
document.getElementById('skillhub-token-hint').textContent = 'The Token is encrypted on this Glad host.';
|
|
124
|
-
document.getElementById('skillhub-remove-btn').style.display = 'none';
|
|
125
|
-
setSkillHubStatus('SkillHub configuration removed.', 'success');
|
|
126
|
-
} catch (error) {
|
|
127
|
-
setSkillHubStatus(error.message || 'Could not remove SkillHub configuration', 'error');
|
|
128
|
-
} finally {
|
|
129
|
-
setSkillHubBusy(false);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function skillHallCard(skill, index) {
|
|
134
|
-
const name = String(skill.displayName || skill.name || skill.slug || 'Unnamed Skill');
|
|
135
|
-
const description = String(skill.description || '');
|
|
136
|
-
const meta = [skill.kind, skill.version].filter(Boolean).join(' · ');
|
|
137
|
-
return `<button type="button" class="skill-hall-card" onclick="selectSkillHallSkill(${index})"><span class="skill-hall-card-name">${escapeHtml(name)}</span>${description ? `<span class="skill-hall-card-description">${escapeHtml(description)}</span>` : ''}<span class="skill-hall-card-meta">${escapeHtml(meta)}</span></button>`;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function renderSkillHall() {
|
|
141
|
-
const grid = document.getElementById('skill-hall-grid');
|
|
142
|
-
const status = document.getElementById('skill-hall-status');
|
|
143
|
-
if (!grid || !status) return;
|
|
144
|
-
const query = skillHallQuery.trim().toLocaleLowerCase();
|
|
145
|
-
const matches = skillHallSkills.map((skill, index) => ({ skill, index })).filter(({ skill }) => {
|
|
146
|
-
if (!query) return true;
|
|
147
|
-
return [skill.displayName, skill.name, skill.slug, skill.description, skill.kind, skill.version]
|
|
148
|
-
.some(value => String(value || '').toLocaleLowerCase().includes(query));
|
|
149
|
-
});
|
|
150
|
-
status.textContent = matches.length ? `${matches.length} Skill${matches.length === 1 ? '' : 's'}`
|
|
151
|
-
: (query ? 'No matching Skills.' : 'No Skills are available for this account.');
|
|
152
|
-
grid.innerHTML = matches.map(({ skill, index }) => skillHallCard(skill, index)).join('');
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async function openSkillHall(event = null) {
|
|
156
|
-
event?.stopPropagation();
|
|
157
|
-
if (document.getElementById('skill-hall-button')?.disabled) {
|
|
158
|
-
showAppToast('Skill暂不可用');
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
document.getElementById('skill-hall-overlay').style.display = 'flex';
|
|
162
|
-
document.getElementById('skill-hall-search').value = '';
|
|
163
|
-
document.getElementById('skill-hall-status').textContent = 'Loading Skills…';
|
|
164
|
-
document.getElementById('skill-hall-grid').innerHTML = '';
|
|
165
|
-
skillHallQuery = '';
|
|
166
|
-
try {
|
|
167
|
-
const response = await fetchWithTimeout('/api/skillhub/skills', {}, 30000);
|
|
168
|
-
const data = await response.json();
|
|
169
|
-
if (!response.ok || !data.success) throw new Error(data.error || 'Could not load Skills');
|
|
170
|
-
skillHallSkills = Array.isArray(data.skills) ? data.skills : [];
|
|
171
|
-
renderSkillHall();
|
|
172
|
-
requestAnimationFrame(() => document.getElementById('skill-hall-search')?.focus());
|
|
173
|
-
} catch (error) {
|
|
174
|
-
skillHallSkills = [];
|
|
175
|
-
document.getElementById('skill-hall-status').textContent = error.message || 'Could not load Skills';
|
|
176
|
-
if (error.message === 'Skill暂不可用') applySkillHubAvailability(false);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function closeSkillHall(event = null) {
|
|
181
|
-
if (event && event.target.id !== 'skill-hall-overlay') return;
|
|
182
|
-
document.getElementById('skill-hall-overlay').style.display = 'none';
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function filterSkillHall(value) {
|
|
186
|
-
skillHallQuery = String(value || '');
|
|
187
|
-
renderSkillHall();
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function selectSkillHallSkill(index) {
|
|
191
|
-
const skill = skillHallSkills[index];
|
|
192
|
-
if (!skill) return;
|
|
193
|
-
closeSkillHall();
|
|
194
|
-
showToolModal(skill);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
void refreshSkillHubAvailability();
|