@bolloon/bolloon-agent 0.1.1 → 0.1.3
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/bin/bolloon-cli.cjs +165 -0
- package/bin/bolloon-daemon.sh +207 -0
- package/bin/bolloon.cmd +11 -0
- package/dist/agents/constraint-layer.js +10 -15
- package/dist/agents/pi-sdk.js +433 -106
- package/dist/agents/protocol.js +82 -1
- package/dist/agents/subagent-manager.js +2 -2
- package/dist/agents/workflow-engine.js +15 -20
- package/dist/agents/workflow-pivot-loop.js +541 -0
- package/dist/bollharness/src/index.js +5 -0
- package/dist/bollharness/src/scripts/checks/check_adr_plan_numbering.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_api_types.js +45 -0
- package/dist/bollharness/src/scripts/checks/check_artifact_link.js +146 -0
- package/dist/bollharness/src/scripts/checks/check_bridge_deps.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_bugfix_binding.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_bugfix_binding_ci.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_doc_file_references.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_doc_freshness.js +135 -0
- package/dist/bollharness/src/scripts/checks/check_doc_links.js +31 -0
- package/dist/bollharness/src/scripts/checks/check_file_existence_claims.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_fragment_integrity.js +34 -0
- package/dist/bollharness/src/scripts/checks/check_hook_installed.js +63 -0
- package/dist/bollharness/src/scripts/checks/check_issue_closure.js +41 -0
- package/dist/bollharness/src/scripts/checks/check_mcp_parity.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_security.js +48 -0
- package/dist/bollharness/src/scripts/checks/check_skill_parity.js +6 -0
- package/dist/bollharness/src/scripts/checks/check_versions.js +6 -0
- package/dist/bollharness/src/scripts/checks/finding.js +13 -0
- package/dist/bollharness/src/scripts/checks/next_decision_number.js +20 -0
- package/dist/bollharness/src/scripts/checks/regenerate_magic_docs.js +6 -0
- package/dist/bollharness/src/scripts/ci/detect_rebaseline_triggers.js +8 -0
- package/dist/bollharness/src/scripts/ci/scan_subprocess_cfg.js +8 -0
- package/dist/bollharness/src/scripts/ci/scan_verify_artifacts.js +8 -0
- package/dist/bollharness/src/scripts/ci/scan_yaml_schema.js +8 -0
- package/dist/bollharness/src/scripts/context_router.js +67 -0
- package/dist/bollharness/src/scripts/deploy-guard.js +157 -0
- package/dist/bollharness/src/scripts/guard-feedback.js +192 -0
- package/dist/bollharness/src/scripts/guard_router.js +158 -0
- package/dist/bollharness/src/scripts/hooks/_hook_output.js +6 -0
- package/dist/bollharness/src/scripts/hooks/auto-python3.js +6 -0
- package/dist/bollharness/src/scripts/hooks/deploy-progress-on-session-end.js +6 -0
- package/dist/bollharness/src/scripts/hooks/failure-analyzer.js +6 -0
- package/dist/bollharness/src/scripts/hooks/gate-judgment-inject.js +92 -0
- package/dist/bollharness/src/scripts/hooks/gate-transition-judgment.js +63 -0
- package/dist/bollharness/src/scripts/hooks/inbox-ack.js +6 -0
- package/dist/bollharness/src/scripts/hooks/inbox-inject-on-start.js +6 -0
- package/dist/bollharness/src/scripts/hooks/inbox-validate.js +6 -0
- package/dist/bollharness/src/scripts/hooks/inbox-write-ledger.js +6 -0
- package/dist/bollharness/src/scripts/hooks/initializer-agent.js +6 -0
- package/dist/bollharness/src/scripts/hooks/loop-detection.js +73 -0
- package/dist/bollharness/src/scripts/hooks/owner-guard.js +6 -0
- package/dist/bollharness/src/scripts/hooks/precompact.js +6 -0
- package/dist/bollharness/src/scripts/hooks/review-agent-gatekeeper.js +6 -0
- package/dist/bollharness/src/scripts/hooks/risk-tracker.js +108 -0
- package/dist/bollharness/src/scripts/hooks/sanitize-on-read.js +6 -0
- package/dist/bollharness/src/scripts/hooks/session-reflection.js +7 -0
- package/dist/bollharness/src/scripts/hooks/session-start-magic-docs.js +7 -0
- package/dist/bollharness/src/scripts/hooks/session-start-reset-risk.js +7 -0
- package/dist/bollharness/src/scripts/hooks/session-start-toolkit-reminder.js +7 -0
- package/dist/bollharness/src/scripts/hooks/stop-evaluator.js +157 -0
- package/dist/bollharness/src/scripts/hooks/tool-call-counter.js +6 -0
- package/dist/bollharness/src/scripts/hooks/trace-analyzer.js +10 -0
- package/dist/bollharness/src/scripts/install/install-trust-token.js +7 -0
- package/dist/bollharness/src/scripts/install/multi_project_registry.js +9 -0
- package/dist/bollharness/src/scripts/install/phase2_auto.js +21 -0
- package/dist/bollharness/src/scripts/install/pre_commit_installer.js +6 -0
- package/dist/bollharness/src/scripts/install/tier_selector.js +7 -0
- package/dist/bollharness/src/scripts/install/transcript_miner.js +7 -0
- package/dist/bollharness/src/scripts/lib/claim_patterns.js +10 -0
- package/dist/bollharness/src/scripts/lib/sanitize_patterns.js +12 -0
- package/dist/bollharness/src/scripts/sanitize.js +6 -0
- package/dist/bollharness-integration/channel-judgment-engine.js +530 -0
- package/dist/bollharness-integration/context-chain-router.js +383 -0
- package/dist/bollharness-integration/context-router-judgment.js +13 -21
- package/dist/bollharness-integration/context-router.js +22 -64
- package/dist/bollharness-integration/gate-state-machine.js +14 -19
- package/dist/bollharness-integration/gate-transition-hooks.js +16 -61
- package/dist/bollharness-integration/guard-checker.js +21 -68
- package/dist/bollharness-integration/index.js +14 -124
- package/dist/bollharness-integration/integration.js +13 -20
- package/dist/bollharness-integration/llm-judgment-engine.js +569 -0
- package/dist/bollharness-integration/skill-adapter.js +18 -64
- package/dist/cli-entry.js +261 -0
- package/dist/constraint-runtime/src/commands.js +17 -7
- package/dist/constraint-runtime/src/constraint/budget.js +1 -6
- package/dist/constraint-runtime/src/constraint/permission.js +1 -6
- package/dist/constraint-runtime/src/models.js +1 -3
- package/dist/constraint-runtime/src/tools.js +17 -7
- package/dist/constraints/index.js +1 -7
- package/dist/documents/reader.js +8 -49
- package/dist/heartbeat/DaemonManager.js +242 -0
- package/dist/heartbeat/HealthMonitor.js +285 -0
- package/dist/heartbeat/StartupVerifier.js +205 -0
- package/dist/heartbeat/Watchdog.js +168 -0
- package/dist/heartbeat/index.js +84 -0
- package/dist/heartbeat/types.js +5 -0
- package/dist/index.js +381 -28
- package/dist/llm/config-store.js +31 -57
- package/dist/llm/llm-judgment-client.js +389 -0
- package/dist/llm/pi-ai.js +9 -52
- package/dist/network/agent-network.js +46 -90
- package/dist/network/hybrid-messenger.js +125 -0
- package/dist/network/iroh-bootstrap.js +38 -0
- package/dist/network/iroh-discovery.js +145 -0
- package/dist/network/iroh-integration.js +9 -16
- package/dist/network/iroh-transport.js +10 -48
- package/dist/network/p2p.js +23 -62
- package/dist/network/storage/adapters/json-adapter.js +4 -42
- package/dist/network/storage/index.js +147 -0
- package/dist/network/storage/types.js +14 -0
- package/dist/pi-ecosystem/index.js +233 -0
- package/dist/pi-ecosystem-colony/index.js +29 -90
- package/dist/pi-ecosystem-goals/index.js +20 -74
- package/dist/pi-ecosystem-judgment/decision.js +29 -47
- package/dist/pi-ecosystem-judgment/distillation.js +16 -29
- package/dist/pi-ecosystem-judgment/human-value-store.js +13 -60
- package/dist/pi-ecosystem-judgment/index.js +21 -74
- package/dist/pi-ecosystem-judgment/value-injection.js +26 -72
- package/dist/pi-ecosystem-mcp/index.js +24 -78
- package/dist/pi-ecosystem-subagents/index.js +20 -69
- package/dist/social/ant-colony/AdaptiveHeartbeat.js +3 -8
- package/dist/social/ant-colony/PheromoneEngine.js +11 -49
- package/dist/social/ant-colony/index.js +6 -0
- package/dist/social/ant-colony/types.js +4 -8
- package/dist/social/channels/ChannelManager.js +8 -46
- package/dist/social/channels/DiapChannelBridge.js +9 -47
- package/dist/social/channels/InterestMatcher.js +2 -7
- package/dist/social/channels/channel-agent-session.js +309 -0
- package/dist/social/channels/channel-heartbeat-agent.js +494 -0
- package/dist/social/channels/diap-doc-parser.js +204 -0
- package/dist/social/channels/harness-workflow-integrator.js +446 -0
- package/dist/social/channels/index.js +9 -0
- package/dist/social/channels/types.js +3 -7
- package/dist/social/global-shared-context.js +6 -47
- package/dist/social/heartbeat.js +29 -72
- package/dist/social/persona/enhanced-persona.js +299 -0
- package/dist/web/client.js +302 -136
- package/dist/web/components/p2p/index.js +159 -9
- package/dist/web/components/p2p/p2p-connection.js +136 -0
- package/dist/web/components/p2p/p2p-manager.js +24 -0
- package/dist/web/components/p2p/p2p-store-memory.js +1 -1
- package/dist/web/components/p2p/types.js +7 -0
- package/dist/web/index.html +5 -0
- package/dist/web/style.css +118 -0
- package/package.json +12 -6
- package/scripts/build-cli.js +206 -0
- package/scripts/postinstall.js +153 -0
- package/src/agents/pi-sdk.ts +347 -28
- package/src/agents/protocol.ts +95 -1
- package/src/agents/workflow-pivot-loop.ts +674 -0
- package/src/bollharness/CLAUDE.md +73 -0
- package/src/bollharness/README.md +143 -0
- package/src/bollharness/README.zh-CN.md +131 -0
- package/src/bollharness/reference/boll-reference/scripts/hooks/stop-evaluator.md +57 -0
- package/src/bollharness/scripts/context-fragments/artifact-linkage.md +14 -0
- package/src/bollharness/scripts/context-fragments/auth-consumers.md +17 -0
- package/src/bollharness/scripts/context-fragments/bridge-constitution.md +13 -0
- package/src/bollharness/scripts/context-fragments/catalyst-distributed.md +18 -0
- package/src/bollharness/scripts/context-fragments/closure-checklist.md +13 -0
- package/src/bollharness/scripts/context-fragments/contract-consumers.md +15 -0
- package/src/bollharness/scripts/context-fragments/db-shared-structures.md +15 -0
- package/src/bollharness/scripts/context-fragments/fixed-three-layers.md +19 -0
- package/src/bollharness/scripts/context-fragments/general-dev-principles.md +11 -0
- package/src/bollharness/scripts/context-fragments/issue-first.md +8 -0
- package/src/bollharness/scripts/context-fragments/mcp-parity.md +16 -0
- package/src/bollharness/scripts/context-fragments/pi-agent-operations.md +108 -0
- package/src/bollharness/scripts/context-fragments/protocol-consumers.md +15 -0
- package/src/bollharness/scripts/context-fragments/run-events-consumers.md +15 -0
- package/src/bollharness/scripts/context-fragments/scene-fidelity.md +13 -0
- package/src/bollharness/scripts/context-fragments/truth-source-hierarchy.md +15 -0
- package/src/bollharness/scripts/context-fragments/two-language.md +15 -0
- package/src/bollharness/scripts/context-fragments/version-sources.md +14 -0
- package/src/bollharness/scripts/hooks/stop-evaluator.md +83 -0
- package/src/bollharness/templates/scaffold/CLAUDE.md +89 -0
- package/src/cli-entry.ts +304 -0
- package/src/heartbeat/DaemonManager.ts +283 -0
- package/src/heartbeat/HealthMonitor.ts +316 -0
- package/src/heartbeat/StartupVerifier.ts +223 -0
- package/src/heartbeat/Watchdog.ts +198 -0
- package/src/heartbeat/index.ts +108 -0
- package/src/heartbeat/types.ts +82 -0
- package/src/llm/config-store.ts +23 -5
- package/src/network/iroh-transport.ts +3 -3
- package/src/web/client.js +302 -136
- package/src/web/components/p2p/P2PModal.tsx +91 -3
- package/src/web/components/p2p/index.ts +171 -9
- package/src/web/components/p2p/p2p-connection.ts +153 -1
- package/src/web/components/p2p/p2p-manager.ts +39 -1
- package/src/web/components/p2p/p2p-store-memory.ts +1 -1
- package/src/web/components/p2p/p2p-tools.ts +315 -0
- package/src/web/components/p2p/types.ts +58 -0
- package/src/web/design.md +99 -0
- package/src/web/index.html +5 -0
- package/src/web/server.ts +353 -36
- package/src/web/style.css +118 -0
- package/tsconfig.cli.json +16 -0
- package/tsconfig.electron.json +1 -1
- package/tsconfig.json +1 -2
- package/dist/web/server.js +0 -1647
- package/dist/web/server.js.map +0 -1
package/dist/web/client.js
CHANGED
|
@@ -10,16 +10,20 @@ const newChannelInput = document.getElementById('new-channel-input');
|
|
|
10
10
|
const channelNameEl = document.getElementById('channel-name');
|
|
11
11
|
const loadSessionBtn = document.getElementById('load-session-btn');
|
|
12
12
|
const sessionFileInput = document.getElementById('session-file-input');
|
|
13
|
+
const newSessionBtn = document.getElementById('new-session-btn');
|
|
13
14
|
|
|
14
|
-
let
|
|
15
|
+
let eventSources = new Map(); // channelId -> EventSource
|
|
15
16
|
let currentChannelId = null;
|
|
16
17
|
let currentAgentId = '';
|
|
17
18
|
let channels = [];
|
|
18
19
|
let isSidebarCollapsed = false;
|
|
19
|
-
let reconnectAttempts =
|
|
20
|
-
let
|
|
20
|
+
let reconnectAttempts = new Map(); // channelId -> attempts
|
|
21
|
+
let reconnectTimers = new Map(); // channelId -> timer
|
|
21
22
|
let lastUserCommand = ''; // 防止用户消息重复显示
|
|
22
23
|
let lastAiContent = ''; // 防止 AI 消息重复显示
|
|
24
|
+
let messagesContainers = new Map(); // channelId -> messages container div
|
|
25
|
+
let sessionMessages = new Map(); // channelId:sessionId -> messages array
|
|
26
|
+
let currentSessionId = null; // 当前显示的 session ID
|
|
23
27
|
|
|
24
28
|
function generateId() {
|
|
25
29
|
return crypto.randomUUID();
|
|
@@ -149,6 +153,57 @@ async function deleteChannel(channelId, e) {
|
|
|
149
153
|
}
|
|
150
154
|
}
|
|
151
155
|
|
|
156
|
+
async function createNewSession() {
|
|
157
|
+
if (!currentChannelId) {
|
|
158
|
+
console.log('[新会话] 没有选中的频道');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
try {
|
|
162
|
+
// 保存当前 session 的消息
|
|
163
|
+
const channel = channels.find(c => c.id === currentChannelId);
|
|
164
|
+
if (channel && currentSessionId) {
|
|
165
|
+
const container = messagesContainers.get(currentChannelId);
|
|
166
|
+
if (container) {
|
|
167
|
+
const messages = Array.from(container.querySelectorAll('.message')).map(msg => ({
|
|
168
|
+
type: msg.classList.contains('message-user') ? 'user' : 'ai',
|
|
169
|
+
content: msg.querySelector('.message-content')?.textContent || ''
|
|
170
|
+
}));
|
|
171
|
+
sessionMessages.set(`${currentChannelId}:${currentSessionId}`, messages);
|
|
172
|
+
console.log('[新会话] 保存旧 session 消息:', messages.length);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const res = await fetch(`/channels/${currentChannelId}/sessions`, {
|
|
177
|
+
method: 'POST'
|
|
178
|
+
});
|
|
179
|
+
const data = await res.json();
|
|
180
|
+
console.log('[新会话] 创建成功:', data);
|
|
181
|
+
|
|
182
|
+
// 更新本地频道数据
|
|
183
|
+
if (channel) {
|
|
184
|
+
if (!channel.sessions) channel.sessions = [];
|
|
185
|
+
channel.sessions.push(data.session);
|
|
186
|
+
channel.currentSessionId = data.currentSessionId;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// 切换到新 session
|
|
190
|
+
const oldSessionId = currentSessionId;
|
|
191
|
+
currentSessionId = data.currentSessionId;
|
|
192
|
+
|
|
193
|
+
// 清空容器并加载新 session
|
|
194
|
+
const container = messagesContainers.get(currentChannelId);
|
|
195
|
+
if (container) {
|
|
196
|
+
container.innerHTML = '';
|
|
197
|
+
showChannelView(currentChannelId);
|
|
198
|
+
addMessage('你好!新会话已开始,有什么我可以帮你的吗?', 'ai', false, container);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
console.log('[新会话] 已切换到:', data.currentSessionId);
|
|
202
|
+
} catch (err) {
|
|
203
|
+
console.error('Failed to create new session:', err);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
152
207
|
function renderChannels() {
|
|
153
208
|
if (!channelList) return;
|
|
154
209
|
channelList.innerHTML = '';
|
|
@@ -184,73 +239,131 @@ function renderCollapsedChannels() {
|
|
|
184
239
|
return;
|
|
185
240
|
}
|
|
186
241
|
|
|
242
|
+
function ensureMessageContainer(channelId) {
|
|
243
|
+
if (!messagesContainers.has(channelId)) {
|
|
244
|
+
const container = document.createElement('div');
|
|
245
|
+
container.className = 'channel-messages';
|
|
246
|
+
container.id = `channel-messages-${channelId}`;
|
|
247
|
+
container.style.display = 'none';
|
|
248
|
+
messagesEl.appendChild(container);
|
|
249
|
+
messagesContainers.set(channelId, container);
|
|
250
|
+
}
|
|
251
|
+
return messagesContainers.get(channelId);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function showChannelView(channelId) {
|
|
255
|
+
// Hide all channel message containers
|
|
256
|
+
messagesContainers.forEach((container, cid) => {
|
|
257
|
+
container.style.display = 'none';
|
|
258
|
+
});
|
|
259
|
+
// Show the selected channel's container
|
|
260
|
+
const container = messagesContainers.get(channelId);
|
|
261
|
+
if (container) {
|
|
262
|
+
container.style.display = 'block';
|
|
263
|
+
}
|
|
264
|
+
// Update messagesEl reference for functions that use it directly
|
|
265
|
+
messagesEl.innerHTML = '';
|
|
266
|
+
if (container) {
|
|
267
|
+
messagesEl.appendChild(container);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
187
271
|
async function selectChannel(channelId) {
|
|
188
272
|
console.log('[selectChannel] 开始切换到:', channelId);
|
|
189
273
|
|
|
274
|
+
// 保存当前 session 的消息
|
|
275
|
+
if (currentChannelId && currentSessionId) {
|
|
276
|
+
const container = messagesContainers.get(currentChannelId);
|
|
277
|
+
if (container) {
|
|
278
|
+
const messages = Array.from(container.querySelectorAll('.message')).map(msg => ({
|
|
279
|
+
type: msg.classList.contains('message-user') ? 'user' : 'ai',
|
|
280
|
+
content: msg.querySelector('.message-content')?.textContent || ''
|
|
281
|
+
}));
|
|
282
|
+
if (messages.length > 0) {
|
|
283
|
+
sessionMessages.set(`${currentChannelId}:${currentSessionId}`, messages);
|
|
284
|
+
console.log('[selectChannel] 保存 session 消息:', messages.length);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
190
289
|
// 立即更新当前频道 ID
|
|
191
290
|
currentChannelId = channelId;
|
|
192
|
-
reconnectAttempts
|
|
291
|
+
reconnectAttempts.set(channelId, 0);
|
|
193
292
|
|
|
194
|
-
//
|
|
293
|
+
// 找到当前频道和 session
|
|
195
294
|
const channel = channels.find(c => c.id === channelId);
|
|
196
|
-
if (channel
|
|
197
|
-
channelNameEl.textContent = channel.name;
|
|
198
|
-
|
|
295
|
+
if (channel) {
|
|
296
|
+
if (channelNameEl) channelNameEl.textContent = channel.name;
|
|
297
|
+
currentSessionId = channel.currentSessionId || 'default';
|
|
298
|
+
console.log('[selectChannel] 频道:', channel.name, 'session:', currentSessionId);
|
|
199
299
|
}
|
|
200
300
|
|
|
201
301
|
renderChannels();
|
|
202
302
|
|
|
203
|
-
//
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
303
|
+
// 确保该频道有消息容器
|
|
304
|
+
const container = ensureMessageContainer(channelId);
|
|
305
|
+
|
|
306
|
+
// 切换到该频道的视图
|
|
307
|
+
showChannelView(channelId);
|
|
308
|
+
|
|
309
|
+
// 如果还没有 SSE 连接,建立连接
|
|
310
|
+
if (!eventSources.has(channelId)) {
|
|
311
|
+
console.log('[selectChannel] 建立 SSE 连接');
|
|
312
|
+
connect(channelId);
|
|
208
313
|
}
|
|
209
314
|
|
|
210
|
-
//
|
|
211
|
-
|
|
315
|
+
// 检查是否有保存的 session 消息
|
|
316
|
+
const sessionKey = `${channelId}:${currentSessionId}`;
|
|
317
|
+
const savedMessages = sessionMessages.get(sessionKey);
|
|
212
318
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
319
|
+
if (savedMessages && savedMessages.length > 0) {
|
|
320
|
+
console.log('[selectChannel] 加载已保存的 session 消息:', savedMessages.length);
|
|
321
|
+
container.innerHTML = '';
|
|
322
|
+
savedMessages.forEach(msg => {
|
|
323
|
+
addMessage(msg.content, msg.type, false, container);
|
|
324
|
+
});
|
|
325
|
+
} else if (container.innerHTML.trim() === '') {
|
|
326
|
+
// 如果容器是空的,加载 session
|
|
327
|
+
try {
|
|
328
|
+
const res = await fetch(`/sessions/${channelId}`);
|
|
329
|
+
const session = await res.json();
|
|
330
|
+
if (session.messages && session.messages.length > 0) {
|
|
331
|
+
session.messages.forEach(msg => {
|
|
332
|
+
addMessage(msg.content, msg.type, false, container);
|
|
333
|
+
});
|
|
334
|
+
} else {
|
|
335
|
+
addMessage('你好!我是 Bolloon Agent。有什么我可以帮你的吗?', 'ai', false, container);
|
|
336
|
+
}
|
|
337
|
+
} catch (err) {
|
|
338
|
+
console.error('[selectChannel] 加载 session 失败:', err);
|
|
339
|
+
addMessage('你好!我是 Bolloon Agent。有什么我可以帮你的吗?', 'ai', false, container);
|
|
223
340
|
}
|
|
224
|
-
} catch (err) {
|
|
225
|
-
console.error('[selectChannel] 加载 session 失败:', err);
|
|
226
|
-
addMessage('你好!我是 Bolloon Agent。有什么我可以帮你的吗?', 'ai', false);
|
|
227
341
|
}
|
|
228
|
-
|
|
229
|
-
// 建立新 SSE 连接
|
|
230
|
-
console.log('[selectChannel] 建立 SSE 连接');
|
|
231
|
-
connect();
|
|
232
342
|
}
|
|
233
343
|
|
|
234
344
|
async function loadSession(channelId) {
|
|
345
|
+
const container = messagesContainers.get(channelId);
|
|
346
|
+
if (!container) return;
|
|
235
347
|
try {
|
|
236
348
|
const res = await fetch(`/sessions/${channelId}`);
|
|
237
349
|
const session = await res.json();
|
|
238
|
-
|
|
350
|
+
container.innerHTML = '';
|
|
239
351
|
if (session.messages && session.messages.length > 0) {
|
|
240
352
|
session.messages.forEach(msg => {
|
|
241
|
-
addMessage(msg.content, msg.type, false);
|
|
353
|
+
addMessage(msg.content, msg.type, false, container);
|
|
242
354
|
});
|
|
243
355
|
} else {
|
|
244
|
-
addMessage('你好!我是 Bolloon Agent。有什么我可以帮你的吗?', 'ai', false);
|
|
356
|
+
addMessage('你好!我是 Bolloon Agent。有什么我可以帮你的吗?', 'ai', false, container);
|
|
245
357
|
}
|
|
246
358
|
} catch (err) {
|
|
247
359
|
console.error('Failed to load session:', err);
|
|
248
|
-
|
|
249
|
-
addMessage('你好!我是 Bolloon Agent。有什么我可以帮你的吗?', 'ai', false);
|
|
360
|
+
container.innerHTML = '';
|
|
361
|
+
addMessage('你好!我是 Bolloon Agent。有什么我可以帮你的吗?', 'ai', false, container);
|
|
250
362
|
}
|
|
251
363
|
}
|
|
252
364
|
|
|
253
|
-
function addMessage(content, type, save = true) {
|
|
365
|
+
function addMessage(content, type, save = true, container) {
|
|
366
|
+
const msgContainer = container || messagesContainers.get(currentChannelId) || messagesEl;
|
|
254
367
|
// 去重:只有 save=true 时(来自 SSE)才去重,save=false 时(来自 session 加载)直接显示
|
|
255
368
|
if (save) {
|
|
256
369
|
const lastContent = type === 'user' ? lastUserCommand : lastAiContent;
|
|
@@ -459,19 +572,20 @@ function addMessage(content, type, save = true) {
|
|
|
459
572
|
}
|
|
460
573
|
|
|
461
574
|
div.appendChild(time);
|
|
462
|
-
|
|
575
|
+
msgContainer.appendChild(div);
|
|
463
576
|
|
|
464
|
-
|
|
577
|
+
msgContainer.scrollTop = msgContainer.scrollHeight;
|
|
465
578
|
}
|
|
466
579
|
|
|
467
|
-
function showTyping() {
|
|
580
|
+
function showTyping(container) {
|
|
581
|
+
const msgContainer = container || messagesContainers.get(currentChannelId) || messagesEl;
|
|
468
582
|
hideTyping();
|
|
469
583
|
const div = document.createElement('div');
|
|
470
584
|
div.className = 'message message-ai';
|
|
471
585
|
div.id = 'typing';
|
|
472
586
|
div.innerHTML = '<div class="typing"><div class="typing-spinner"></div><span class="typing-text">思考中...</span></div>';
|
|
473
|
-
|
|
474
|
-
|
|
587
|
+
msgContainer.appendChild(div);
|
|
588
|
+
msgContainer.scrollTop = msgContainer.scrollHeight;
|
|
475
589
|
}
|
|
476
590
|
|
|
477
591
|
function hideTyping() {
|
|
@@ -482,7 +596,8 @@ function hideTyping() {
|
|
|
482
596
|
|
|
483
597
|
let streamingMessageEl = null;
|
|
484
598
|
|
|
485
|
-
function showStreaming() {
|
|
599
|
+
function showStreaming(container) {
|
|
600
|
+
const msgContainer = container || messagesContainers.get(currentChannelId) || messagesEl;
|
|
486
601
|
hideStreaming();
|
|
487
602
|
streamingMessageEl = document.createElement('div');
|
|
488
603
|
streamingMessageEl.className = 'message message-ai';
|
|
@@ -490,8 +605,8 @@ function showStreaming() {
|
|
|
490
605
|
const bubble = document.createElement('div');
|
|
491
606
|
bubble.className = 'bubble bubble-ai streaming-content';
|
|
492
607
|
streamingMessageEl.appendChild(bubble);
|
|
493
|
-
|
|
494
|
-
|
|
608
|
+
msgContainer.appendChild(streamingMessageEl);
|
|
609
|
+
msgContainer.scrollTop = msgContainer.scrollHeight;
|
|
495
610
|
}
|
|
496
611
|
|
|
497
612
|
function hideStreaming() {
|
|
@@ -501,47 +616,57 @@ function hideStreaming() {
|
|
|
501
616
|
}
|
|
502
617
|
}
|
|
503
618
|
|
|
504
|
-
function updateStreamingContent(content) {
|
|
619
|
+
function updateStreamingContent(content, container) {
|
|
620
|
+
const msgContainer = container || messagesContainers.get(currentChannelId) || messagesEl;
|
|
505
621
|
if (streamingMessageEl) {
|
|
506
622
|
const bubble = streamingMessageEl.querySelector('.streaming-content');
|
|
507
623
|
if (bubble) {
|
|
508
624
|
bubble.textContent = content;
|
|
509
|
-
|
|
625
|
+
msgContainer.scrollTop = msgContainer.scrollHeight;
|
|
510
626
|
}
|
|
511
627
|
}
|
|
512
628
|
}
|
|
513
629
|
|
|
514
|
-
function handleStreamEvent(data) {
|
|
630
|
+
function handleStreamEvent(data, container) {
|
|
631
|
+
const msgContainer = container || messagesContainers.get(currentChannelId) || messagesEl;
|
|
632
|
+
// 始终确保有工作流显示区域
|
|
633
|
+
if (!workflowDisplayEl) {
|
|
634
|
+
workflowDisplayEl = createWorkflowDisplay();
|
|
635
|
+
msgContainer.appendChild(workflowDisplayEl);
|
|
636
|
+
}
|
|
637
|
+
|
|
515
638
|
if (data.streamType === 'thinking') {
|
|
516
|
-
showStreaming();
|
|
517
|
-
updateStreamingContent(data.content || '思考中...');
|
|
639
|
+
showStreaming(msgContainer);
|
|
640
|
+
updateStreamingContent(data.content || '思考中...', msgContainer);
|
|
518
641
|
} else if (data.streamType === 'token') {
|
|
519
|
-
showStreaming();
|
|
642
|
+
showStreaming(msgContainer);
|
|
520
643
|
const current = streamingMessageEl?.querySelector('.streaming-content')?.textContent || '';
|
|
521
|
-
updateStreamingContent(current + data.content);
|
|
644
|
+
updateStreamingContent(current + data.content, msgContainer);
|
|
522
645
|
}
|
|
523
646
|
}
|
|
524
647
|
|
|
525
|
-
function handleStatusEvent(data) {
|
|
648
|
+
function handleStatusEvent(data, container) {
|
|
649
|
+
const msgContainer = container || messagesContainers.get(currentChannelId) || messagesEl;
|
|
526
650
|
// 检查是否是工具调用结果
|
|
527
651
|
const content = data.content || '';
|
|
528
652
|
const isJsonResult = content.startsWith('{') && content.includes('"success"');
|
|
529
653
|
|
|
530
654
|
if (isJsonResult) {
|
|
531
655
|
// 工具结果:折叠显示
|
|
532
|
-
showToolResult(data.tool, content);
|
|
656
|
+
showToolResult(data.tool, content, msgContainer);
|
|
533
657
|
} else {
|
|
534
658
|
// 普通状态:流式显示
|
|
535
|
-
showStreaming();
|
|
659
|
+
showStreaming(msgContainer);
|
|
536
660
|
const icon = data.tool ? `🔧 ${data.tool}: ` : '';
|
|
537
|
-
updateStreamingContent(icon + data.content);
|
|
661
|
+
updateStreamingContent(icon + data.content, msgContainer);
|
|
538
662
|
}
|
|
539
663
|
}
|
|
540
664
|
|
|
541
665
|
// 显示工具调用结果(折叠)
|
|
542
666
|
let toolResultContainer = null;
|
|
543
667
|
|
|
544
|
-
function showToolResult(toolName, resultJson) {
|
|
668
|
+
function showToolResult(toolName, resultJson, container) {
|
|
669
|
+
const msgContainer = container || messagesContainers.get(currentChannelId) || messagesEl;
|
|
545
670
|
// 清理之前的流式显示
|
|
546
671
|
hideStreaming();
|
|
547
672
|
|
|
@@ -549,7 +674,7 @@ function showToolResult(toolName, resultJson) {
|
|
|
549
674
|
if (!toolResultContainer) {
|
|
550
675
|
toolResultContainer = document.createElement('div');
|
|
551
676
|
toolResultContainer.className = 'tool-results-container';
|
|
552
|
-
|
|
677
|
+
msgContainer.appendChild(toolResultContainer);
|
|
553
678
|
}
|
|
554
679
|
|
|
555
680
|
// 尝试解析并格式化 JSON
|
|
@@ -584,7 +709,7 @@ function showToolResult(toolName, resultJson) {
|
|
|
584
709
|
resultEl.appendChild(headerEl);
|
|
585
710
|
resultEl.appendChild(contentEl);
|
|
586
711
|
toolResultContainer.appendChild(resultEl);
|
|
587
|
-
|
|
712
|
+
msgContainer.scrollTop = msgContainer.scrollHeight;
|
|
588
713
|
}
|
|
589
714
|
|
|
590
715
|
// 格式化工具结果为易读格式
|
|
@@ -635,13 +760,14 @@ function createWorkflowDisplay() {
|
|
|
635
760
|
return container;
|
|
636
761
|
}
|
|
637
762
|
|
|
638
|
-
function handleTaskStatusEvent(data) {
|
|
763
|
+
function handleTaskStatusEvent(data, container) {
|
|
764
|
+
const msgContainer = container || messagesContainers.get(currentChannelId) || messagesEl;
|
|
639
765
|
console.log('[工作流] 任务状态:', data);
|
|
640
766
|
|
|
641
767
|
// 获取或创建工作流显示区域
|
|
642
768
|
if (!workflowDisplayEl) {
|
|
643
769
|
workflowDisplayEl = createWorkflowDisplay();
|
|
644
|
-
|
|
770
|
+
msgContainer.appendChild(workflowDisplayEl);
|
|
645
771
|
}
|
|
646
772
|
|
|
647
773
|
const stepsList = workflowDisplayEl.querySelector('.workflow-steps-list');
|
|
@@ -685,35 +811,58 @@ function handleTaskStatusEvent(data) {
|
|
|
685
811
|
}
|
|
686
812
|
}
|
|
687
813
|
|
|
688
|
-
function handleWorkflowStepEvent(data) {
|
|
814
|
+
function handleWorkflowStepEvent(data, container) {
|
|
815
|
+
const msgContainer = container || messagesContainers.get(currentChannelId) || messagesEl;
|
|
689
816
|
console.log('[工作流] 步骤:', data);
|
|
817
|
+
console.log('[工作流] 步骤标签:', data.step, '内容:', data.content?.substring(0, 80));
|
|
690
818
|
|
|
819
|
+
// 获取或创建工作流显示区域
|
|
691
820
|
if (!workflowDisplayEl) {
|
|
692
821
|
workflowDisplayEl = createWorkflowDisplay();
|
|
693
|
-
|
|
822
|
+
msgContainer.appendChild(workflowDisplayEl);
|
|
694
823
|
}
|
|
695
824
|
|
|
696
825
|
const streamsDiv = workflowDisplayEl.querySelector('.workflow-streams');
|
|
826
|
+
const header = workflowDisplayEl.querySelector('.workflow-header');
|
|
827
|
+
|
|
828
|
+
// 更新工作流标题
|
|
829
|
+
if (data.step && data.step !== '系统' && data.step !== '状态') {
|
|
830
|
+
header.querySelector('.workflow-title').textContent = `执行: ${data.step}`;
|
|
831
|
+
}
|
|
697
832
|
|
|
698
833
|
// 如果有步骤标签,显示步骤信息
|
|
699
|
-
if (data.step) {
|
|
834
|
+
if (data.step && data.content) {
|
|
700
835
|
const stepEl = document.createElement('div');
|
|
701
836
|
stepEl.className = 'workflow-step-stream';
|
|
837
|
+
|
|
838
|
+
// 根据内容类型选择不同样式
|
|
839
|
+
const isError = data.content.includes('❌') || data.content.includes('错误');
|
|
840
|
+
const isSuccess = data.content.includes('✅') || data.content.includes('成功');
|
|
841
|
+
const isLoop = data.content.includes('🔄') || data.content.includes('循环');
|
|
842
|
+
|
|
843
|
+
let icon = '🔧';
|
|
844
|
+
if (isError) icon = '❌';
|
|
845
|
+
else if (isSuccess) icon = '✅';
|
|
846
|
+
else if (isLoop) icon = '🔄';
|
|
847
|
+
|
|
702
848
|
stepEl.innerHTML = `
|
|
703
|
-
<div class="step-label"
|
|
704
|
-
<div class="step-content">${data.content
|
|
849
|
+
<div class="step-label">${icon} ${data.step}</div>
|
|
850
|
+
<div class="step-content">${data.content}</div>
|
|
705
851
|
`;
|
|
706
852
|
streamsDiv.appendChild(stepEl);
|
|
707
|
-
|
|
853
|
+
|
|
854
|
+
// 自动滚动
|
|
855
|
+
msgContainer.scrollTop = msgContainer.scrollHeight;
|
|
708
856
|
}
|
|
709
857
|
}
|
|
710
858
|
|
|
711
|
-
function handleWorkflowLoopEvent(data) {
|
|
859
|
+
function handleWorkflowLoopEvent(data, container) {
|
|
860
|
+
const msgContainer = container || messagesContainers.get(currentChannelId) || messagesEl;
|
|
712
861
|
console.log('[工作流] 循环:', data);
|
|
713
862
|
|
|
714
863
|
if (!workflowDisplayEl) {
|
|
715
864
|
workflowDisplayEl = createWorkflowDisplay();
|
|
716
|
-
|
|
865
|
+
msgContainer.appendChild(workflowDisplayEl);
|
|
717
866
|
}
|
|
718
867
|
|
|
719
868
|
const loopCount = workflowDisplayEl.querySelector('.workflow-loop-count');
|
|
@@ -738,16 +887,17 @@ function handleWorkflowLoopEvent(data) {
|
|
|
738
887
|
<div class="loop-content">${data.content}</div>
|
|
739
888
|
`;
|
|
740
889
|
streamsDiv.appendChild(loopEl);
|
|
741
|
-
|
|
890
|
+
msgContainer.scrollTop = msgContainer.scrollHeight;
|
|
742
891
|
}
|
|
743
892
|
}
|
|
744
893
|
|
|
745
894
|
// 用户命令可视化 - 当用户发送命令时调用
|
|
746
895
|
let userCommandDisplayEl = null;
|
|
747
896
|
|
|
748
|
-
function showUserCommand(command) {
|
|
897
|
+
function showUserCommand(command, container) {
|
|
898
|
+
const msgContainer = container || messagesContainers.get(currentChannelId) || messagesEl;
|
|
749
899
|
// 先移除之前的消息中的 user bubble(如果有重复的话)
|
|
750
|
-
const existingUserBubbles =
|
|
900
|
+
const existingUserBubbles = msgContainer.querySelectorAll('.message-user');
|
|
751
901
|
existingUserBubbles.forEach(el => el.remove());
|
|
752
902
|
|
|
753
903
|
// 移除之前的命令显示
|
|
@@ -769,100 +919,110 @@ function showUserCommand(command) {
|
|
|
769
919
|
|
|
770
920
|
// 在工作流显示之前插入
|
|
771
921
|
if (workflowDisplayEl) {
|
|
772
|
-
|
|
922
|
+
msgContainer.insertBefore(userCommandDisplayEl, workflowDisplayEl);
|
|
773
923
|
} else {
|
|
774
|
-
|
|
924
|
+
msgContainer.appendChild(userCommandDisplayEl);
|
|
775
925
|
}
|
|
776
926
|
|
|
777
|
-
|
|
927
|
+
msgContainer.scrollTop = msgContainer.scrollHeight;
|
|
778
928
|
}
|
|
779
929
|
|
|
780
|
-
function connect() {
|
|
781
|
-
|
|
782
|
-
if (
|
|
783
|
-
|
|
784
|
-
|
|
930
|
+
function connect(channelId) {
|
|
931
|
+
const targetChannelId = channelId || currentChannelId;
|
|
932
|
+
if (!targetChannelId) return;
|
|
933
|
+
|
|
934
|
+
// 清除该频道的重连定时器
|
|
935
|
+
if (reconnectTimers.has(targetChannelId)) {
|
|
936
|
+
clearTimeout(reconnectTimers.get(targetChannelId));
|
|
937
|
+
reconnectTimers.delete(targetChannelId);
|
|
785
938
|
}
|
|
786
939
|
|
|
787
|
-
//
|
|
788
|
-
if (
|
|
789
|
-
|
|
790
|
-
|
|
940
|
+
// 关闭该频道的旧连接
|
|
941
|
+
if (eventSources.has(targetChannelId)) {
|
|
942
|
+
eventSources.get(targetChannelId).close();
|
|
943
|
+
eventSources.delete(targetChannelId);
|
|
791
944
|
}
|
|
792
945
|
|
|
793
|
-
const sseUrl =
|
|
946
|
+
const sseUrl = `/events?channelId=${encodeURIComponent(targetChannelId)}`;
|
|
794
947
|
console.log('[connect] 创建 SSE 连接:', sseUrl);
|
|
795
948
|
|
|
796
|
-
eventSource = new EventSource(sseUrl);
|
|
949
|
+
const eventSource = new EventSource(sseUrl);
|
|
950
|
+
eventSources.set(targetChannelId, eventSource);
|
|
951
|
+
|
|
952
|
+
if (!reconnectAttempts.has(targetChannelId)) {
|
|
953
|
+
reconnectAttempts.set(targetChannelId, 0);
|
|
954
|
+
}
|
|
797
955
|
|
|
798
956
|
eventSource.onopen = () => {
|
|
799
|
-
console.log('[SSE] 已连接');
|
|
800
|
-
reconnectAttempts
|
|
957
|
+
console.log('[SSE] 已连接 channelId:', targetChannelId);
|
|
958
|
+
reconnectAttempts.set(targetChannelId, 0);
|
|
801
959
|
};
|
|
802
960
|
|
|
803
961
|
eventSource.onerror = () => {
|
|
804
|
-
console.error('[SSE] 连接错误');
|
|
962
|
+
console.error('[SSE] 连接错误 channelId:', targetChannelId);
|
|
805
963
|
eventSource.close();
|
|
806
|
-
|
|
807
|
-
reconnectAttempts
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
964
|
+
eventSources.delete(targetChannelId);
|
|
965
|
+
const attempts = (reconnectAttempts.get(targetChannelId) || 0) + 1;
|
|
966
|
+
reconnectAttempts.set(targetChannelId, attempts);
|
|
967
|
+
const timer = setTimeout(() => connect(targetChannelId), Math.min(5000 * attempts, 30000));
|
|
968
|
+
reconnectTimers.set(targetChannelId, timer);
|
|
811
969
|
};
|
|
812
970
|
|
|
813
971
|
eventSource.onmessage = (e) => {
|
|
814
972
|
try {
|
|
815
973
|
const data = JSON.parse(e.data);
|
|
816
|
-
|
|
974
|
+
const msgChannelId = data.channelId || targetChannelId;
|
|
975
|
+
console.log('[SSE] 收到消息:', data.type, 'channelId:', msgChannelId);
|
|
817
976
|
|
|
818
|
-
//
|
|
819
|
-
if (
|
|
820
|
-
console.log('[SSE]
|
|
977
|
+
// 路由消息到正确的频道(即使该频道不是当前视图)
|
|
978
|
+
if (msgChannelId !== targetChannelId) {
|
|
979
|
+
console.log('[SSE] 忽略非目标频道消息');
|
|
821
980
|
return;
|
|
822
981
|
}
|
|
823
982
|
|
|
983
|
+
// 使用正确的消息容器
|
|
984
|
+
const container = messagesContainers.get(msgChannelId) || messagesEl;
|
|
985
|
+
|
|
824
986
|
if (data.type === 'user') {
|
|
825
|
-
showUserCommand(data.content);
|
|
826
|
-
// 不再调用 addMessage,避免重复
|
|
987
|
+
showUserCommand(data.content, container);
|
|
827
988
|
} else if (data.type === 'ai') {
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
channelNameEl.textContent = data.newName;
|
|
850
|
-
}
|
|
989
|
+
addMessage(data.content, 'ai', true, container);
|
|
990
|
+
} else if (data.type === 'stream') {
|
|
991
|
+
handleStreamEvent(data, container);
|
|
992
|
+
} else if (data.type === 'regenerating') {
|
|
993
|
+
const messages = container.querySelectorAll('.message-ai');
|
|
994
|
+
if (messages.length > 0) {
|
|
995
|
+
const lastAiMsg = messages[messages.length - 1];
|
|
996
|
+
lastAiMsg.remove();
|
|
997
|
+
}
|
|
998
|
+
showTyping(container);
|
|
999
|
+
} else if (data.type === 'status') {
|
|
1000
|
+
handleStatusEvent(data, container);
|
|
1001
|
+
} else if (data.type === 'done') {
|
|
1002
|
+
hideTyping();
|
|
1003
|
+
} else if (data.type === 'renamed') {
|
|
1004
|
+
const channel = channels.find(c => c.id === data.channelId);
|
|
1005
|
+
if (channel) {
|
|
1006
|
+
channel.name = data.newName;
|
|
1007
|
+
renderChannels();
|
|
1008
|
+
if (currentChannelId === data.channelId && channelNameEl) {
|
|
1009
|
+
channelNameEl.textContent = data.newName;
|
|
851
1010
|
}
|
|
852
|
-
} else if (data.type === 'error') {
|
|
853
|
-
hideTyping();
|
|
854
|
-
addMessage('错误: ' + data.content, 'ai');
|
|
855
|
-
} else if (data.type === 'task_status') {
|
|
856
|
-
handleTaskStatusEvent(data);
|
|
857
|
-
} else if (data.type === 'workflow_step') {
|
|
858
|
-
handleWorkflowStepEvent(data);
|
|
859
|
-
} else if (data.type === 'workflow_loop') {
|
|
860
|
-
handleWorkflowLoopEvent(data);
|
|
861
1011
|
}
|
|
862
|
-
}
|
|
863
|
-
|
|
1012
|
+
} else if (data.type === 'error') {
|
|
1013
|
+
hideTyping();
|
|
1014
|
+
addMessage('错误: ' + data.content, 'ai', true, container);
|
|
1015
|
+
} else if (data.type === 'task_status') {
|
|
1016
|
+
handleTaskStatusEvent(data, container);
|
|
1017
|
+
} else if (data.type === 'workflow_step') {
|
|
1018
|
+
handleWorkflowStepEvent(data, container);
|
|
1019
|
+
} else if (data.type === 'workflow_loop') {
|
|
1020
|
+
handleWorkflowLoopEvent(data, container);
|
|
864
1021
|
}
|
|
865
|
-
}
|
|
1022
|
+
} catch (parseErr) {
|
|
1023
|
+
console.error('[SSE] 解析错误', parseErr);
|
|
1024
|
+
}
|
|
1025
|
+
};
|
|
866
1026
|
}
|
|
867
1027
|
|
|
868
1028
|
async function sendMessage() {
|
|
@@ -965,6 +1125,12 @@ if (newChannelBtn) {
|
|
|
965
1125
|
});
|
|
966
1126
|
}
|
|
967
1127
|
|
|
1128
|
+
if (newSessionBtn) {
|
|
1129
|
+
newSessionBtn.addEventListener('click', () => {
|
|
1130
|
+
createNewSession();
|
|
1131
|
+
});
|
|
1132
|
+
}
|
|
1133
|
+
|
|
968
1134
|
if (newChannelInput) {
|
|
969
1135
|
newChannelInput.addEventListener('keydown', (e) => {
|
|
970
1136
|
if (e.key === 'Enter') {
|