glad-web 1.0.45 → 1.0.46
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/lib/codex/structured-session.js +15 -3
- package/lib/commands/web.js +23 -4
- package/lib/config/manager.js +19 -0
- package/lib/server/routes/skillhub.js +104 -0
- package/lib/session/session-manager.js +71 -40
- package/lib/skillhub/client.js +121 -0
- package/lib/skillhub/settings-store.js +168 -0
- package/lib/skillhub/skill-installer.js +320 -0
- package/lib/web/bootstrap.js +34 -0
- package/lib/web/claude.js +29 -8
- package/lib/web/codex.js +5 -2
- package/lib/web/composer.js +30 -0
- package/lib/web/core.js +47 -35
- package/lib/web/index.html +43 -12
- package/lib/web/layout.js +4 -7
- package/lib/web/notifications.js +1 -0
- package/lib/web/session.js +3 -2
- package/lib/web/shell.js +17 -2
- package/lib/web/skillhub.js +197 -0
- package/lib/web/styles.css +35 -8
- package/package.json +6 -3
package/lib/web/core.js
CHANGED
|
@@ -226,6 +226,40 @@
|
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
function renderSessionCard(session) {
|
|
230
|
+
const workingDirectory = session.workingDirectory || 'Unknown directory';
|
|
231
|
+
const encodedId = encodePathValue(session.id);
|
|
232
|
+
const encodedName = encodePathValue(session.name);
|
|
233
|
+
const encodedDir = encodePathValue(workingDirectory);
|
|
234
|
+
const encodedToolKey = encodePathValue(session.toolKey || '');
|
|
235
|
+
const timedInputCount = Number(session.timedInputCount) || 0;
|
|
236
|
+
const timerBadge = timedInputCount > 0
|
|
237
|
+
? `<span class="timer-count-badge" title="${timedInputCount} scheduled timer${timedInputCount > 1 ? 's' : ''}">
|
|
238
|
+
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"></circle><path d="M12 7v5l3 2"></path></svg>
|
|
239
|
+
${timedInputCount}
|
|
240
|
+
</span>`
|
|
241
|
+
: '';
|
|
242
|
+
return `<div class="session-card${session.id === activeSessionId ? ' selected' : ''}" data-session-id="${escapeHtml(session.id)}">
|
|
243
|
+
<span class="active-session-dot" title="Current session" aria-label="Current session"></span>
|
|
244
|
+
<div class="session-info">
|
|
245
|
+
<h3><span class="session-name" title="${escapeHtml(session.name)}">${escapeHtml(session.name)}</span>${session.hasUnreadCompletion ? '<span class="completion-dot" title="Completed"></span>' : ''}${timerBadge} <button type="button" class="icon-btn session-edit-btn" title="Rename session" aria-label="Rename session" onclick="renameSession(decodePathValue('${encodedId}'), decodePathValue('${encodedName}'), event)"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg></button></h3>
|
|
246
|
+
<p>${escapeHtml(session.tool)}</p>
|
|
247
|
+
<p>${new Date(session.startTime).toLocaleTimeString()}</p>
|
|
248
|
+
</div>
|
|
249
|
+
<div class="session-actions">
|
|
250
|
+
${renderServerChanSessionAction(session)}
|
|
251
|
+
<button class="btn-join" onclick="joinSession(decodePathValue('${encodedId}'), decodePathValue('${encodedName}'), decodePathValue('${encodedToolKey}'))">Connect</button>
|
|
252
|
+
<button type="button" class="icon-btn btn-delete session-delete-btn" title="Delete session" aria-label="Delete session" onclick="deleteSession(decodePathValue('${encodedId}'), event)"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg></button>
|
|
253
|
+
</div>
|
|
254
|
+
<div class="session-dir-row">
|
|
255
|
+
<button class="copy-dir-btn" title="Copy directory" onclick="copySessionDirectory(decodePathValue('${encodedDir}'), event)">
|
|
256
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
|
|
257
|
+
</button>
|
|
258
|
+
<p title="${escapeHtml(workingDirectory)}">${escapeHtml(workingDirectory)}</p>
|
|
259
|
+
</div>
|
|
260
|
+
</div>`;
|
|
261
|
+
}
|
|
262
|
+
|
|
229
263
|
async function loadSessions() {
|
|
230
264
|
log('Loading sessions...');
|
|
231
265
|
const list = document.getElementById('sessions-list');
|
|
@@ -238,38 +272,7 @@
|
|
|
238
272
|
return;
|
|
239
273
|
}
|
|
240
274
|
|
|
241
|
-
|
|
242
|
-
sessions.forEach(s => {
|
|
243
|
-
const workingDirectory = s.workingDirectory || 'Unknown directory';
|
|
244
|
-
const encodedName = encodePathValue(s.name);
|
|
245
|
-
const encodedDir = encodePathValue(workingDirectory);
|
|
246
|
-
const timedInputCount = Number(s.timedInputCount) || 0;
|
|
247
|
-
const timerBadge = timedInputCount > 0
|
|
248
|
-
? `<span class="timer-count-badge" title="${timedInputCount} scheduled timer${timedInputCount > 1 ? 's' : ''}">
|
|
249
|
-
<svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="9"></circle><path d="M12 7v5l3 2"></path></svg>
|
|
250
|
-
${timedInputCount}
|
|
251
|
-
</span>`
|
|
252
|
-
: '';
|
|
253
|
-
html += `<div class="session-card${s.id === activeSessionId ? ' selected' : ''}" data-session-id="${escapeHtml(s.id)}">
|
|
254
|
-
<div class="session-info">
|
|
255
|
-
<h3>${escapeHtml(s.name)}${s.hasUnreadCompletion ? '<span class="completion-dot" title="Completed"></span>' : ''}${timerBadge} <button type="button" class="icon-btn session-edit-btn" title="Rename session" aria-label="Rename session" onclick="renameSession('${s.id}', decodePathValue('${encodedName}'), event)"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg></button></h3>
|
|
256
|
-
<p>${escapeHtml(s.tool)}</p>
|
|
257
|
-
<p>${new Date(s.startTime).toLocaleTimeString()}</p>
|
|
258
|
-
</div>
|
|
259
|
-
<div class="session-actions">
|
|
260
|
-
${renderServerChanSessionAction(s)}
|
|
261
|
-
<button class="btn-join" onclick="joinSession('${s.id}', decodePathValue('${encodedName}'), '${escapeHtml(s.toolKey || '')}')">Connect</button>
|
|
262
|
-
<button type="button" class="icon-btn btn-delete session-delete-btn" title="Delete session" aria-label="Delete session" onclick="deleteSession('${s.id}', event)"><svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path></svg></button>
|
|
263
|
-
</div>
|
|
264
|
-
<div class="session-dir-row">
|
|
265
|
-
<button class="copy-dir-btn" title="Copy directory" onclick="copySessionDirectory(decodePathValue('${encodedDir}'), event)">
|
|
266
|
-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="11" height="11" rx="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
|
|
267
|
-
</button>
|
|
268
|
-
<p title="${escapeHtml(workingDirectory)}">${escapeHtml(workingDirectory)}</p>
|
|
269
|
-
</div>
|
|
270
|
-
</div>`;
|
|
271
|
-
});
|
|
272
|
-
list.innerHTML = html;
|
|
275
|
+
list.innerHTML = sessions.map(renderSessionCard).join('');
|
|
273
276
|
} catch (e) {
|
|
274
277
|
list.innerHTML = `<div style="color:#ff3b30; text-align:center; margin-top:50px;"><p>Failed to load: ${e.message}</p><button class="btn-retry" onclick="loadSessions()">Retry</button></div>`;
|
|
275
278
|
}
|
|
@@ -290,13 +293,19 @@
|
|
|
290
293
|
}
|
|
291
294
|
}
|
|
292
295
|
|
|
293
|
-
async function showToolModal() {
|
|
296
|
+
async function showToolModal(skill = null) {
|
|
297
|
+
window.pendingSkillHubSkill = skill || null;
|
|
294
298
|
document.getElementById('modal-overlay').style.display = 'flex';
|
|
295
299
|
const list = document.getElementById('tools-list');
|
|
300
|
+
const title = document.getElementById('tool-modal-title');
|
|
301
|
+
const skillLabel = document.getElementById('tool-modal-skill');
|
|
302
|
+
if (title) title.textContent = skill ? 'Start Skill Session' : 'Create Session';
|
|
303
|
+
if (skillLabel) skillLabel.textContent = skill ? String(skill.displayName || skill.name || '') : '';
|
|
296
304
|
loadAppConfig();
|
|
297
305
|
try {
|
|
298
306
|
const res = await fetchWithTimeout('/api/tools');
|
|
299
|
-
|
|
307
|
+
let tools = await res.json();
|
|
308
|
+
if (skill) tools = tools.filter(tool => tool.key === 'codex');
|
|
300
309
|
|
|
301
310
|
let html = '';
|
|
302
311
|
tools.forEach(t => {
|
|
@@ -331,7 +340,10 @@
|
|
|
331
340
|
}
|
|
332
341
|
|
|
333
342
|
function closeToolModal(e) {
|
|
334
|
-
if (!e || e.target.id === 'modal-overlay')
|
|
343
|
+
if (!e || e.target.id === 'modal-overlay') {
|
|
344
|
+
document.getElementById('modal-overlay').style.display = 'none';
|
|
345
|
+
window.pendingSkillHubSkill = null;
|
|
346
|
+
}
|
|
335
347
|
}
|
|
336
348
|
|
|
337
349
|
function isLobbyVisible() {
|
package/lib/web/index.html
CHANGED
|
@@ -6,16 +6,7 @@
|
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
|
|
7
7
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
8
8
|
<meta name="theme-color" content="#000000">
|
|
9
|
-
<script>
|
|
10
|
-
(() => {
|
|
11
|
-
const storedTheme = localStorage.getItem('glad-theme');
|
|
12
|
-
const theme = storedTheme === 'light' || storedTheme === 'dark'
|
|
13
|
-
? storedTheme
|
|
14
|
-
: (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
|
|
15
|
-
document.documentElement.dataset.theme = theme;
|
|
16
|
-
document.documentElement.style.backgroundColor = theme === 'dark' ? '#000000' : '#f5f6f8';
|
|
17
|
-
})();
|
|
18
|
-
</script>
|
|
9
|
+
<script src="bootstrap.js"></script>
|
|
19
10
|
<link rel="manifest" href="manifest.json">
|
|
20
11
|
<link rel="icon" type="image/svg+xml" href="logo.svg">
|
|
21
12
|
<link rel="apple-touch-icon" href="logo.svg">
|
|
@@ -50,7 +41,10 @@
|
|
|
50
41
|
<div id="lobby-view" class="view active">
|
|
51
42
|
<div id="lobby">
|
|
52
43
|
<div class="header">
|
|
53
|
-
<
|
|
44
|
+
<button id="skill-hall-button" class="skill-hall-entry-btn" type="button" disabled
|
|
45
|
+
onclick="openSkillHall()" title="Skill暂不可用" aria-label="Skill暂不可用">
|
|
46
|
+
<svg class="action-icon" aria-hidden="true"><use href="#icon-skills"></use></svg><span>Skills</span>
|
|
47
|
+
</button>
|
|
54
48
|
<div class="header-actions">
|
|
55
49
|
<button class="header-action-btn" onclick="showToolModal()" title="New AI session"><span>+</span><span>Session</span></button>
|
|
56
50
|
<button id="usage-dashboard-button" class="header-action-btn icon-only" type="button"
|
|
@@ -359,7 +353,7 @@
|
|
|
359
353
|
<!-- Tool Modal -->
|
|
360
354
|
<div id="modal-overlay" onclick="closeToolModal(event)">
|
|
361
355
|
<div id="tool-modal" onclick="event.stopPropagation()">
|
|
362
|
-
<div class="tool-modal-header"><h2>Create Session</h2><button class="icon-btn" type="button" onclick="closeToolModal()" aria-label="Close">×</button></div>
|
|
356
|
+
<div class="tool-modal-header"><div><h2 id="tool-modal-title">Create Session</h2><p id="tool-modal-skill" class="tool-modal-skill"></p></div><button class="icon-btn" type="button" onclick="closeToolModal()" aria-label="Close">×</button></div>
|
|
363
357
|
<div style="margin-bottom: 15px;">
|
|
364
358
|
<label style="display:block; margin-bottom: 8px; font-size: 14px; color: var(--text-dim);">Working Directory (Optional):</label>
|
|
365
359
|
<div style="margin-bottom: 8px; color: var(--text-dim); font-size: 12px; line-height: 1.4;">
|
|
@@ -373,6 +367,19 @@
|
|
|
373
367
|
</div>
|
|
374
368
|
</div>
|
|
375
369
|
|
|
370
|
+
<!-- Skill Hall Modal -->
|
|
371
|
+
<div id="skill-hall-overlay" onclick="closeSkillHall(event)">
|
|
372
|
+
<div id="skill-hall-modal" role="dialog" aria-modal="true" aria-labelledby="skill-hall-title" onclick="event.stopPropagation()">
|
|
373
|
+
<div class="skill-hall-header">
|
|
374
|
+
<div><h2 id="skill-hall-title">Skill Hall</h2><p>Select a private Skill to start a temporary Codex session.</p></div>
|
|
375
|
+
<button class="icon-btn" type="button" onclick="closeSkillHall()" aria-label="Close">×</button>
|
|
376
|
+
</div>
|
|
377
|
+
<input id="skill-hall-search" class="skill-hall-search" type="search" placeholder="Search skills…" aria-label="Search skills" oninput="filterSkillHall(this.value)">
|
|
378
|
+
<div id="skill-hall-status" class="skill-hall-status" aria-live="polite"></div>
|
|
379
|
+
<div id="skill-hall-grid" class="skill-hall-grid"></div>
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
|
|
376
383
|
<!-- Usage Source Modal -->
|
|
377
384
|
<div id="usage-source-overlay" onclick="closeUsageSourceModal(event)">
|
|
378
385
|
<div id="usage-source-modal" role="dialog" aria-modal="true" aria-labelledby="usage-source-modal-title" onclick="event.stopPropagation()">
|
|
@@ -440,6 +447,29 @@
|
|
|
440
447
|
<button id="serverchan-test-btn" class="small-btn" type="button" onclick="testServerChanSettings()">Send Test</button>
|
|
441
448
|
</div>
|
|
442
449
|
</section>
|
|
450
|
+
<section class="settings-section" aria-labelledby="skillhub-settings-title">
|
|
451
|
+
<div class="settings-section-label" id="skillhub-settings-title">Skills</div>
|
|
452
|
+
<div class="settings-provider-header">
|
|
453
|
+
<div><h3>SkillHub</h3><p>Connect Glad to a private Skill server.</p></div>
|
|
454
|
+
</div>
|
|
455
|
+
<div class="form-field">
|
|
456
|
+
<label for="skillhub-base-url">Server URL</label>
|
|
457
|
+
<input id="skillhub-base-url" type="url" autocomplete="off" placeholder="http://skillhub:10070">
|
|
458
|
+
<div class="serverchan-help">Remote servers must use HTTPS.</div>
|
|
459
|
+
</div>
|
|
460
|
+
<div class="form-field">
|
|
461
|
+
<label for="skillhub-token">API Token</label>
|
|
462
|
+
<input id="skillhub-token" type="password" autocomplete="off" placeholder="clh_...">
|
|
463
|
+
<div id="skillhub-token-hint" class="serverchan-help"></div>
|
|
464
|
+
</div>
|
|
465
|
+
<div id="skillhub-settings-status" class="serverchan-settings-status" aria-live="polite"></div>
|
|
466
|
+
<div class="serverchan-modal-actions">
|
|
467
|
+
<button id="skillhub-remove-btn" class="small-btn danger" type="button" onclick="removeSkillHubSettings()">Remove</button>
|
|
468
|
+
<span class="serverchan-action-spacer"></span>
|
|
469
|
+
<button id="skillhub-save-btn" class="small-btn primary" type="button" onclick="saveSkillHubSettings()">Save</button>
|
|
470
|
+
<button id="skillhub-test-btn" class="small-btn" type="button" onclick="testSkillHubSettings()">Test</button>
|
|
471
|
+
</div>
|
|
472
|
+
</section>
|
|
443
473
|
</div>
|
|
444
474
|
</div>
|
|
445
475
|
<div id="app-toast" role="status" aria-live="polite"></div>
|
|
@@ -502,6 +532,7 @@
|
|
|
502
532
|
<script src="core.js"></script>
|
|
503
533
|
<script src="layout.js"></script>
|
|
504
534
|
<script src="notifications.js"></script>
|
|
535
|
+
<script src="skillhub.js"></script>
|
|
505
536
|
<script src="claude.js"></script>
|
|
506
537
|
<script src="schedules.js"></script>
|
|
507
538
|
<script src="shell.js"></script>
|
package/lib/web/layout.js
CHANGED
|
@@ -1,19 +1,16 @@
|
|
|
1
|
-
const GLAD_SPLIT_QUERY =
|
|
2
|
-
const GLAD_SIDEBAR_KEY =
|
|
1
|
+
const GLAD_SPLIT_QUERY = window.gladLayout.splitQuery;
|
|
2
|
+
const GLAD_SIDEBAR_KEY = window.gladLayout.sidebarStorageKey;
|
|
3
3
|
|
|
4
4
|
function isSplitLayout() {
|
|
5
5
|
return window.matchMedia(GLAD_SPLIT_QUERY).matches;
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
function clampSidebarWidth(value) {
|
|
9
|
-
|
|
10
|
-
return Math.min(max, Math.max(300, Number(value) || 348));
|
|
9
|
+
return window.gladLayout.clampSidebarWidth(value);
|
|
11
10
|
}
|
|
12
11
|
|
|
13
12
|
function applySidebarWidth(value) {
|
|
14
|
-
|
|
15
|
-
document.documentElement.style.setProperty('--sidebar-w', `${width}px`);
|
|
16
|
-
return width;
|
|
13
|
+
return window.gladLayout.applySidebarWidth(value);
|
|
17
14
|
}
|
|
18
15
|
|
|
19
16
|
function initializeResponsiveLayout() {
|
package/lib/web/notifications.js
CHANGED
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
event?.stopPropagation();
|
|
51
51
|
document.getElementById('settings-modal-overlay').style.display = 'flex';
|
|
52
52
|
if (typeof syncGladThemeControls === 'function') syncGladThemeControls();
|
|
53
|
+
if (typeof loadSkillHubSettings === 'function') void loadSkillHubSettings();
|
|
53
54
|
setServerChanStatus('Loading configuration…');
|
|
54
55
|
try {
|
|
55
56
|
const response = await fetchWithTimeout('/api/notifications/serverchan', {}, 10000);
|
package/lib/web/session.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
activeSessionId = id;
|
|
7
7
|
window.activeSessionId = id;
|
|
8
8
|
activeToolKey = toolKey;
|
|
9
|
+
if (typeof resetComposerSendState === 'function') resetComposerSendState();
|
|
9
10
|
clearTimeout(sessionPollTimer);
|
|
10
11
|
sessionPollTimer = null;
|
|
11
12
|
markCompletionRead(id);
|
|
@@ -108,7 +109,7 @@
|
|
|
108
109
|
status: claudeStatus,
|
|
109
110
|
pendingPermissionCount: claudePendingPermissions.filter(item => item.status === 'pending').length,
|
|
110
111
|
canAbort: claudeStatus === 'thinking'
|
|
111
|
-
});
|
|
112
|
+
}, { providerStateReceived: true });
|
|
112
113
|
renderClaudeChat();
|
|
113
114
|
}
|
|
114
115
|
if (msg.type === 'claude-event') applyClaudeEvent(msg.event);
|
|
@@ -159,7 +160,7 @@
|
|
|
159
160
|
if (msg.type === 'codex-snapshot' && msg.snapshot) {
|
|
160
161
|
codexMessages = msg.snapshot.messages || [];
|
|
161
162
|
codexPendingPermissions = msg.snapshot.pendingPermissions || [];
|
|
162
|
-
applyCodexState(msg.snapshot.state || {});
|
|
163
|
+
applyCodexState(msg.snapshot.state || {}, { providerStateReceived: true });
|
|
163
164
|
}
|
|
164
165
|
if (msg.type === 'codex-detail-response' && msg.detail) applyCodexDetailResponse(msg);
|
|
165
166
|
if (msg.type === 'codex-event') applyCodexEvent(msg.event);
|
package/lib/web/shell.js
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
async function deleteSession(id, e) {
|
|
2
|
-
e
|
|
3
|
-
|
|
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
|
+
}
|
|
4
18
|
}
|
|
5
19
|
|
|
6
20
|
function showLobby() {
|
|
@@ -10,6 +24,7 @@
|
|
|
10
24
|
activeSessionId = null;
|
|
11
25
|
window.activeSessionId = null;
|
|
12
26
|
activeToolKey = null;
|
|
27
|
+
if (typeof resetComposerSendState === 'function') resetComposerSendState();
|
|
13
28
|
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
|
|
14
29
|
document.getElementById('lobby-view').classList.add('active');
|
|
15
30
|
refreshSessionsNow();
|
|
@@ -0,0 +1,197 @@
|
|
|
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();
|
package/lib/web/styles.css
CHANGED
|
@@ -4,17 +4,20 @@
|
|
|
4
4
|
.view.active { display: flex; }
|
|
5
5
|
#lobby { overflow-y: auto; padding: 20px; padding-top: env(safe-area-inset-top); box-sizing: border-box; }
|
|
6
6
|
.header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24px; }
|
|
7
|
-
.header
|
|
8
|
-
.
|
|
9
|
-
.
|
|
7
|
+
.header-actions { display: flex; align-items: center; gap: 5px; margin-left: auto; }
|
|
8
|
+
.skill-hall-entry-btn { height: 36px; padding: 0 11px; border: 1px solid rgba(0,122,255,.38); border-radius: 10px; background: rgba(0,122,255,.12); color: var(--primary); display: inline-flex; align-items: center; justify-content: center; gap: 5px; font-size: 13px; font-weight: 800; cursor: pointer; flex-shrink: 0; }
|
|
9
|
+
.skill-hall-entry-btn:active { background: rgba(0,122,255,.22); transform: scale(.97); }
|
|
10
|
+
.skill-hall-entry-btn:disabled { border-color: rgba(142,142,147,.25); background: rgba(142,142,147,.1); color: var(--text-dim); cursor: not-allowed; transform: none; }
|
|
10
11
|
.header-action-btn { height: 36px; min-width: 36px; padding: 0 10px; border: 0; border-radius: 10px; background: var(--primary); color: #fff; display: inline-flex; align-items: center; justify-content: center; gap: 3px; font-size: 13px; font-weight: 750; line-height: 1; white-space: nowrap; cursor: pointer; flex-shrink: 0; }
|
|
11
12
|
.header-action-btn.icon-only { width: 36px; padding: 0; }
|
|
12
13
|
.header-action-btn:active { background: #0062cc; transform: scale(.97); }
|
|
13
14
|
.btn-retry { background: #333; color: #fff; border: none; padding: 8px 16px; border-radius: 20px; margin-top: 10px; cursor: pointer; }
|
|
14
15
|
.session-card { background: var(--card-bg); border-radius: 12px; padding: 12px 16px 8px; margin-bottom: 12px; display: grid; grid-template-columns: minmax(0, 1fr) auto; column-gap: 10px; row-gap: 0; align-items: center; position: relative; }
|
|
16
|
+
.active-session-dot { display: none; }
|
|
15
17
|
.completion-dot { width: 9px; height: 9px; border-radius: 50%; background: #ff3b30; flex-shrink: 0; }
|
|
16
18
|
.session-info { flex: 1; min-width: 0; }
|
|
17
|
-
.session-info h3 { margin: 0 0 4px 0; font-size: 17px; display: flex; align-items: center; gap: 8px; }
|
|
19
|
+
.session-info h3 { min-width: 0; margin: 0 0 4px 0; font-size: 17px; display: flex; align-items: center; gap: 8px; }
|
|
20
|
+
.session-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
18
21
|
.session-info p { margin: 0; font-size: 13px; color: var(--text-dim); }
|
|
19
22
|
.timer-count-badge { display: inline-flex; align-items: center; gap: 3px; color: #fff; background: rgba(0,122,255,0.22); border: 1px solid rgba(0,122,255,0.34); border-radius: 999px; padding: 2px 7px; font-size: 11px; font-weight: 800; flex-shrink: 0; }
|
|
20
23
|
.session-dir-row { grid-column: 1 / -1; display: flex; align-items: center; gap: 6px; width: 100%; min-width: 0; }
|
|
@@ -37,7 +40,7 @@
|
|
|
37
40
|
#modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); z-index: 10000; display: none; align-items: center; justify-content: center; padding: 20px; }
|
|
38
41
|
#tool-modal { background: var(--card-bg); width: 100%; max-width: 400px; border-radius: 16px; padding: 20px; box-shadow: 0 20px 40px rgba(0,0,0,0.4); }
|
|
39
42
|
#settings-modal-overlay { position: fixed; inset: 0; background: rgba(0,0,0,0.8); z-index: 10020; display: none; align-items: center; justify-content: center; padding: 20px; box-sizing: border-box; }
|
|
40
|
-
#settings-modal { background: var(--card-bg); width: 100%; max-width: 450px; border-radius: 16px; padding: 20px; box-shadow: 0 20px 40px rgba(0,0,0,0.4); box-sizing: border-box; }
|
|
43
|
+
#settings-modal { background: var(--card-bg); width: 100%; max-width: 450px; max-height: calc(100vh - 40px); overflow-y: auto; border-radius: 16px; padding: 20px; box-shadow: 0 20px 40px rgba(0,0,0,0.4); box-sizing: border-box; }
|
|
41
44
|
.settings-modal-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; margin-bottom: 20px; }
|
|
42
45
|
.settings-modal-header h2 { margin: 0; font-size: 20px; }
|
|
43
46
|
.settings-modal-header p { margin: 5px 0 0; color: var(--text-dim); font-size: 12px; line-height: 1.45; }
|
|
@@ -58,6 +61,22 @@
|
|
|
58
61
|
.serverchan-modal-actions { display: flex; align-items: center; gap: 8px; }
|
|
59
62
|
.serverchan-action-spacer { flex: 1; }
|
|
60
63
|
#serverchan-remove-btn { display: none; }
|
|
64
|
+
#skillhub-remove-btn { display: none; }
|
|
65
|
+
.tool-modal-skill { min-height: 16px; margin: 4px 0 0; color: var(--text-dim); font-size: 12px; }
|
|
66
|
+
#skill-hall-overlay { position: fixed; inset: 0; z-index: 10010; display: none; align-items: center; justify-content: center; padding: 18px; box-sizing: border-box; background: rgba(0,0,0,0.8); }
|
|
67
|
+
#skill-hall-modal { display: flex; flex-direction: column; width: min(820px, 100%); max-height: min(760px, calc(100vh - 36px)); padding: 20px; box-sizing: border-box; border-radius: 16px; background: var(--card-bg); box-shadow: 0 20px 50px rgba(0,0,0,0.45); }
|
|
68
|
+
.skill-hall-header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; }
|
|
69
|
+
.skill-hall-header h2 { margin: 0; font-size: 21px; }
|
|
70
|
+
.skill-hall-header p { margin: 5px 0 0; color: var(--text-dim); font-size: 12px; }
|
|
71
|
+
.skill-hall-search { width: 100%; min-height: 42px; margin-top: 16px; padding: 9px 11px; box-sizing: border-box; border: 1px solid rgba(255,255,255,0.12); border-radius: 9px; background: rgba(255,255,255,0.08); color: var(--text); font-size: 14px; outline: none; }
|
|
72
|
+
.skill-hall-search:focus { border-color: rgba(0,122,255,0.65); }
|
|
73
|
+
.skill-hall-status { min-height: 20px; padding: 10px 1px 7px; color: var(--text-dim); font-size: 12px; }
|
|
74
|
+
.skill-hall-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: 10px; overflow-y: auto; padding: 1px; }
|
|
75
|
+
.skill-hall-card { display: flex; min-height: 126px; flex-direction: column; align-items: stretch; gap: 8px; padding: 14px; border: 1px solid rgba(255,255,255,0.1); border-radius: 12px; background: rgba(255,255,255,0.045); color: var(--text); text-align: left; cursor: pointer; }
|
|
76
|
+
.skill-hall-card:hover { border-color: rgba(0,122,255,0.55); background: rgba(0,122,255,0.09); }
|
|
77
|
+
.skill-hall-card-name { font-size: 15px; font-weight: 700; overflow-wrap: anywhere; }
|
|
78
|
+
.skill-hall-card-description { display: -webkit-box; overflow: hidden; color: var(--text-dim); font-size: 12px; line-height: 1.45; -webkit-box-orient: vertical; -webkit-line-clamp: 3; }
|
|
79
|
+
.skill-hall-card-meta { margin-top: auto; color: var(--primary); font-size: 11px; }
|
|
61
80
|
#app-toast { position: fixed; left: 50%; bottom: max(24px, env(safe-area-inset-bottom)); z-index: 10100; max-width: calc(100vw - 32px); transform: translate(-50%, 18px); opacity: 0; pointer-events: none; padding: 9px 14px; border-radius: 999px; background: rgba(44,44,46,0.96); border: 1px solid rgba(255,255,255,0.12); color: #fff; font-size: 13px; box-shadow: 0 10px 28px rgba(0,0,0,0.35); transition: opacity .18s ease, transform .18s ease; }
|
|
62
81
|
#app-toast.visible { opacity: 1; transform: translate(-50%, 0); }
|
|
63
82
|
.tool-item { padding: 12px; border-bottom: 1px solid #333; cursor: pointer; display: flex; align-items: center; border-radius: 8px; margin-top: 4px; }
|
|
@@ -143,6 +162,7 @@
|
|
|
143
162
|
#cmd-input::placeholder { color: rgba(255,255,255,0.45); }
|
|
144
163
|
#cmd-input:focus { background: rgba(255,255,255,0.1); border-color: rgba(0,122,255,0.42); color: #fff; }
|
|
145
164
|
#send-btn { width: 44px; height: 38px; margin-left: 10px; background: #007aff; border: none; border-radius: 19px; color: #fff; display: flex; align-items: center; justify-content: center; flex-shrink: 0; cursor: pointer; }
|
|
165
|
+
#send-btn:disabled { background: color-mix(in srgb, var(--text-dim) 34%, var(--surface)); color: var(--text-dim); opacity: .72; cursor: not-allowed; }
|
|
146
166
|
#attachment-strip { display: none; width: min(calc(100% - 28px), var(--control-content-max)); margin: -3px auto 2px auto; gap: 7px; overflow-x: auto; padding: 0 0 4px 0; box-sizing: border-box; scrollbar-width: none; }
|
|
147
167
|
#attachment-strip.active { display: flex; }
|
|
148
168
|
#attachment-strip::-webkit-scrollbar { display: none; }
|
|
@@ -671,14 +691,16 @@ html[data-theme="light"] .session-attention-pill.subagent { border-color: #99c8f
|
|
|
671
691
|
#lobby-view { display: flex !important; width: min(var(--sidebar-w), calc(100vw - 484px)); flex: 0 0 min(var(--sidebar-w), calc(100vw - 484px)); border-right: 1px solid var(--line); background: var(--bg); }
|
|
672
692
|
#lobby { width: 100%; padding: 18px 14px; }
|
|
673
693
|
#lobby-view .header { margin-bottom: 18px; }
|
|
674
|
-
#lobby-view .header
|
|
675
|
-
#lobby-view .header-action-btn
|
|
676
|
-
#lobby-view .header-action-btn:not(.icon-only)
|
|
694
|
+
#lobby-view .header-actions { gap: 3px; }
|
|
695
|
+
#lobby-view .header-action-btn.icon-only { width: 34px; min-width: 34px; }
|
|
696
|
+
#lobby-view .header-action-btn:not(.icon-only) { width: auto; min-width: 0; padding: 0 8px; font-size: 13px; }
|
|
697
|
+
#lobby-view .header-action-btn:not(.icon-only) span:first-child { font-size: 18px; }
|
|
677
698
|
#sidebar-resizer { display: block; width: 4px; flex: 0 0 4px; cursor: col-resize; background: var(--line); touch-action: none; transition: background .15s; }
|
|
678
699
|
#sidebar-resizer:hover, #sidebar-resizer.dragging { background: var(--primary); }
|
|
679
700
|
#detail-pane:not(:has(.view.active)) #detail-empty { display: flex; }
|
|
680
701
|
#back-btn, .usage-nav-button { visibility: hidden; pointer-events: none; }
|
|
681
702
|
.session-card.selected { border: 1px solid color-mix(in srgb, var(--primary) 48%, transparent); background: color-mix(in srgb, var(--primary) 10%, var(--card-bg)); }
|
|
703
|
+
.session-card.selected .active-session-dot { position: absolute; top: 7px; right: 7px; display: block; width: 9px; height: 9px; border: 2px solid var(--card-bg); border-radius: 50%; background: #34c759; box-shadow: 0 0 0 1px rgba(52,199,89,.25); pointer-events: none; }
|
|
682
704
|
}
|
|
683
705
|
|
|
684
706
|
@media (max-width: 919px) {
|
|
@@ -703,6 +725,7 @@ html[data-theme="light"] .usage-panel,
|
|
|
703
725
|
html[data-theme="light"] .usage-summary-card,
|
|
704
726
|
html[data-theme="light"] #tool-modal,
|
|
705
727
|
html[data-theme="light"] #settings-modal,
|
|
728
|
+
html[data-theme="light"] #skill-hall-modal,
|
|
706
729
|
html[data-theme="light"] #usage-source-modal,
|
|
707
730
|
html[data-theme="light"] #schedule-modal,
|
|
708
731
|
html[data-theme="light"] #claude-picker-panel,
|
|
@@ -715,6 +738,7 @@ html[data-theme="light"] #codex-prompt-panel,
|
|
|
715
738
|
html[data-theme="light"] #codex-skill-panel { background: var(--card-bg) !important; border-color: var(--line) !important; color: var(--text) !important; }
|
|
716
739
|
html[data-theme="light"] #modal-overlay,
|
|
717
740
|
html[data-theme="light"] #settings-modal-overlay,
|
|
741
|
+
html[data-theme="light"] #skill-hall-overlay,
|
|
718
742
|
html[data-theme="light"] #usage-source-overlay { background: var(--overlay-bg); }
|
|
719
743
|
html[data-theme="light"] #claude-chat-container,
|
|
720
744
|
html[data-theme="light"] #codex-chat-container,
|
|
@@ -741,11 +765,14 @@ html[data-theme="light"] .composer-action-btn,
|
|
|
741
765
|
html[data-theme="light"] .key-btn,
|
|
742
766
|
html[data-theme="light"] .command-key,
|
|
743
767
|
html[data-theme="light"] .small-btn { border-color: var(--line); background: var(--surface-soft); color: var(--text); }
|
|
768
|
+
html[data-theme="light"] .skill-hall-card { border-color: var(--line); background: var(--surface-soft); color: var(--text); }
|
|
769
|
+
html[data-theme="light"] .skill-hall-entry-btn { border-color: rgba(0,122,255,.28); background: rgba(0,122,255,.08); }
|
|
744
770
|
html[data-theme="light"] .claude-ctrl-btn.primary { color: #065fbd; background: rgba(0,122,255,.11); }
|
|
745
771
|
html[data-theme="light"] .claude-ctrl-btn.danger,
|
|
746
772
|
html[data-theme="light"] .small-btn.danger { color: #d92d20; background: rgba(217,45,32,.09); }
|
|
747
773
|
html[data-theme="light"] #settings-modal input,
|
|
748
774
|
html[data-theme="light"] #settings-modal select,
|
|
775
|
+
html[data-theme="light"] .skill-hall-search,
|
|
749
776
|
html[data-theme="light"] .form-field input,
|
|
750
777
|
html[data-theme="light"] .form-field select,
|
|
751
778
|
html[data-theme="light"] .form-field textarea,
|