opc-agent 4.0.44 → 4.1.1

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.
Files changed (250) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.md +20 -20
  2. package/.github/ISSUE_TEMPLATE/feature_request.md +14 -14
  3. package/.github/PULL_REQUEST_TEMPLATE.md +13 -13
  4. package/CHANGELOG.md +48 -48
  5. package/CONTRIBUTING.md +36 -36
  6. package/README.zh-CN.md +497 -497
  7. package/dist/channels/wechat.js +6 -6
  8. package/dist/cli.js +2 -2
  9. package/dist/core/runtime.js +18 -0
  10. package/dist/deploy/index.js +56 -56
  11. package/dist/providers/index.js +39 -13
  12. package/dist/studio/server.js +211 -20
  13. package/dist/studio-ui/index.html +279 -24
  14. package/dist/ui/components.js +105 -105
  15. package/examples/README.md +22 -22
  16. package/examples/basic-agent.ts +90 -90
  17. package/examples/brain-integration.ts +71 -71
  18. package/examples/multi-channel.ts +74 -74
  19. package/fix-sidebar.mjs +188 -188
  20. package/install.ps1 +154 -154
  21. package/install.sh +164 -164
  22. package/package.json +1 -1
  23. package/scripts/install.ps1 +31 -31
  24. package/scripts/install.sh +40 -40
  25. package/serve-studio.js +13 -13
  26. package/serve-test.js +25 -25
  27. package/src/channels/dingtalk.ts +46 -46
  28. package/src/channels/email.ts +351 -351
  29. package/src/channels/feishu.ts +349 -349
  30. package/src/channels/googlechat.ts +42 -42
  31. package/src/channels/imessage.ts +31 -31
  32. package/src/channels/irc.ts +82 -82
  33. package/src/channels/line.ts +32 -32
  34. package/src/channels/matrix.ts +33 -33
  35. package/src/channels/mattermost.ts +57 -57
  36. package/src/channels/msteams.ts +32 -32
  37. package/src/channels/nostr.ts +32 -32
  38. package/src/channels/qq.ts +33 -33
  39. package/src/channels/signal.ts +32 -32
  40. package/src/channels/sms.ts +33 -33
  41. package/src/channels/telegram.ts +616 -616
  42. package/src/channels/twitch.ts +65 -65
  43. package/src/channels/voice-call.ts +100 -100
  44. package/src/channels/websocket.ts +399 -399
  45. package/src/channels/wechat.ts +329 -329
  46. package/src/channels/whatsapp.ts +32 -32
  47. package/src/cli/chat.ts +99 -99
  48. package/src/cli/setup.ts +314 -314
  49. package/src/cli.ts +2 -2
  50. package/src/core/agent.ts +476 -476
  51. package/src/core/api-server.ts +277 -277
  52. package/src/core/audio.ts +98 -98
  53. package/src/core/collaboration.ts +275 -275
  54. package/src/core/context-discovery.ts +85 -85
  55. package/src/core/context-refs.ts +140 -140
  56. package/src/core/gateway.ts +106 -106
  57. package/src/core/heartbeat.ts +51 -51
  58. package/src/core/hooks.ts +105 -105
  59. package/src/core/ide-bridge.ts +133 -133
  60. package/src/core/node-network.ts +86 -86
  61. package/src/core/profiles.ts +122 -122
  62. package/src/core/runtime.ts +18 -0
  63. package/src/core/scheduler.ts +187 -187
  64. package/src/core/session-manager.ts +137 -137
  65. package/src/core/subagent.ts +98 -98
  66. package/src/core/vision.ts +180 -180
  67. package/src/core/workflow-graph.ts +365 -365
  68. package/src/daemon.ts +96 -96
  69. package/src/deploy/index.ts +255 -255
  70. package/src/doctor.ts +156 -156
  71. package/src/eval/index.ts +211 -211
  72. package/src/eval/suites/basic.json +16 -16
  73. package/src/eval/suites/memory.json +12 -12
  74. package/src/eval/suites/safety.json +14 -14
  75. package/src/hub/brain-seed.ts +54 -54
  76. package/src/hub/client.ts +60 -60
  77. package/src/mcp/servers/calculator-mcp.ts +65 -65
  78. package/src/mcp/servers/crypto-mcp.ts +73 -73
  79. package/src/mcp/servers/database-mcp.ts +72 -72
  80. package/src/mcp/servers/datetime-mcp.ts +69 -69
  81. package/src/mcp/servers/filesystem.ts +66 -66
  82. package/src/mcp/servers/github-mcp.ts +58 -58
  83. package/src/mcp/servers/index.ts +63 -63
  84. package/src/mcp/servers/json-mcp.ts +102 -102
  85. package/src/mcp/servers/memory-mcp.ts +56 -56
  86. package/src/mcp/servers/regex-mcp.ts +53 -53
  87. package/src/mcp/servers/web-mcp.ts +49 -49
  88. package/src/memory/context-compressor.ts +189 -189
  89. package/src/memory/seed-loader.ts +212 -212
  90. package/src/memory/user-profiler.ts +215 -215
  91. package/src/plugins/content-filter.ts +23 -23
  92. package/src/plugins/logger.ts +18 -18
  93. package/src/plugins/rate-limiter.ts +38 -38
  94. package/src/protocols/a2a/client.ts +132 -132
  95. package/src/protocols/a2a/index.ts +8 -8
  96. package/src/protocols/a2a/server.ts +333 -333
  97. package/src/protocols/a2a/types.ts +88 -88
  98. package/src/protocols/a2a/utils.ts +50 -50
  99. package/src/protocols/agui/client.ts +83 -83
  100. package/src/protocols/agui/index.ts +4 -4
  101. package/src/protocols/agui/server.ts +218 -218
  102. package/src/protocols/agui/types.ts +153 -153
  103. package/src/protocols/index.ts +2 -2
  104. package/src/protocols/mcp/agent-tools.ts +134 -134
  105. package/src/protocols/mcp/index.ts +8 -8
  106. package/src/protocols/mcp/server.ts +262 -262
  107. package/src/protocols/mcp/types.ts +69 -69
  108. package/src/providers/index.ts +632 -608
  109. package/src/publish/index.ts +376 -376
  110. package/src/scheduler/cron-engine.ts +191 -191
  111. package/src/scheduler/index.ts +2 -2
  112. package/src/schema/oad.ts +217 -217
  113. package/src/security/approval.ts +131 -131
  114. package/src/security/approvals.ts +143 -143
  115. package/src/security/elevated.ts +105 -105
  116. package/src/security/guardrails.ts +248 -248
  117. package/src/security/index.ts +9 -9
  118. package/src/security/keys.ts +87 -87
  119. package/src/security/secrets.ts +129 -129
  120. package/src/skills/builtin/index.ts +408 -408
  121. package/src/skills/marketplace.ts +113 -113
  122. package/src/skills/types.ts +42 -42
  123. package/src/studio/server.ts +209 -22
  124. package/src/studio/templates-data.ts +178 -178
  125. package/src/studio-ui/index.html +279 -24
  126. package/src/telemetry/index.ts +324 -324
  127. package/src/tools/builtin/browser.ts +299 -299
  128. package/src/tools/builtin/datetime.ts +41 -41
  129. package/src/tools/builtin/file.ts +107 -107
  130. package/src/tools/builtin/home-assistant.ts +116 -116
  131. package/src/tools/builtin/rl-tools.ts +243 -243
  132. package/src/tools/builtin/shell.ts +43 -43
  133. package/src/tools/builtin/vision.ts +64 -64
  134. package/src/tools/builtin/web-search.ts +126 -126
  135. package/src/tools/builtin/web.ts +35 -35
  136. package/src/tools/document-processor.ts +213 -213
  137. package/src/tools/image-generator.ts +150 -150
  138. package/src/tools/integrations/calendar.ts +73 -73
  139. package/src/tools/integrations/code-exec.ts +39 -39
  140. package/src/tools/integrations/csv-analyzer.ts +92 -92
  141. package/src/tools/integrations/database.ts +44 -44
  142. package/src/tools/integrations/email-send.ts +76 -76
  143. package/src/tools/integrations/git-tool.ts +42 -42
  144. package/src/tools/integrations/github-tool.ts +76 -76
  145. package/src/tools/integrations/image-gen.ts +56 -56
  146. package/src/tools/integrations/index.ts +92 -92
  147. package/src/tools/integrations/jira.ts +83 -83
  148. package/src/tools/integrations/notion.ts +71 -71
  149. package/src/tools/integrations/npm-tool.ts +48 -48
  150. package/src/tools/integrations/pdf-reader.ts +58 -58
  151. package/src/tools/integrations/slack.ts +65 -65
  152. package/src/tools/integrations/summarizer.ts +49 -49
  153. package/src/tools/integrations/translator.ts +48 -48
  154. package/src/tools/integrations/trello.ts +60 -60
  155. package/src/tools/integrations/vector-search.ts +42 -42
  156. package/src/tools/integrations/web-scraper.ts +47 -47
  157. package/src/tools/integrations/web-search.ts +58 -58
  158. package/src/tools/integrations/webhook.ts +38 -38
  159. package/src/tools/mcp-client.ts +131 -131
  160. package/src/tools/web-scraper.ts +179 -179
  161. package/src/tools/web-search.ts +180 -180
  162. package/src/ui/components.ts +127 -127
  163. package/srv-out.txt +1 -1
  164. package/templates/ecommerce-assistant/README.md +45 -45
  165. package/templates/ecommerce-assistant/oad.yaml +47 -47
  166. package/templates/tech-support/README.md +43 -43
  167. package/templates/tech-support/oad.yaml +45 -45
  168. package/test-agent/Dockerfile +9 -9
  169. package/test-agent/README.md +50 -50
  170. package/test-agent/agent.yaml +23 -23
  171. package/test-agent/docker-compose.yml +11 -11
  172. package/test-agent/oad.yaml +31 -31
  173. package/test-agent/package-lock.json +1492 -1492
  174. package/test-agent/package.json +17 -17
  175. package/test-agent/src/index.ts +24 -24
  176. package/test-agent/src/skills/echo.ts +15 -15
  177. package/test-agent/tsconfig.json +24 -24
  178. package/test-full.js +43 -43
  179. package/test-sidebar.js +22 -22
  180. package/test-studio3.js +75 -75
  181. package/test-studio4.js +41 -41
  182. package/tests/a2a-protocol.test.ts +285 -285
  183. package/tests/agui-protocol.test.ts +246 -246
  184. package/tests/api-server.test.ts +148 -148
  185. package/tests/approvals.test.ts +89 -89
  186. package/tests/audio.test.ts +40 -40
  187. package/tests/brain-seed-extended.test.ts +490 -490
  188. package/tests/brain-seed.test.ts +239 -239
  189. package/tests/browser.test.ts +179 -179
  190. package/tests/channels/discord.test.ts +79 -79
  191. package/tests/channels/email.test.ts +148 -148
  192. package/tests/channels/feishu.test.ts +123 -123
  193. package/tests/channels/telegram.test.ts +129 -129
  194. package/tests/channels/websocket.test.ts +53 -53
  195. package/tests/channels/wechat.test.ts +170 -170
  196. package/tests/channels-extra.test.ts +45 -45
  197. package/tests/chat-cli.test.ts +160 -160
  198. package/tests/cli.test.ts +46 -46
  199. package/tests/context-compressor.test.ts +172 -172
  200. package/tests/context-refs.test.ts +121 -121
  201. package/tests/cron-engine.test.ts +101 -101
  202. package/tests/daemon.test.ts +135 -135
  203. package/tests/deepbrain-wire.test.ts +234 -234
  204. package/tests/deploy-and-dag.test.ts +196 -196
  205. package/tests/doctor.test.ts +38 -38
  206. package/tests/document-processor.test.ts +69 -69
  207. package/tests/e2e-nocode.test.ts +442 -442
  208. package/tests/elevated.test.ts +69 -69
  209. package/tests/eval.test.ts +173 -173
  210. package/tests/gateway.test.ts +63 -63
  211. package/tests/guardrails.test.ts +177 -177
  212. package/tests/home-assistant.test.ts +40 -40
  213. package/tests/hooks.test.ts +79 -79
  214. package/tests/ide-bridge.test.ts +38 -38
  215. package/tests/image-generator.test.ts +84 -84
  216. package/tests/init-role.test.ts +124 -124
  217. package/tests/integrations.test.ts +249 -249
  218. package/tests/mcp-client.test.ts +92 -92
  219. package/tests/mcp-server.test.ts +178 -178
  220. package/tests/mcp-servers.test.ts +260 -260
  221. package/tests/node-network.test.ts +74 -74
  222. package/tests/plugin-a2a-enhanced.test.ts +230 -230
  223. package/tests/profiles.test.ts +61 -61
  224. package/tests/publish.test.ts +231 -231
  225. package/tests/rl-tools.test.ts +93 -93
  226. package/tests/sandbox-manager.test.ts +46 -46
  227. package/tests/scheduler.test.ts +200 -200
  228. package/tests/secrets.test.ts +107 -107
  229. package/tests/security-enhanced.test.ts +233 -233
  230. package/tests/settings-api.test.ts +148 -148
  231. package/tests/setup.test.ts +73 -73
  232. package/tests/subagent.test.ts +193 -193
  233. package/tests/telegram-discord.test.ts +60 -60
  234. package/tests/telemetry.test.ts +186 -186
  235. package/tests/user-profiler.test.ts +169 -169
  236. package/tests/v090-features.test.ts +254 -254
  237. package/tests/vision.test.ts +61 -61
  238. package/tests/voice-call.test.ts +47 -47
  239. package/tests/voice-enhanced.test.ts +169 -169
  240. package/tests/voice-interaction.test.ts +38 -38
  241. package/tests/web-search.test.ts +155 -155
  242. package/tests/workflow-graph.test.ts +279 -279
  243. package/tutorial/customer-service-agent/README.md +612 -612
  244. package/tutorial/customer-service-agent/SOUL.md +26 -26
  245. package/tutorial/customer-service-agent/agent.yaml +63 -63
  246. package/tutorial/customer-service-agent/package.json +19 -19
  247. package/tutorial/customer-service-agent/src/index.ts +69 -69
  248. package/tutorial/customer-service-agent/src/skills/faq.ts +27 -27
  249. package/tutorial/customer-service-agent/src/skills/ticket.ts +22 -22
  250. package/tutorial/customer-service-agent/tsconfig.json +14 -14
@@ -388,6 +388,9 @@
388
388
  <div class="nav-item" data-page="global-models" onclick="navigate('global-models')">
389
389
  <span class="icon">🧠</span> Models
390
390
  </div>
391
+ <div class="nav-item" data-page="global-channels" onclick="navigate('global-channels')">
392
+ <span class="icon">📡</span> Channels
393
+ </div>
391
394
  <div class="nav-item" data-page="global-memory" onclick="navigate('global-memory')">
392
395
  <span class="icon">💾</span> Memory
393
396
  </div>
@@ -419,6 +422,14 @@
419
422
  </div>
420
423
  <button class="btn btn-primary" onclick="navigate('create')">✨ Create New Agent</button>
421
424
  </div>
425
+ <!-- Dashboard Stats -->
426
+ <div class="card-grid" style="margin-bottom:24px;" id="dashboard-stats"></div>
427
+ <!-- Quick Actions -->
428
+ <div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:24px;" id="dashboard-actions">
429
+ <button class="btn btn-secondary" onclick="navigate('create')">✨ Create Agent</button>
430
+ <button class="btn btn-secondary" onclick="navigate('global-models')">🧠 Configure Model</button>
431
+ <button class="btn btn-secondary" onclick="navigate('templates')">📋 Browse Templates</button>
432
+ </div>
422
433
  <!-- Health Status Section -->
423
434
  <div id="health-section" style="margin-bottom:24px;"></div>
424
435
  <div id="agents-list" class="card-grid"></div>
@@ -470,31 +481,46 @@
470
481
  <div class="agent-settings-content" id="agent-settings-content">
471
482
  <div class="agent-tab-panel active" id="atab-role">
472
483
  <h3>角色配置</h3>
473
- <p style="color:var(--text-muted)">Agent 角色和人设配置(即将上线)</p>
484
+ <div class="form-group"><label class="label">Agent 名称</label><input class="input" id="atab-role-name" placeholder="Agent name"></div>
485
+ <div class="form-group"><label class="label">描述</label><textarea class="input" id="atab-role-desc" rows="2" placeholder="Brief description..."></textarea></div>
486
+ <div class="form-group"><label class="label">System Prompt</label><textarea class="input" id="atab-role-prompt" rows="6" placeholder="You are a helpful assistant..."></textarea></div>
487
+ <button class="btn btn-primary" onclick="saveAgentRole()">💾 保存</button>
488
+ <span id="atab-role-status" style="margin-left:12px;font-size:18px;"></span>
474
489
  </div>
475
490
  <div class="agent-tab-panel" id="atab-models">
476
491
  <h3>模型配置</h3>
477
- <p style="color:var(--text-muted)">Agent 使用的模型配置(即将上线)</p>
492
+ <div class="form-group">
493
+ <label style="display:flex;align-items:center;gap:8px;cursor:pointer;font-size:18px;"><input type="checkbox" id="atab-model-override"> 覆盖全局模型设置</label>
494
+ </div>
495
+ <div id="atab-model-fields" style="display:none;">
496
+ <div class="form-group"><label class="label">Provider</label><select class="input" id="atab-model-provider"><option value="ollama">Ollama (Local)</option><option value="openai">OpenAI</option><option value="anthropic">Anthropic</option><option value="deepseek">DeepSeek</option></select></div>
497
+ <div class="form-group"><label class="label">Model</label><input class="input" id="atab-model-name" placeholder="e.g. gpt-4o-mini"></div>
498
+ <div class="form-group"><label class="label">Temperature</label><input class="input" id="atab-model-temp" type="number" min="0" max="2" step="0.1" value="0.7"></div>
499
+ </div>
500
+ <button class="btn btn-primary" onclick="saveAgentModel()">💾 保存</button>
501
+ <span id="atab-model-status" style="margin-left:12px;font-size:18px;"></span>
478
502
  </div>
479
503
  <div class="agent-tab-panel" id="atab-channels">
480
504
  <h3>渠道配置</h3>
481
- <p style="color:var(--text-muted)">Agent 接入的渠道配置(即将上线)</p>
505
+ <div id="atab-channels-list" style="font-size:18px;color:var(--text-muted);">加载中...</div>
506
+ <button class="btn btn-primary" style="margin-top:16px;" onclick="saveAgentChannels()">💾 保存</button>
507
+ <span id="atab-channels-status" style="margin-left:12px;font-size:18px;"></span>
482
508
  </div>
483
509
  <div class="agent-tab-panel" id="atab-memory">
484
510
  <h3>记忆管理</h3>
485
- <p style="color:var(--text-muted)">Agent 记忆和知识库(即将上线)</p>
511
+ <div id="atab-memory-list" style="font-size:18px;color:var(--text-muted);">加载中...</div>
486
512
  </div>
487
513
  <div class="agent-tab-panel" id="atab-skills">
488
514
  <h3>技能配置</h3>
489
- <p style="color:var(--text-muted)">Agent 已安装的技能(即将上线)</p>
515
+ <div id="atab-skills-list" style="font-size:18px;color:var(--text-muted);">加载中...</div>
490
516
  </div>
491
517
  <div class="agent-tab-panel" id="atab-schedules">
492
518
  <h3>定时任务</h3>
493
- <p style="color:var(--text-muted)">Agent 定时任务配置(即将上线)</p>
519
+ <div id="atab-schedules-list" style="font-size:18px;color:var(--text-muted);">加载中...</div>
494
520
  </div>
495
521
  <div class="agent-tab-panel" id="atab-usage">
496
522
  <h3>用量统计</h3>
497
- <p style="color:var(--text-muted)">Agent 使用量和成本(即将上线)</p>
523
+ <div id="atab-usage-content" style="font-size:18px;color:var(--text-muted);">加载中...</div>
498
524
  </div>
499
525
  </div>
500
526
  </div>
@@ -735,6 +761,10 @@
735
761
  <label class="label">Description</label>
736
762
  <textarea class="input" id="sched-desc" rows="2" placeholder="e.g. Send a news summary every morning at 8am"></textarea>
737
763
  </div>
764
+ <div class="form-group">
765
+ <label class="label">Agent</label>
766
+ <select class="input" id="sched-agent"></select>
767
+ </div>
738
768
  <div class="form-group">
739
769
  <label class="label">Output Channel</label>
740
770
  <select class="input" id="sched-channel">
@@ -1127,6 +1157,7 @@
1127
1157
  async function init() {
1128
1158
  await Promise.all([loadTemplates(), loadAgents(), loadSidebarAgents()]);
1129
1159
  loadSidebarGroups();
1160
+ loadDashboardStats();
1130
1161
  handleRoute();
1131
1162
  window.addEventListener('popstate', handleRoute);
1132
1163
  checkFirstRun();
@@ -1317,26 +1348,61 @@
1317
1348
 
1318
1349
  agentChatHistory.push({ role: 'user', content: msg });
1319
1350
 
1320
- // Send to API
1351
+ // Add assistant message placeholder
1352
+ const assistantDiv = document.createElement('div');
1353
+ assistantDiv.className = 'agent-chat-msg assistant';
1354
+ assistantDiv.textContent = '';
1355
+ messagesEl.appendChild(assistantDiv);
1356
+
1357
+ // Send to API and parse SSE stream
1321
1358
  try {
1322
1359
  const res = await fetch(`/api/agents/${selectedAgentId}/chat`, {
1323
1360
  method: 'POST',
1324
1361
  headers: { 'Content-Type': 'application/json' },
1325
- body: JSON.stringify({ message: msg, history: agentChatHistory })
1362
+ body: JSON.stringify({ message: msg, history: agentChatHistory.slice(0, -1) })
1326
1363
  });
1327
- const data = await res.json();
1328
- const reply = data.reply || data.message || data.content || JSON.stringify(data);
1329
- const assistantDiv = document.createElement('div');
1330
- assistantDiv.className = 'agent-chat-msg assistant';
1331
- assistantDiv.textContent = reply;
1332
- messagesEl.appendChild(assistantDiv);
1333
- agentChatHistory.push({ role: 'assistant', content: reply });
1364
+
1365
+ if (!res.ok) {
1366
+ let errMsg = `HTTP ${res.status}`;
1367
+ try { const ej = await res.json(); errMsg = ej.error || errMsg; } catch {}
1368
+ throw new Error(errMsg);
1369
+ }
1370
+
1371
+ const reader = res.body.getReader();
1372
+ const decoder = new TextDecoder();
1373
+ let fullReply = '';
1374
+ let buffer = '';
1375
+
1376
+ while (true) {
1377
+ const { done, value } = await reader.read();
1378
+ if (done) break;
1379
+ buffer += decoder.decode(value, { stream: true });
1380
+ const lines = buffer.split('\n');
1381
+ buffer = lines.pop() || '';
1382
+ for (const line of lines) {
1383
+ const trimmed = line.trim();
1384
+ if (!trimmed || !trimmed.startsWith('data: ')) continue;
1385
+ const data = trimmed.slice(6);
1386
+ if (data === '[DONE]') continue;
1387
+ try {
1388
+ const parsed = JSON.parse(data);
1389
+ const content = parsed.choices?.[0]?.delta?.content;
1390
+ if (content) {
1391
+ fullReply += content;
1392
+ assistantDiv.textContent = fullReply;
1393
+ messagesEl.scrollTop = messagesEl.scrollHeight;
1394
+ }
1395
+ } catch {}
1396
+ }
1397
+ }
1398
+
1399
+ if (!fullReply) {
1400
+ assistantDiv.textContent = '(No response received)';
1401
+ }
1402
+ agentChatHistory.push({ role: 'assistant', content: fullReply });
1334
1403
  } catch(e) {
1335
- const errDiv = document.createElement('div');
1336
- errDiv.className = 'agent-chat-msg assistant';
1337
- errDiv.style.borderColor = 'var(--red)';
1338
- errDiv.textContent = `⚠️ 发送失败: ${e.message}`;
1339
- messagesEl.appendChild(errDiv);
1404
+ assistantDiv.style.borderColor = 'var(--red)';
1405
+ assistantDiv.textContent = `⚠️ 发送失败: ${e.message}`;
1340
1406
  }
1341
1407
  messagesEl.scrollTop = messagesEl.scrollHeight;
1342
1408
  }
@@ -1355,11 +1421,12 @@
1355
1421
  const navItem = document.querySelector(`.nav-item[data-page="${page}"]`);
1356
1422
  if (navItem) navItem.classList.add('active');
1357
1423
 
1358
- if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); loadSidebarAgents(); }
1424
+ if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); loadSidebarAgents(); loadDashboardStats(); }
1359
1425
  if (page === 'create') { renderWizard(); renderWizardTemplates(); }
1360
1426
  if (page === 'settings') { showSettings(currentSettingsTab || 'models'); }
1361
1427
  if (page === 'global-runtime') { currentSettingsTab='status'; showSettings('status'); showPage('settings'); return; }
1362
1428
  if (page === 'global-models') { currentSettingsTab='models'; showSettings('models'); showPage('settings'); return; }
1429
+ if (page === 'global-channels') { currentSettingsTab='channels'; showSettings('channels'); showPage('settings'); return; }
1363
1430
  if (page === 'global-memory') { currentSettingsTab='memory'; showSettings('memory'); showPage('settings'); return; }
1364
1431
  if (page === 'global-templates') { navigate('templates'); return; }
1365
1432
  if (page === 'create-group') { loadGroupAgentSelect(); }
@@ -2429,8 +2496,31 @@
2429
2496
  let frCreatedAgentId = null;
2430
2497
 
2431
2498
  async function checkFirstRun() {
2432
- // Skip first-run wizard — agents are configured via init
2433
- return;
2499
+ try {
2500
+ const res = await fetch(`${API}/api/first-run/status`);
2501
+ const data = await res.json();
2502
+ if (data.completed) return; // already configured
2503
+ // Check if models are configured — if so, skip
2504
+ const modelRes = await fetch(`${API}/api/settings/models`);
2505
+ const modelData = await modelRes.json();
2506
+ const hasCloudKey = modelData.providers && Object.values(modelData.providers).some(p => p && p.apiKey);
2507
+ if (hasCloudKey) return; // user already has API keys
2508
+ // Check if Ollama is running with models
2509
+ const ollamaRes = await fetch(`${API}/api/settings/models/local`);
2510
+ const ollamaData = await ollamaRes.json();
2511
+ if (ollamaData.running && ollamaData.models?.length > 0) {
2512
+ // Ollama ready — auto-set local mode and skip wizard
2513
+ await fetch(`${API}/api/settings/models`, {
2514
+ method: 'PUT', headers: {'Content-Type':'application/json'},
2515
+ body: JSON.stringify({ mode: 'local', provider: 'ollama', chatModel: ollamaData.models[0]?.name || 'qwen2.5:7b' })
2516
+ });
2517
+ return;
2518
+ }
2519
+ // Nothing configured — show wizard
2520
+ showFirstRunWizard({ ollamaDetected: ollamaData.running, ollamaModels: ollamaData.models });
2521
+ } catch {
2522
+ // API not ready, skip
2523
+ }
2434
2524
  }
2435
2525
 
2436
2526
  function showFirstRunWizard(data) {
@@ -2561,6 +2651,167 @@
2561
2651
  });
2562
2652
  }
2563
2653
 
2654
+ // === Dashboard Stats ===
2655
+ async function loadDashboardStats() {
2656
+ const el = document.getElementById('dashboard-stats');
2657
+ if (!el) return;
2658
+ try {
2659
+ const [agentsRes, modelsRes] = await Promise.all([
2660
+ fetch('/api/agents').then(r=>r.json()).catch(()=>({})),
2661
+ fetch('/api/settings/models').then(r=>r.json()).catch(()=>({})),
2662
+ ]);
2663
+ const agentList = agentsRes.agents || [];
2664
+ const currentModel = modelsRes.chatModel || 'Not configured';
2665
+ el.innerHTML = `
2666
+ <div class="card stat-card"><div class="stat-value">${agentList.length}</div><div class="stat-label">Agents</div></div>
2667
+ <div class="card stat-card"><div class="stat-value" style="font-size:18px;">${currentModel}</div><div class="stat-label">Current Model</div></div>
2668
+ <div class="card stat-card"><div class="stat-value">${agentList.filter(a=>(a.status||'').toLowerCase()==='online').length}</div><div class="stat-label">Online</div></div>
2669
+ `;
2670
+ } catch { el.innerHTML = ''; }
2671
+ }
2672
+
2673
+ // === Agent Detail Settings Tab Logic ===
2674
+ function switchAgentTabOrig(tab) {
2675
+ document.querySelectorAll('.agent-tab').forEach(t => t.classList.remove('active'));
2676
+ document.querySelector(`.agent-tab[data-atab="${tab}"]`)?.classList.add('active');
2677
+ document.querySelectorAll('.agent-tab-panel').forEach(p => p.classList.remove('active'));
2678
+ document.getElementById(`atab-${tab}`)?.classList.add('active');
2679
+ loadAgentTabData(tab);
2680
+ }
2681
+ // Override original
2682
+ switchAgentTab = switchAgentTabOrig;
2683
+
2684
+ async function loadAgentTabData(tab) {
2685
+ if (!selectedAgentId) return;
2686
+ const id = selectedAgentId;
2687
+ try {
2688
+ if (tab === 'role') {
2689
+ const res = await fetch(`/api/agents/${id}`);
2690
+ const a = await res.json();
2691
+ document.getElementById('atab-role-name').value = a.name || '';
2692
+ document.getElementById('atab-role-desc').value = a.description || '';
2693
+ document.getElementById('atab-role-prompt').value = a.systemPrompt || a.prompt || '';
2694
+ } else if (tab === 'models') {
2695
+ const res = await fetch(`/api/agents/${id}`);
2696
+ const a = await res.json();
2697
+ const ov = document.getElementById('atab-model-override');
2698
+ const fields = document.getElementById('atab-model-fields');
2699
+ ov.checked = !!a.modelOverride;
2700
+ fields.style.display = ov.checked ? '' : 'none';
2701
+ ov.onchange = () => { fields.style.display = ov.checked ? '' : 'none'; };
2702
+ if (a.modelOverride) {
2703
+ document.getElementById('atab-model-provider').value = a.provider || 'ollama';
2704
+ document.getElementById('atab-model-name').value = a.model || '';
2705
+ document.getElementById('atab-model-temp').value = a.temperature ?? 0.7;
2706
+ }
2707
+ } else if (tab === 'channels') {
2708
+ const chList = ['web','telegram','discord','slack','feishu','email','whatsapp'];
2709
+ const res = await fetch(`/api/agents/${id}`);
2710
+ const a = await res.json();
2711
+ const enabled = a.channels || ['web'];
2712
+ document.getElementById('atab-channels-list').innerHTML = chList.map(ch =>
2713
+ `<label style="display:flex;align-items:center;gap:10px;padding:8px 0;font-size:18px;cursor:pointer;">
2714
+ <input type="checkbox" class="atab-ch-cb" value="${ch}" ${enabled.includes(ch)?'checked':''}>
2715
+ ${ch.charAt(0).toUpperCase()+ch.slice(1)}
2716
+ </label>`
2717
+ ).join('');
2718
+ } else if (tab === 'memory') {
2719
+ const el = document.getElementById('atab-memory-list');
2720
+ el.innerHTML = '<span style="color:var(--text-muted);">⏳ Loading...</span>';
2721
+ try {
2722
+ const res = await fetch(`/api/agents/${id}/memory`);
2723
+ const data = await res.json();
2724
+ const entries = data.entries || [];
2725
+ if (!entries.length) { el.innerHTML = '<p style="color:var(--text-muted);">No memories yet.</p>'; return; }
2726
+ el.innerHTML = '<div class="timeline">' + entries.map(e =>
2727
+ `<div class="timeline-item"><div class="timeline-date">${new Date(e.timestamp).toLocaleString()}</div><div class="timeline-content">${esc(e.summary||e.content||'')}</div></div>`
2728
+ ).join('') + '</div>';
2729
+ } catch { el.innerHTML = '<p style="color:var(--text-muted);">Failed to load memories.</p>'; }
2730
+ } else if (tab === 'skills') {
2731
+ const el = document.getElementById('atab-skills-list');
2732
+ el.innerHTML = '<span style="color:var(--text-muted);">⏳ Loading...</span>';
2733
+ try {
2734
+ const res = await fetch(`/api/agents/${id}`);
2735
+ const a = await res.json();
2736
+ const skills = a.skills || [];
2737
+ if (!skills.length) { el.innerHTML = '<p style="color:var(--text-muted);">No skills installed.</p>'; return; }
2738
+ el.innerHTML = skills.map(s => `<div class="card" style="margin-bottom:8px;padding:12px;"><span style="font-size:18px;font-weight:600;">${s.name||s}</span></div>`).join('');
2739
+ } catch { el.innerHTML = '<p style="color:var(--text-muted);">Failed to load skills.</p>'; }
2740
+ } else if (tab === 'schedules') {
2741
+ const el = document.getElementById('atab-schedules-list');
2742
+ el.innerHTML = '<span style="color:var(--text-muted);">⏳ Loading...</span>';
2743
+ try {
2744
+ const res = await fetch('/api/schedules');
2745
+ const all = await res.json();
2746
+ const tasks = (Array.isArray(all)?all:[]).filter(t => t.agentId === id);
2747
+ if (!tasks.length) { el.innerHTML = '<p style="color:var(--text-muted);">No schedules for this agent.</p>'; return; }
2748
+ el.innerHTML = tasks.map(t => `<div class="card" style="margin-bottom:8px;padding:12px;display:flex;align-items:center;gap:12px;">
2749
+ <span>⏰</span><div style="flex:1;"><div style="font-weight:600;font-size:18px;">${esc(t.name)}</div><div style="font-size:18px;color:var(--text-dim);">${esc(t.schedule||t.frequency||'')}</div></div>
2750
+ <span style="font-size:18px;color:${t.enabled?'var(--green)':'var(--text-dim)'};">${t.enabled?'Active':'Paused'}</span>
2751
+ </div>`).join('');
2752
+ } catch { el.innerHTML = '<p style="color:var(--text-muted);">Failed to load schedules.</p>'; }
2753
+ } else if (tab === 'usage') {
2754
+ const el = document.getElementById('atab-usage-content');
2755
+ el.innerHTML = '<span style="color:var(--text-muted);">⏳ Loading...</span>';
2756
+ try {
2757
+ const res = await fetch('/api/settings/usage');
2758
+ const data = await res.json();
2759
+ const tokens = data.totalTokens || 0;
2760
+ const cost = data.totalCost || 0;
2761
+ el.innerHTML = `<div class="card-grid"><div class="card stat-card"><div class="stat-value">${tokens>1000?(tokens/1000).toFixed(1)+'K':tokens}</div><div class="stat-label">Total Tokens</div></div><div class="card stat-card"><div class="stat-value">$${cost.toFixed(4)}</div><div class="stat-label">Est. Cost</div></div></div>`;
2762
+ } catch { el.innerHTML = '<p style="color:var(--text-muted);">Failed to load usage data.</p>'; }
2763
+ }
2764
+ } catch(e) { console.error('loadAgentTabData error:', e); }
2765
+ }
2766
+
2767
+ async function saveAgentRole() {
2768
+ if (!selectedAgentId) return;
2769
+ const st = document.getElementById('atab-role-status');
2770
+ try {
2771
+ await fetch(`/api/agents/${selectedAgentId}`, { method:'PUT', headers:{'Content-Type':'application/json'}, body:JSON.stringify({
2772
+ name: document.getElementById('atab-role-name').value.trim(),
2773
+ description: document.getElementById('atab-role-desc').value.trim(),
2774
+ systemPrompt: document.getElementById('atab-role-prompt').value.trim(),
2775
+ })});
2776
+ st.textContent = '✅ Saved'; st.style.color = 'var(--green)';
2777
+ loadSidebarAgents();
2778
+ } catch { st.textContent = '❌ Failed'; st.style.color = 'var(--red)'; }
2779
+ }
2780
+
2781
+ async function saveAgentModel() {
2782
+ if (!selectedAgentId) return;
2783
+ const st = document.getElementById('atab-model-status');
2784
+ const ov = document.getElementById('atab-model-override').checked;
2785
+ const body = { modelOverride: ov };
2786
+ if (ov) { body.provider = document.getElementById('atab-model-provider').value; body.model = document.getElementById('atab-model-name').value; body.temperature = parseFloat(document.getElementById('atab-model-temp').value); }
2787
+ try {
2788
+ await fetch(`/api/agents/${selectedAgentId}`, { method:'PUT', headers:{'Content-Type':'application/json'}, body:JSON.stringify(body) });
2789
+ st.textContent = '✅ Saved'; st.style.color = 'var(--green)';
2790
+ } catch { st.textContent = '❌ Failed'; st.style.color = 'var(--red)'; }
2791
+ }
2792
+
2793
+ async function saveAgentChannels() {
2794
+ if (!selectedAgentId) return;
2795
+ const st = document.getElementById('atab-channels-status');
2796
+ const channels = [...document.querySelectorAll('.atab-ch-cb:checked')].map(cb => cb.value);
2797
+ try {
2798
+ await fetch(`/api/agents/${selectedAgentId}`, { method:'PUT', headers:{'Content-Type':'application/json'}, body:JSON.stringify({ channels }) });
2799
+ st.textContent = '✅ Saved'; st.style.color = 'var(--green)';
2800
+ } catch { st.textContent = '❌ Failed'; st.style.color = 'var(--red)'; }
2801
+ }
2802
+
2803
+ // === Populate schedule form agent select ===
2804
+ async function populateSchedAgentSelect() {
2805
+ const sel = document.getElementById('sched-agent');
2806
+ if (!sel) return;
2807
+ try {
2808
+ const res = await fetch('/api/agents');
2809
+ const data = await res.json();
2810
+ const list = data.agents || [];
2811
+ sel.innerHTML = list.map(a => `<option value="${a.id}">${a.name||a.id}</option>`).join('');
2812
+ } catch { sel.innerHTML = '<option value="">No agents</option>'; }
2813
+ }
2814
+
2564
2815
  // === Start ===
2565
2816
  init();
2566
2817
 
@@ -2627,6 +2878,9 @@
2627
2878
  document.getElementById('sched-desc').value = task ? task.description : '';
2628
2879
  document.getElementById('sched-channel').value = task ? task.outputChannel : 'web';
2629
2880
  onSchedFreqChange();
2881
+ populateSchedAgentSelect().then(() => {
2882
+ if (task?.agentId) document.getElementById('sched-agent').value = task.agentId;
2883
+ });
2630
2884
  }
2631
2885
 
2632
2886
  function hideScheduleForm() {
@@ -2647,6 +2901,7 @@
2647
2901
  time: document.getElementById('sched-time').value,
2648
2902
  schedule: document.getElementById('sched-frequency').value === 'custom' ? document.getElementById('sched-cron').value.trim() : '',
2649
2903
  description: document.getElementById('sched-desc').value.trim(),
2904
+ agentId: document.getElementById('sched-agent').value,
2650
2905
  outputChannel: document.getElementById('sched-channel').value,
2651
2906
  enabled: true,
2652
2907
  };
@@ -13,111 +13,111 @@ function generateChatWidget(config) {
13
13
  const msgUser = isDark ? '#0f3460' : '#e3f2fd';
14
14
  const msgBot = isDark ? '#1a1a2e' : '#f5f5f5';
15
15
  const accent = '#00d2ff';
16
- return `<!DOCTYPE html>
17
- <html lang="en">
18
- <head>
19
- <meta charset="UTF-8">
20
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
21
- <title>${title}</title>
22
- <style>
23
- * { margin: 0; padding: 0; box-sizing: border-box; }
24
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: ${bg}; color: ${fg}; height: 100vh; display: flex; flex-direction: column; }
25
- .chat-header { padding: 16px 20px; background: ${isDark ? '#16213e' : '#fafafa'}; border-bottom: 1px solid ${isDark ? '#2a2a4a' : '#e0e0e0'}; font-weight: 600; font-size: 16px; display: flex; align-items: center; gap: 8px; }
26
- .chat-header::before { content: '💬'; }
27
- .chat-messages { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px; }
28
- .msg { max-width: 80%; padding: 10px 14px; border-radius: 12px; line-height: 1.5; font-size: 14px; white-space: pre-wrap; word-wrap: break-word; }
29
- .msg.user { align-self: flex-end; background: ${msgUser}; border-bottom-right-radius: 4px; }
30
- .msg.assistant { align-self: flex-start; background: ${msgBot}; border: 1px solid ${isDark ? '#2a2a4a' : '#e0e0e0'}; border-bottom-left-radius: 4px; }
31
- .msg.streaming::after { content: '▊'; animation: blink 0.7s infinite; }
32
- @keyframes blink { 50% { opacity: 0; } }
33
- .chat-input { display: flex; gap: 8px; padding: 12px 16px; border-top: 1px solid ${isDark ? '#2a2a4a' : '#e0e0e0'}; background: ${isDark ? '#16213e' : '#fafafa'}; }
34
- .chat-input textarea { flex: 1; resize: none; border: 1px solid ${isDark ? '#2a2a4a' : '#ccc'}; border-radius: 8px; padding: 10px; font-size: 14px; background: ${inputBg}; color: ${fg}; outline: none; font-family: inherit; min-height: 42px; max-height: 120px; }
35
- .chat-input textarea:focus { border-color: ${accent}; }
36
- .chat-input button { background: ${accent}; color: #000; border: none; border-radius: 8px; padding: 0 20px; cursor: pointer; font-weight: 600; font-size: 14px; }
37
- .chat-input button:hover { opacity: 0.85; }
38
- .chat-input button:disabled { opacity: 0.4; cursor: not-allowed; }
39
- </style>
40
- </head>
41
- <body>
42
- <div class="chat-header">${title}</div>
43
- <div class="chat-messages" id="messages"></div>
44
- <div class="chat-input">
45
- <textarea id="input" rows="1" placeholder="Type a message..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMessage()}"></textarea>
46
- <button id="sendBtn" onclick="sendMessage()">Send</button>
47
- </div>
48
- <script>
49
- const ENDPOINT = ${JSON.stringify(endpoint)};
50
- const messages = [];
51
- const $msgs = document.getElementById('messages');
52
- const $input = document.getElementById('input');
53
- const $btn = document.getElementById('sendBtn');
54
-
55
- function addMsg(role, text) {
56
- const div = document.createElement('div');
57
- div.className = 'msg ' + role;
58
- div.textContent = text;
59
- $msgs.appendChild(div);
60
- $msgs.scrollTop = $msgs.scrollHeight;
61
- return div;
62
- }
63
-
64
- async function sendMessage() {
65
- const text = $input.value.trim();
66
- if (!text) return;
67
- $input.value = '';
68
- $btn.disabled = true;
69
- messages.push({ role: 'user', content: text });
70
- addMsg('user', text);
71
-
72
- const div = addMsg('assistant', '');
73
- div.classList.add('streaming');
74
- let full = '';
75
-
76
- try {
77
- const res = await fetch(ENDPOINT, {
78
- method: 'POST',
79
- headers: { 'Content-Type': 'application/json' },
80
- body: JSON.stringify({ messages }),
81
- });
82
-
83
- if (res.headers.get('content-type')?.includes('text/event-stream')) {
84
- const reader = res.body.getReader();
85
- const decoder = new TextDecoder();
86
- let buf = '';
87
- while (true) {
88
- const { done, value } = await reader.read();
89
- if (done) break;
90
- buf += decoder.decode(value, { stream: true });
91
- const lines = buf.split('\\n');
92
- buf = lines.pop() || '';
93
- for (const line of lines) {
94
- if (line.startsWith('data: ')) {
95
- const data = line.slice(6);
96
- if (data === '[DONE]') break;
97
- try { const j = JSON.parse(data); full += j.content || j.delta || ''; div.textContent = full; } catch {}
98
- }
99
- }
100
- $msgs.scrollTop = $msgs.scrollHeight;
101
- }
102
- } else {
103
- const data = await res.json();
104
- full = data.content || data.message || JSON.stringify(data);
105
- div.textContent = full;
106
- }
107
- } catch (e) {
108
- full = 'Error: ' + e.message;
109
- div.textContent = full;
110
- }
111
-
112
- div.classList.remove('streaming');
113
- messages.push({ role: 'assistant', content: full });
114
- $btn.disabled = false;
115
- $input.focus();
116
- }
117
-
118
- $input.focus();
119
- </script>
120
- </body>
16
+ return `<!DOCTYPE html>
17
+ <html lang="en">
18
+ <head>
19
+ <meta charset="UTF-8">
20
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
21
+ <title>${title}</title>
22
+ <style>
23
+ * { margin: 0; padding: 0; box-sizing: border-box; }
24
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: ${bg}; color: ${fg}; height: 100vh; display: flex; flex-direction: column; }
25
+ .chat-header { padding: 16px 20px; background: ${isDark ? '#16213e' : '#fafafa'}; border-bottom: 1px solid ${isDark ? '#2a2a4a' : '#e0e0e0'}; font-weight: 600; font-size: 16px; display: flex; align-items: center; gap: 8px; }
26
+ .chat-header::before { content: '💬'; }
27
+ .chat-messages { flex: 1; overflow-y: auto; padding: 16px; display: flex; flex-direction: column; gap: 12px; }
28
+ .msg { max-width: 80%; padding: 10px 14px; border-radius: 12px; line-height: 1.5; font-size: 14px; white-space: pre-wrap; word-wrap: break-word; }
29
+ .msg.user { align-self: flex-end; background: ${msgUser}; border-bottom-right-radius: 4px; }
30
+ .msg.assistant { align-self: flex-start; background: ${msgBot}; border: 1px solid ${isDark ? '#2a2a4a' : '#e0e0e0'}; border-bottom-left-radius: 4px; }
31
+ .msg.streaming::after { content: '▊'; animation: blink 0.7s infinite; }
32
+ @keyframes blink { 50% { opacity: 0; } }
33
+ .chat-input { display: flex; gap: 8px; padding: 12px 16px; border-top: 1px solid ${isDark ? '#2a2a4a' : '#e0e0e0'}; background: ${isDark ? '#16213e' : '#fafafa'}; }
34
+ .chat-input textarea { flex: 1; resize: none; border: 1px solid ${isDark ? '#2a2a4a' : '#ccc'}; border-radius: 8px; padding: 10px; font-size: 14px; background: ${inputBg}; color: ${fg}; outline: none; font-family: inherit; min-height: 42px; max-height: 120px; }
35
+ .chat-input textarea:focus { border-color: ${accent}; }
36
+ .chat-input button { background: ${accent}; color: #000; border: none; border-radius: 8px; padding: 0 20px; cursor: pointer; font-weight: 600; font-size: 14px; }
37
+ .chat-input button:hover { opacity: 0.85; }
38
+ .chat-input button:disabled { opacity: 0.4; cursor: not-allowed; }
39
+ </style>
40
+ </head>
41
+ <body>
42
+ <div class="chat-header">${title}</div>
43
+ <div class="chat-messages" id="messages"></div>
44
+ <div class="chat-input">
45
+ <textarea id="input" rows="1" placeholder="Type a message..." onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMessage()}"></textarea>
46
+ <button id="sendBtn" onclick="sendMessage()">Send</button>
47
+ </div>
48
+ <script>
49
+ const ENDPOINT = ${JSON.stringify(endpoint)};
50
+ const messages = [];
51
+ const $msgs = document.getElementById('messages');
52
+ const $input = document.getElementById('input');
53
+ const $btn = document.getElementById('sendBtn');
54
+
55
+ function addMsg(role, text) {
56
+ const div = document.createElement('div');
57
+ div.className = 'msg ' + role;
58
+ div.textContent = text;
59
+ $msgs.appendChild(div);
60
+ $msgs.scrollTop = $msgs.scrollHeight;
61
+ return div;
62
+ }
63
+
64
+ async function sendMessage() {
65
+ const text = $input.value.trim();
66
+ if (!text) return;
67
+ $input.value = '';
68
+ $btn.disabled = true;
69
+ messages.push({ role: 'user', content: text });
70
+ addMsg('user', text);
71
+
72
+ const div = addMsg('assistant', '');
73
+ div.classList.add('streaming');
74
+ let full = '';
75
+
76
+ try {
77
+ const res = await fetch(ENDPOINT, {
78
+ method: 'POST',
79
+ headers: { 'Content-Type': 'application/json' },
80
+ body: JSON.stringify({ messages }),
81
+ });
82
+
83
+ if (res.headers.get('content-type')?.includes('text/event-stream')) {
84
+ const reader = res.body.getReader();
85
+ const decoder = new TextDecoder();
86
+ let buf = '';
87
+ while (true) {
88
+ const { done, value } = await reader.read();
89
+ if (done) break;
90
+ buf += decoder.decode(value, { stream: true });
91
+ const lines = buf.split('\\n');
92
+ buf = lines.pop() || '';
93
+ for (const line of lines) {
94
+ if (line.startsWith('data: ')) {
95
+ const data = line.slice(6);
96
+ if (data === '[DONE]') break;
97
+ try { const j = JSON.parse(data); full += j.content || j.delta || ''; div.textContent = full; } catch {}
98
+ }
99
+ }
100
+ $msgs.scrollTop = $msgs.scrollHeight;
101
+ }
102
+ } else {
103
+ const data = await res.json();
104
+ full = data.content || data.message || JSON.stringify(data);
105
+ div.textContent = full;
106
+ }
107
+ } catch (e) {
108
+ full = 'Error: ' + e.message;
109
+ div.textContent = full;
110
+ }
111
+
112
+ div.classList.remove('streaming');
113
+ messages.push({ role: 'assistant', content: full });
114
+ $btn.disabled = false;
115
+ $input.focus();
116
+ }
117
+
118
+ $input.focus();
119
+ </script>
120
+ </body>
121
121
  </html>`;
122
122
  }
123
123
  //# sourceMappingURL=components.js.map