claude-simple 0.1.0
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 +225 -0
- package/client.js +139 -0
- package/dist/addon-fit.min.js +2 -0
- package/dist/app.0.1.0.min.js +825 -0
- package/dist/highlight.min.css +10 -0
- package/dist/highlight.min.js +1244 -0
- package/dist/index.html +16 -0
- package/dist/marked.min.js +6 -0
- package/dist/style.0.1.0.min.css +1050 -0
- package/dist/xterm.min.css +209 -0
- package/dist/xterm.min.js +2 -0
- package/gateway.js +349 -0
- package/package.json +41 -0
- package/public/addon-fit.min.js +2 -0
- package/public/agent-panel.js +1079 -0
- package/public/api.js +78 -0
- package/public/app.js +95 -0
- package/public/base.css +4 -0
- package/public/cache.js +27 -0
- package/public/chat.js +1087 -0
- package/public/files.js +476 -0
- package/public/git.js +130 -0
- package/public/index.html +61 -0
- package/public/lib.js +2 -0
- package/public/router.js +71 -0
- package/public/settings.js +90 -0
- package/public/shell/boot.js +159 -0
- package/public/shell/dashboard.js +269 -0
- package/public/shell/hub.js +192 -0
- package/public/shell/layout.js +195 -0
- package/public/shell/notes.js +323 -0
- package/public/shell/session-context.js +19 -0
- package/public/shell/session-list.js +442 -0
- package/public/shell/session-nav.js +299 -0
- package/public/shell/templates.js +342 -0
- package/public/shell/util.js +63 -0
- package/public/shell.js +589 -0
- package/public/state.js +362 -0
- package/public/style.css +1048 -0
- package/public/terminal.js +177 -0
- package/public/xterm.min.css +209 -0
- package/public/xterm.min.js +2 -0
- package/server.js +12 -0
- package/src/agents/claude.js +58 -0
- package/src/agents/codex.js +284 -0
- package/src/agents/grok.js +318 -0
- package/src/agents/meta-agent.js +1952 -0
- package/src/agents/models.js +339 -0
- package/src/agents/sessions.js +1508 -0
- package/src/app.js +1282 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
// shell/hub.js — multi-machine picker, menu, polling
|
|
2
|
+
import { batch, esc, $ } from '../lib.js';
|
|
3
|
+
import {
|
|
4
|
+
machinesList, hubMode, hubMachineReady, selectedMachineId, setSelectedMachine,
|
|
5
|
+
currentProject, sessionFilter, viewingFile, ctx,
|
|
6
|
+
} from '../state.js';
|
|
7
|
+
import { api, probeHub } from '../api.js';
|
|
8
|
+
import { loadAllSessions, loadWorkspaces } from './session-list.js';
|
|
9
|
+
import { loadSessionMetaMap } from './notes.js';
|
|
10
|
+
// goHome imported dynamically in enterMachine to avoid circular deps
|
|
11
|
+
|
|
12
|
+
export async function refreshMachinesList() {
|
|
13
|
+
try {
|
|
14
|
+
const list = await api('GET', '/api/machines');
|
|
15
|
+
machinesList.value = Array.isArray(list) ? list : [];
|
|
16
|
+
} catch {
|
|
17
|
+
try {
|
|
18
|
+
const hub = await probeHub();
|
|
19
|
+
if (hub?.machines) machinesList.value = hub.machines;
|
|
20
|
+
} catch { /* ignore */ }
|
|
21
|
+
}
|
|
22
|
+
return machinesList.peek() || [];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function renderMachinePickerList() {
|
|
26
|
+
const listEl = $('machine-picker-list');
|
|
27
|
+
const emptyEl = $('machine-picker-empty');
|
|
28
|
+
if (!listEl) return;
|
|
29
|
+
const ms = machinesList.peek() || [];
|
|
30
|
+
if (!ms.length) {
|
|
31
|
+
listEl.innerHTML = '';
|
|
32
|
+
emptyEl?.classList.remove('hidden');
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
emptyEl?.classList.add('hidden');
|
|
36
|
+
listEl.innerHTML = ms.map(m => {
|
|
37
|
+
const online = m.online !== false;
|
|
38
|
+
const meta = [m.hostname, m.platform].filter(Boolean).join(' · ');
|
|
39
|
+
return `
|
|
40
|
+
<button type="button" class="machine-pick-card w-full text-left px-4 py-3 rounded-xl border border-base-300
|
|
41
|
+
hover:border-primary hover:bg-primary/5 transition-colors ${online ? '' : 'opacity-50'}"
|
|
42
|
+
data-pick-machine="${esc(m.id)}" ${online ? '' : 'disabled'}>
|
|
43
|
+
<div class="flex items-center gap-2">
|
|
44
|
+
<span class="w-2 h-2 rounded-full flex-shrink-0 ${online ? 'bg-success' : 'bg-error'}"></span>
|
|
45
|
+
<span class="font-semibold text-sm font-mono">${esc(m.id)}</span>
|
|
46
|
+
</div>
|
|
47
|
+
${meta ? `<div class="text-xs text-base-content/45 mt-1 pl-4">${esc(meta)}</div>` : ''}
|
|
48
|
+
</button>`;
|
|
49
|
+
}).join('');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function showMachinePicker() {
|
|
53
|
+
const el = $('machine-picker');
|
|
54
|
+
if (!el) return;
|
|
55
|
+
el.classList.remove('hidden');
|
|
56
|
+
hubMachineReady.value = false;
|
|
57
|
+
renderMachinePickerList();
|
|
58
|
+
const status = $('machine-picker-status');
|
|
59
|
+
if (status) {
|
|
60
|
+
const n = (machinesList.peek() || []).filter(m => m.online !== false).length;
|
|
61
|
+
status.textContent = `${n} online`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function hideMachinePicker() {
|
|
66
|
+
$('machine-picker')?.classList.add('hidden');
|
|
67
|
+
hubMachineReady.value = true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function closeMachineMenu() {
|
|
71
|
+
$('machine-menu')?.classList.add('hidden');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function renderMachineMenuList() {
|
|
75
|
+
const listEl = $('machine-menu-list');
|
|
76
|
+
const emptyEl = $('machine-menu-empty');
|
|
77
|
+
if (!listEl) return;
|
|
78
|
+
const ms = machinesList.peek() || [];
|
|
79
|
+
const cur = selectedMachineId.peek() || '';
|
|
80
|
+
if (!ms.length) {
|
|
81
|
+
listEl.innerHTML = '';
|
|
82
|
+
emptyEl?.classList.remove('hidden');
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
emptyEl?.classList.add('hidden');
|
|
86
|
+
listEl.innerHTML = ms.map(m => {
|
|
87
|
+
const online = m.online !== false;
|
|
88
|
+
const active = m.id === cur;
|
|
89
|
+
const meta = [m.hostname, m.platform].filter(Boolean).join(' · ');
|
|
90
|
+
const since = m.connectedAt
|
|
91
|
+
? new Date(m.connectedAt).toLocaleTimeString()
|
|
92
|
+
: '';
|
|
93
|
+
return `
|
|
94
|
+
<button type="button"
|
|
95
|
+
class="machine-menu-item w-full text-left px-2 py-1.5 rounded-md text-xs
|
|
96
|
+
${active ? 'bg-primary/15 text-primary' : 'hover:bg-base-200'}
|
|
97
|
+
${online ? '' : 'opacity-50'}"
|
|
98
|
+
data-pick-machine="${esc(m.id)}"
|
|
99
|
+
${online ? '' : 'disabled'}>
|
|
100
|
+
<div class="flex items-center gap-1.5">
|
|
101
|
+
<span class="w-1.5 h-1.5 rounded-full flex-shrink-0 ${online ? 'bg-success' : 'bg-error'}"></span>
|
|
102
|
+
<span class="font-mono font-semibold truncate flex-1">${esc(m.id)}</span>
|
|
103
|
+
${active ? '<span class="text-[9px] opacity-70">当前</span>' : ''}
|
|
104
|
+
<span class="text-[9px] ${online ? 'text-success' : 'text-error'}">${online ? 'online' : 'offline'}</span>
|
|
105
|
+
</div>
|
|
106
|
+
${meta || since ? `<div class="text-[10px] text-base-content/40 pl-3.5 mt-0.5 truncate">${esc(meta)}${since ? ' · ' + esc(since) : ''}</div>` : ''}
|
|
107
|
+
</button>`;
|
|
108
|
+
}).join('');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function syncTopbarMachine() {
|
|
112
|
+
const wrap = $('machine-menu-wrap');
|
|
113
|
+
const label = $('topbar-machine-label');
|
|
114
|
+
const dot = $('topbar-machine-dot');
|
|
115
|
+
if (!hubMode.peek()) {
|
|
116
|
+
wrap?.classList.add('hidden');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
wrap?.classList.remove('hidden');
|
|
120
|
+
const cur = selectedMachineId.peek() || '';
|
|
121
|
+
const ms = machinesList.peek() || [];
|
|
122
|
+
const info = ms.find(m => m.id === cur);
|
|
123
|
+
const online = info ? info.online !== false : false;
|
|
124
|
+
if (label) label.textContent = cur || '选择机器';
|
|
125
|
+
if (dot) {
|
|
126
|
+
dot.classList.toggle('bg-success', !!cur && online);
|
|
127
|
+
dot.classList.toggle('bg-warning', !!cur && !online);
|
|
128
|
+
dot.classList.toggle('bg-base-content/30', !cur);
|
|
129
|
+
}
|
|
130
|
+
renderMachineMenuList();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let _machinePollTimer = null;
|
|
134
|
+
export function startMachinePolling() {
|
|
135
|
+
if (_machinePollTimer) return;
|
|
136
|
+
_machinePollTimer = setInterval(async () => {
|
|
137
|
+
if (!hubMode.peek() || !ctx.token) return;
|
|
138
|
+
await refreshMachinesList();
|
|
139
|
+
syncTopbarMachine();
|
|
140
|
+
// If current machine went offline, warn in topbar label only (don't force picker)
|
|
141
|
+
const cur = selectedMachineId.peek();
|
|
142
|
+
if (cur) {
|
|
143
|
+
const m = (machinesList.peek() || []).find(x => x.id === cur);
|
|
144
|
+
if (m && m.online === false) {
|
|
145
|
+
const label = $('topbar-machine-label');
|
|
146
|
+
if (label) label.textContent = `${cur} (offline)`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}, 15000);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Enter / switch hub machine. */
|
|
153
|
+
export async function enterMachine(machineId, { forceReload = false } = {}) {
|
|
154
|
+
if (!machineId) return;
|
|
155
|
+
const prev = selectedMachineId.peek();
|
|
156
|
+
const switching = prev && prev !== machineId;
|
|
157
|
+
setSelectedMachine(machineId);
|
|
158
|
+
hideMachinePicker();
|
|
159
|
+
closeMachineMenu();
|
|
160
|
+
syncTopbarMachine();
|
|
161
|
+
|
|
162
|
+
if (switching || forceReload) {
|
|
163
|
+
try { ctx.ws?.close(); } catch {}
|
|
164
|
+
ctx.ws = null;
|
|
165
|
+
ctx.sessionId = null;
|
|
166
|
+
batch(() => {
|
|
167
|
+
currentProject.value = null;
|
|
168
|
+
sessionFilter.value = null;
|
|
169
|
+
viewingFile.value = null;
|
|
170
|
+
});
|
|
171
|
+
// Drop previous machine paths so Files/Git don't browse the wrong host
|
|
172
|
+
try {
|
|
173
|
+
const { filesRoot, gitRoot, filesPath } = await import('../state.js');
|
|
174
|
+
filesRoot.value = '';
|
|
175
|
+
gitRoot.value = '';
|
|
176
|
+
filesPath.value = '';
|
|
177
|
+
} catch { /* ignore */ }
|
|
178
|
+
if (switching) {
|
|
179
|
+
const { goHome } = await import('./session-nav.js');
|
|
180
|
+
goHome();
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const bar = $('topbar-project');
|
|
185
|
+
if (bar && (!currentProject.peek())) {
|
|
186
|
+
bar.textContent = `Machine · ${machineId}`;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
await Promise.all([loadAllSessions(), loadWorkspaces(), loadSessionMetaMap()]);
|
|
190
|
+
hubMachineReady.value = true;
|
|
191
|
+
}
|
|
192
|
+
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
// shell/layout.js — sidebar, theme, new-session modal
|
|
2
|
+
import { batch, esc, $ } from '../lib.js';
|
|
3
|
+
import {
|
|
4
|
+
workspacesData, sessionFilter, filesPath, viewingFile, currentProject,
|
|
5
|
+
filesRoot, gitRoot, currentModel, currentAgent, setAgent,
|
|
6
|
+
hubMode, machinesList, selectedMachineId, setSelectedMachine,
|
|
7
|
+
getDefaultModel, AGENT_LABELS, ctx,
|
|
8
|
+
} from '../state.js';
|
|
9
|
+
// filesRoot/gitRoot used when starting a new session (default paths)
|
|
10
|
+
import { api } from '../api.js';
|
|
11
|
+
import { connectWS, clearMessages, appendSystemMsg, applyChatDensity } from '../chat.js';
|
|
12
|
+
import { refreshModelSelect } from './session-list.js';
|
|
13
|
+
import { switchTab } from './session-nav.js';
|
|
14
|
+
import { showMachinePicker } from './hub.js';
|
|
15
|
+
|
|
16
|
+
// ── Sidebar ───────────────────────────────────────────────────────────────────
|
|
17
|
+
export function openSidebar() { $('sidebar').classList.add('open'); $('sidebar-overlay').classList.remove('hidden'); }
|
|
18
|
+
export function closeSidebar() { $('sidebar').classList.remove('open'); $('sidebar-overlay').classList.add('hidden'); }
|
|
19
|
+
export function closeSidebarOnMobile() { if (window.innerWidth <= 640) closeSidebar(); }
|
|
20
|
+
|
|
21
|
+
export function initSidebarToggle() {
|
|
22
|
+
const btn = $('sidebar-toggle');
|
|
23
|
+
const sidebar = $('sidebar');
|
|
24
|
+
let collapsed = false;
|
|
25
|
+
let savedWidth = sidebar.style.width || '260px';
|
|
26
|
+
|
|
27
|
+
btn.addEventListener('click', () => {
|
|
28
|
+
collapsed = !collapsed;
|
|
29
|
+
if (collapsed) {
|
|
30
|
+
savedWidth = sidebar.style.width || '260px';
|
|
31
|
+
sidebar.style.width = '0';
|
|
32
|
+
sidebar.style.minWidth = '0';
|
|
33
|
+
sidebar.style.overflow = 'hidden';
|
|
34
|
+
} else {
|
|
35
|
+
sidebar.style.width = savedWidth;
|
|
36
|
+
sidebar.style.minWidth = '';
|
|
37
|
+
sidebar.style.overflow = '';
|
|
38
|
+
}
|
|
39
|
+
btn.textContent = collapsed ? '▶' : '◀';
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function initSidebarResize() {
|
|
44
|
+
const handle = $('sidebar-resize');
|
|
45
|
+
const sidebar = $('sidebar');
|
|
46
|
+
let dragging = false;
|
|
47
|
+
|
|
48
|
+
handle.addEventListener('mousedown', e => {
|
|
49
|
+
dragging = true;
|
|
50
|
+
document.body.style.userSelect = 'none';
|
|
51
|
+
document.body.style.cursor = 'col-resize';
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
});
|
|
54
|
+
document.addEventListener('mousemove', e => {
|
|
55
|
+
if (!dragging) return;
|
|
56
|
+
const w = Math.min(Math.max(e.clientX, 160), 520);
|
|
57
|
+
sidebar.style.width = w + 'px';
|
|
58
|
+
sidebar.style.minWidth = '';
|
|
59
|
+
});
|
|
60
|
+
document.addEventListener('mouseup', () => {
|
|
61
|
+
if (!dragging) return;
|
|
62
|
+
dragging = false;
|
|
63
|
+
document.body.style.userSelect = '';
|
|
64
|
+
document.body.style.cursor = '';
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ── Theme ─────────────────────────────────────────────────────────────────────
|
|
69
|
+
export function applyTheme(theme) {
|
|
70
|
+
document.documentElement.setAttribute('data-theme', theme);
|
|
71
|
+
localStorage.setItem('theme', theme);
|
|
72
|
+
const btn = $('btn-theme');
|
|
73
|
+
if (btn) btn.textContent = theme === 'dark' || theme === 'night' || theme === 'black' ? '🌙' : '☀️';
|
|
74
|
+
// Swap highlight.js stylesheet for light/dark
|
|
75
|
+
let link = document.getElementById('hljs-theme');
|
|
76
|
+
if (!link) {
|
|
77
|
+
link = document.querySelector('link[href*="highlight"][href*="styles"]');
|
|
78
|
+
if (link) link.id = 'hljs-theme';
|
|
79
|
+
}
|
|
80
|
+
if (link) {
|
|
81
|
+
const dark = theme === 'dark' || theme === 'night' || theme === 'black';
|
|
82
|
+
link.href = dark
|
|
83
|
+
? 'https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11/styles/github-dark.min.css'
|
|
84
|
+
: 'https://cdn.jsdelivr.net/npm/@highlightjs/cdn-assets@11/styles/github.min.css';
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function toggleTheme() {
|
|
89
|
+
const cur = document.documentElement.getAttribute('data-theme') || 'dark';
|
|
90
|
+
applyTheme(cur === 'dark' ? 'light' : 'dark');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// ── New Session modal ─────────────────────────────────────────────────────────
|
|
94
|
+
export function openNewSessionModal() {
|
|
95
|
+
const agentSel = $('new-session-agent');
|
|
96
|
+
if (agentSel) agentSel.value = currentAgent.peek() || 'claude';
|
|
97
|
+
const sel = $('new-session-workspace');
|
|
98
|
+
if (sel) {
|
|
99
|
+
const wss = workspacesData.peek();
|
|
100
|
+
sel.innerHTML = wss.map(w =>
|
|
101
|
+
`<option value="${esc(w.configDir)}">${esc(w.name)} — ${esc(w.configDir)}</option>`
|
|
102
|
+
).join('');
|
|
103
|
+
// pre-select current session's workspace
|
|
104
|
+
if (ctx.configDir) sel.value = ctx.configDir;
|
|
105
|
+
}
|
|
106
|
+
const mwrap = $('new-session-machine-wrap');
|
|
107
|
+
const msel = $('new-session-machine');
|
|
108
|
+
if (mwrap && msel) {
|
|
109
|
+
// Machine already chosen at login; still show locked selection for clarity
|
|
110
|
+
if (hubMode.peek()) {
|
|
111
|
+
mwrap.style.display = '';
|
|
112
|
+
const ms = machinesList.peek() || [];
|
|
113
|
+
const cur = selectedMachineId.peek() || '';
|
|
114
|
+
msel.innerHTML = ms.length
|
|
115
|
+
? ms.map(m => `<option value="${esc(m.id)}">${esc(m.id)}${m.hostname ? ' — ' + esc(m.hostname) : ''}</option>`).join('')
|
|
116
|
+
: '<option value="">(no machines online)</option>';
|
|
117
|
+
if (cur) msel.value = cur;
|
|
118
|
+
msel.disabled = true;
|
|
119
|
+
} else {
|
|
120
|
+
mwrap.style.display = 'none';
|
|
121
|
+
if (msel) msel.disabled = false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
updateNewSessionAgentUI();
|
|
125
|
+
$('new-session-path').value = currentProject.peek()?.path || '';
|
|
126
|
+
$('new-session-error').classList.add('hidden');
|
|
127
|
+
$('modal-new-session').showModal();
|
|
128
|
+
setTimeout(() => $('new-session-path').focus(), 50);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function updateNewSessionAgentUI() {
|
|
132
|
+
const agent = $('new-session-agent')?.value || 'claude';
|
|
133
|
+
const wrap = $('new-session-ws-wrap');
|
|
134
|
+
if (wrap) wrap.style.display = agent === 'claude' ? '' : 'none';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export async function startNewSession() {
|
|
138
|
+
const rawPath = $('new-session-path').value.trim();
|
|
139
|
+
const agent = $('new-session-agent')?.value || 'claude';
|
|
140
|
+
const configDir = agent === 'claude' ? $('new-session-workspace').value : null;
|
|
141
|
+
const errEl = $('new-session-error');
|
|
142
|
+
errEl.classList.add('hidden');
|
|
143
|
+
|
|
144
|
+
if (!rawPath) { errEl.textContent = 'Working directory is required'; errEl.classList.remove('hidden'); return; }
|
|
145
|
+
|
|
146
|
+
// Hub: new session uses currently selected machine
|
|
147
|
+
if (hubMode.peek()) {
|
|
148
|
+
const mid = selectedMachineId.peek() || $('new-session-machine')?.value || machinesList.peek()?.[0]?.id;
|
|
149
|
+
if (!mid) {
|
|
150
|
+
errEl.textContent = '请先选择一台机器';
|
|
151
|
+
errEl.classList.remove('hidden');
|
|
152
|
+
showMachinePicker();
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
setSelectedMachine(mid);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
try {
|
|
159
|
+
const result = await api('POST', '/api/resolve-path', { path: rawPath });
|
|
160
|
+
if (!result.isDir) throw new Error('Path is not a directory');
|
|
161
|
+
|
|
162
|
+
$('modal-new-session').close();
|
|
163
|
+
|
|
164
|
+
const name = result.path.split('/').filter(Boolean).pop() || result.path;
|
|
165
|
+
ctx.sessionId = null;
|
|
166
|
+
ctx.configDir = configDir || null;
|
|
167
|
+
ctx.agent = agent;
|
|
168
|
+
setAgent(agent);
|
|
169
|
+
currentModel.value = getDefaultModel(agent) || currentModel.peek();
|
|
170
|
+
localStorage.setItem('model', currentModel.peek());
|
|
171
|
+
refreshModelSelect();
|
|
172
|
+
batch(() => {
|
|
173
|
+
currentProject.value = { id: name, name, path: result.path, machineId: selectedMachineId.peek() || ctx.machineId };
|
|
174
|
+
sessionFilter.value = result.path;
|
|
175
|
+
filesRoot.value = result.path;
|
|
176
|
+
gitRoot.value = result.path;
|
|
177
|
+
filesPath.value = '';
|
|
178
|
+
viewingFile.value = null;
|
|
179
|
+
});
|
|
180
|
+
$('topbar-project').textContent = result.path;
|
|
181
|
+
const fi = $('files-root-input'); if (fi) fi.value = result.path;
|
|
182
|
+
const gi = $('git-root-input'); if (gi) gi.value = result.path;
|
|
183
|
+
$('welcome').classList.add('hidden');
|
|
184
|
+
const pv = $('project-view');
|
|
185
|
+
pv.classList.remove('hidden');
|
|
186
|
+
pv.style.display = 'flex';
|
|
187
|
+
clearMessages();
|
|
188
|
+
connectWS();
|
|
189
|
+
switchTab('chat');
|
|
190
|
+
appendSystemMsg(`New ${AGENT_LABELS[agent] || agent} session · ${result.path}`);
|
|
191
|
+
} catch (e) {
|
|
192
|
+
errEl.textContent = e.message;
|
|
193
|
+
errEl.classList.remove('hidden');
|
|
194
|
+
}
|
|
195
|
+
}
|