opc-agent 4.1.2 → 4.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.
Files changed (738) hide show
  1. package/dist/analytics/index.d.ts.map +1 -0
  2. package/dist/analytics/index.js.map +1 -0
  3. package/dist/channels/dingtalk.d.ts.map +1 -0
  4. package/dist/channels/dingtalk.js.map +1 -0
  5. package/dist/channels/discord.d.ts.map +1 -0
  6. package/dist/channels/discord.js.map +1 -0
  7. package/dist/channels/email.d.ts.map +1 -0
  8. package/dist/channels/email.js.map +1 -0
  9. package/dist/channels/feishu.d.ts.map +1 -0
  10. package/dist/channels/feishu.js.map +1 -0
  11. package/dist/channels/googlechat.d.ts.map +1 -0
  12. package/dist/channels/googlechat.js.map +1 -0
  13. package/dist/channels/imessage.d.ts.map +1 -0
  14. package/dist/channels/imessage.js.map +1 -0
  15. package/dist/channels/index.d.ts.map +1 -0
  16. package/dist/channels/index.js.map +1 -0
  17. package/dist/channels/irc.d.ts.map +1 -0
  18. package/dist/channels/irc.js.map +1 -0
  19. package/dist/channels/line.d.ts.map +1 -0
  20. package/dist/channels/line.js.map +1 -0
  21. package/dist/channels/matrix.d.ts.map +1 -0
  22. package/dist/channels/matrix.js.map +1 -0
  23. package/dist/channels/mattermost.d.ts.map +1 -0
  24. package/dist/channels/mattermost.js.map +1 -0
  25. package/dist/channels/msteams.d.ts.map +1 -0
  26. package/dist/channels/msteams.js.map +1 -0
  27. package/dist/channels/nostr.d.ts.map +1 -0
  28. package/dist/channels/nostr.js.map +1 -0
  29. package/dist/channels/qq.d.ts.map +1 -0
  30. package/dist/channels/qq.js.map +1 -0
  31. package/dist/channels/signal.d.ts.map +1 -0
  32. package/dist/channels/signal.js.map +1 -0
  33. package/dist/channels/slack.d.ts.map +1 -0
  34. package/dist/channels/slack.js.map +1 -0
  35. package/dist/channels/sms.d.ts.map +1 -0
  36. package/dist/channels/sms.js.map +1 -0
  37. package/dist/channels/telegram.d.ts.map +1 -0
  38. package/dist/channels/telegram.js.map +1 -0
  39. package/dist/channels/twitch.d.ts.map +1 -0
  40. package/dist/channels/twitch.js.map +1 -0
  41. package/dist/channels/voice-call.d.ts.map +1 -0
  42. package/dist/channels/voice-call.js.map +1 -0
  43. package/dist/channels/voice.d.ts.map +1 -0
  44. package/dist/channels/voice.js.map +1 -0
  45. package/dist/channels/web.d.ts.map +1 -0
  46. package/dist/channels/web.js.map +1 -0
  47. package/dist/channels/webhook.d.ts.map +1 -0
  48. package/dist/channels/webhook.js.map +1 -0
  49. package/dist/channels/websocket.d.ts.map +1 -0
  50. package/dist/channels/websocket.js.map +1 -0
  51. package/dist/channels/wechat.d.ts.map +1 -0
  52. package/dist/channels/wechat.js.map +1 -0
  53. package/dist/channels/whatsapp.d.ts.map +1 -0
  54. package/dist/channels/whatsapp.js.map +1 -0
  55. package/dist/cli/chat.d.ts.map +1 -0
  56. package/dist/cli/chat.js.map +1 -0
  57. package/dist/cli/setup.d.ts.map +1 -0
  58. package/dist/cli/setup.js.map +1 -0
  59. package/dist/cli.d.ts.map +1 -0
  60. package/dist/cli.js +72 -11
  61. package/dist/cli.js.map +1 -0
  62. package/dist/core/a2a.d.ts.map +1 -0
  63. package/dist/core/a2a.js.map +1 -0
  64. package/dist/core/agent.d.ts.map +1 -0
  65. package/dist/core/agent.js.map +1 -0
  66. package/dist/core/analytics-engine.d.ts.map +1 -0
  67. package/dist/core/analytics-engine.js.map +1 -0
  68. package/dist/core/api-server.d.ts.map +1 -0
  69. package/dist/core/api-server.js.map +1 -0
  70. package/dist/core/audio.d.ts.map +1 -0
  71. package/dist/core/audio.js.map +1 -0
  72. package/dist/core/auth.d.ts.map +1 -0
  73. package/dist/core/auth.js.map +1 -0
  74. package/dist/core/cache.d.ts.map +1 -0
  75. package/dist/core/cache.js.map +1 -0
  76. package/dist/core/collaboration.d.ts.map +1 -0
  77. package/dist/core/collaboration.js.map +1 -0
  78. package/dist/core/compose.d.ts.map +1 -0
  79. package/dist/core/compose.js.map +1 -0
  80. package/dist/core/config.d.ts.map +1 -0
  81. package/dist/core/config.js.map +1 -0
  82. package/dist/core/context-discovery.d.ts.map +1 -0
  83. package/dist/core/context-discovery.js.map +1 -0
  84. package/dist/core/context-refs.d.ts.map +1 -0
  85. package/dist/core/context-refs.js.map +1 -0
  86. package/dist/core/errors.d.ts.map +1 -0
  87. package/dist/core/errors.js.map +1 -0
  88. package/dist/core/gateway.d.ts.map +1 -0
  89. package/dist/core/gateway.js.map +1 -0
  90. package/dist/core/heartbeat.d.ts.map +1 -0
  91. package/dist/core/heartbeat.js.map +1 -0
  92. package/dist/core/hitl.d.ts.map +1 -0
  93. package/dist/core/hitl.js.map +1 -0
  94. package/dist/core/hooks.d.ts.map +1 -0
  95. package/dist/core/hooks.js.map +1 -0
  96. package/dist/core/ide-bridge.d.ts.map +1 -0
  97. package/dist/core/ide-bridge.js.map +1 -0
  98. package/dist/core/knowledge.d.ts.map +1 -0
  99. package/dist/core/knowledge.js.map +1 -0
  100. package/dist/core/logger.d.ts.map +1 -0
  101. package/dist/core/logger.js.map +1 -0
  102. package/dist/core/node-network.d.ts.map +1 -0
  103. package/dist/core/node-network.js.map +1 -0
  104. package/dist/core/orchestrator.d.ts.map +1 -0
  105. package/dist/core/orchestrator.js.map +1 -0
  106. package/dist/core/performance.d.ts.map +1 -0
  107. package/dist/core/performance.js.map +1 -0
  108. package/dist/core/profiles.d.ts.map +1 -0
  109. package/dist/core/profiles.js.map +1 -0
  110. package/dist/core/rate-limiter.d.ts.map +1 -0
  111. package/dist/core/rate-limiter.js.map +1 -0
  112. package/dist/core/room.d.ts.map +1 -0
  113. package/dist/core/room.js.map +1 -0
  114. package/dist/core/runtime.d.ts.map +1 -0
  115. package/dist/core/runtime.js.map +1 -0
  116. package/dist/core/sandbox.d.ts.map +1 -0
  117. package/dist/core/sandbox.js.map +1 -0
  118. package/dist/core/scheduler.d.ts.map +1 -0
  119. package/dist/core/scheduler.js.map +1 -0
  120. package/dist/core/security.d.ts.map +1 -0
  121. package/dist/core/security.js.map +1 -0
  122. package/dist/core/session-manager.d.ts.map +1 -0
  123. package/dist/core/session-manager.js.map +1 -0
  124. package/dist/core/streaming.d.ts.map +1 -0
  125. package/dist/core/streaming.js.map +1 -0
  126. package/dist/core/subagent.d.ts.map +1 -0
  127. package/dist/core/subagent.js.map +1 -0
  128. package/dist/core/types.d.ts.map +1 -0
  129. package/dist/core/types.js.map +1 -0
  130. package/dist/core/versioning.d.ts.map +1 -0
  131. package/dist/core/versioning.js.map +1 -0
  132. package/dist/core/vision.d.ts.map +1 -0
  133. package/dist/core/vision.js.map +1 -0
  134. package/dist/core/watch.d.ts.map +1 -0
  135. package/dist/core/watch.js.map +1 -0
  136. package/dist/core/workflow-graph.d.ts.map +1 -0
  137. package/dist/core/workflow-graph.js.map +1 -0
  138. package/dist/core/workflow.d.ts.map +1 -0
  139. package/dist/core/workflow.js.map +1 -0
  140. package/dist/daemon.d.ts.map +1 -0
  141. package/dist/daemon.js.map +1 -0
  142. package/dist/deploy/hermes.d.ts.map +1 -0
  143. package/dist/deploy/hermes.js.map +1 -0
  144. package/dist/deploy/index.d.ts.map +1 -0
  145. package/dist/deploy/index.js.map +1 -0
  146. package/dist/deploy/openclaw.d.ts.map +1 -0
  147. package/dist/deploy/openclaw.js.map +1 -0
  148. package/dist/doctor.d.ts.map +1 -0
  149. package/dist/doctor.js.map +1 -0
  150. package/dist/eval/index.d.ts.map +1 -0
  151. package/dist/eval/index.js.map +1 -0
  152. package/dist/hub/brain-seed.d.ts.map +1 -0
  153. package/dist/hub/brain-seed.js.map +1 -0
  154. package/dist/hub/client.d.ts.map +1 -0
  155. package/dist/hub/client.js.map +1 -0
  156. package/dist/i18n/index.d.ts.map +1 -0
  157. package/dist/i18n/index.js.map +1 -0
  158. package/dist/index.d.ts.map +1 -0
  159. package/dist/index.js.map +1 -0
  160. package/dist/mcp/servers/calculator-mcp.d.ts.map +1 -0
  161. package/dist/mcp/servers/calculator-mcp.js.map +1 -0
  162. package/dist/mcp/servers/crypto-mcp.d.ts.map +1 -0
  163. package/dist/mcp/servers/crypto-mcp.js.map +1 -0
  164. package/dist/mcp/servers/database-mcp.d.ts.map +1 -0
  165. package/dist/mcp/servers/database-mcp.js.map +1 -0
  166. package/dist/mcp/servers/datetime-mcp.d.ts.map +1 -0
  167. package/dist/mcp/servers/datetime-mcp.js.map +1 -0
  168. package/dist/mcp/servers/filesystem.d.ts.map +1 -0
  169. package/dist/mcp/servers/filesystem.js.map +1 -0
  170. package/dist/mcp/servers/github-mcp.d.ts.map +1 -0
  171. package/dist/mcp/servers/github-mcp.js.map +1 -0
  172. package/dist/mcp/servers/index.d.ts.map +1 -0
  173. package/dist/mcp/servers/index.js.map +1 -0
  174. package/dist/mcp/servers/json-mcp.d.ts.map +1 -0
  175. package/dist/mcp/servers/json-mcp.js.map +1 -0
  176. package/dist/mcp/servers/memory-mcp.d.ts.map +1 -0
  177. package/dist/mcp/servers/memory-mcp.js.map +1 -0
  178. package/dist/mcp/servers/regex-mcp.d.ts.map +1 -0
  179. package/dist/mcp/servers/regex-mcp.js.map +1 -0
  180. package/dist/mcp/servers/web-mcp.d.ts.map +1 -0
  181. package/dist/mcp/servers/web-mcp.js.map +1 -0
  182. package/dist/memory/context-compressor.d.ts.map +1 -0
  183. package/dist/memory/context-compressor.js.map +1 -0
  184. package/dist/memory/deepbrain.d.ts.map +1 -0
  185. package/dist/memory/deepbrain.js.map +1 -0
  186. package/dist/memory/index.d.ts.map +1 -0
  187. package/dist/memory/index.js.map +1 -0
  188. package/dist/memory/seed-loader.d.ts.map +1 -0
  189. package/dist/memory/seed-loader.js.map +1 -0
  190. package/dist/memory/user-profiler.d.ts.map +1 -0
  191. package/dist/memory/user-profiler.js.map +1 -0
  192. package/dist/plugins/content-filter.d.ts.map +1 -0
  193. package/dist/plugins/content-filter.js.map +1 -0
  194. package/dist/plugins/index.d.ts.map +1 -0
  195. package/dist/plugins/index.js.map +1 -0
  196. package/dist/plugins/logger.d.ts.map +1 -0
  197. package/dist/plugins/logger.js.map +1 -0
  198. package/dist/plugins/rate-limiter.d.ts.map +1 -0
  199. package/dist/plugins/rate-limiter.js.map +1 -0
  200. package/dist/protocols/a2a/client.d.ts.map +1 -0
  201. package/dist/protocols/a2a/client.js.map +1 -0
  202. package/dist/protocols/a2a/index.d.ts.map +1 -0
  203. package/dist/protocols/a2a/index.js.map +1 -0
  204. package/dist/protocols/a2a/server.d.ts.map +1 -0
  205. package/dist/protocols/a2a/server.js.map +1 -0
  206. package/dist/protocols/a2a/types.d.ts.map +1 -0
  207. package/dist/protocols/a2a/types.js.map +1 -0
  208. package/dist/protocols/a2a/utils.d.ts.map +1 -0
  209. package/dist/protocols/a2a/utils.js.map +1 -0
  210. package/dist/protocols/agui/client.d.ts.map +1 -0
  211. package/dist/protocols/agui/client.js.map +1 -0
  212. package/dist/protocols/agui/index.d.ts.map +1 -0
  213. package/dist/protocols/agui/index.js.map +1 -0
  214. package/dist/protocols/agui/server.d.ts.map +1 -0
  215. package/dist/protocols/agui/server.js.map +1 -0
  216. package/dist/protocols/agui/types.d.ts.map +1 -0
  217. package/dist/protocols/agui/types.js.map +1 -0
  218. package/dist/protocols/index.d.ts.map +1 -0
  219. package/dist/protocols/index.js.map +1 -0
  220. package/dist/protocols/mcp/agent-tools.d.ts.map +1 -0
  221. package/dist/protocols/mcp/agent-tools.js.map +1 -0
  222. package/dist/protocols/mcp/index.d.ts.map +1 -0
  223. package/dist/protocols/mcp/index.js.map +1 -0
  224. package/dist/protocols/mcp/server.d.ts.map +1 -0
  225. package/dist/protocols/mcp/server.js.map +1 -0
  226. package/dist/protocols/mcp/types.d.ts.map +1 -0
  227. package/dist/protocols/mcp/types.js.map +1 -0
  228. package/dist/providers/index.d.ts.map +1 -0
  229. package/dist/providers/index.js.map +1 -0
  230. package/dist/publish/index.d.ts.map +1 -0
  231. package/dist/publish/index.js.map +1 -0
  232. package/dist/scheduler/cron-engine.d.ts.map +1 -0
  233. package/dist/scheduler/cron-engine.js.map +1 -0
  234. package/dist/scheduler/index.d.ts.map +1 -0
  235. package/dist/scheduler/index.js.map +1 -0
  236. package/dist/schema/oad.d.ts.map +1 -0
  237. package/dist/schema/oad.js.map +1 -0
  238. package/dist/security/approval.d.ts.map +1 -0
  239. package/dist/security/approval.js.map +1 -0
  240. package/dist/security/approvals.d.ts.map +1 -0
  241. package/dist/security/approvals.js.map +1 -0
  242. package/dist/security/elevated.d.ts.map +1 -0
  243. package/dist/security/elevated.js.map +1 -0
  244. package/dist/security/guardrails.d.ts.map +1 -0
  245. package/dist/security/guardrails.js.map +1 -0
  246. package/dist/security/index.d.ts.map +1 -0
  247. package/dist/security/index.js.map +1 -0
  248. package/dist/security/keys.d.ts.map +1 -0
  249. package/dist/security/keys.js.map +1 -0
  250. package/dist/security/secrets.d.ts.map +1 -0
  251. package/dist/security/secrets.js.map +1 -0
  252. package/dist/skills/auto-learn.d.ts.map +1 -0
  253. package/dist/skills/auto-learn.js.map +1 -0
  254. package/dist/skills/base.d.ts.map +1 -0
  255. package/dist/skills/base.js.map +1 -0
  256. package/dist/skills/builtin/index.d.ts.map +1 -0
  257. package/dist/skills/builtin/index.js.map +1 -0
  258. package/dist/skills/document.d.ts.map +1 -0
  259. package/dist/skills/document.js.map +1 -0
  260. package/dist/skills/http.d.ts.map +1 -0
  261. package/dist/skills/http.js.map +1 -0
  262. package/dist/skills/index.d.ts.map +1 -0
  263. package/dist/skills/index.js.map +1 -0
  264. package/dist/skills/marketplace.d.ts.map +1 -0
  265. package/dist/skills/marketplace.js.map +1 -0
  266. package/dist/skills/scheduler.d.ts.map +1 -0
  267. package/dist/skills/scheduler.js.map +1 -0
  268. package/dist/skills/types.d.ts.map +1 -0
  269. package/dist/skills/types.js.map +1 -0
  270. package/dist/skills/webhook-trigger.d.ts.map +1 -0
  271. package/dist/skills/webhook-trigger.js.map +1 -0
  272. package/dist/studio/server.d.ts.map +1 -0
  273. package/dist/studio/server.js.map +1 -0
  274. package/dist/studio/templates-data.d.ts.map +1 -0
  275. package/dist/studio/templates-data.js.map +1 -0
  276. package/dist/telemetry/index.d.ts.map +1 -0
  277. package/dist/telemetry/index.js.map +1 -0
  278. package/dist/templates/code-reviewer.d.ts.map +1 -0
  279. package/dist/templates/code-reviewer.js.map +1 -0
  280. package/dist/templates/content-writer.d.ts.map +1 -0
  281. package/dist/templates/content-writer.js.map +1 -0
  282. package/dist/templates/customer-service.d.ts.map +1 -0
  283. package/dist/templates/customer-service.js.map +1 -0
  284. package/dist/templates/data-analyst.d.ts.map +1 -0
  285. package/dist/templates/data-analyst.js.map +1 -0
  286. package/dist/templates/executive-assistant.d.ts.map +1 -0
  287. package/dist/templates/executive-assistant.js.map +1 -0
  288. package/dist/templates/financial-advisor.d.ts.map +1 -0
  289. package/dist/templates/financial-advisor.js.map +1 -0
  290. package/dist/templates/hr-recruiter.d.ts.map +1 -0
  291. package/dist/templates/hr-recruiter.js.map +1 -0
  292. package/dist/templates/knowledge-base.d.ts.map +1 -0
  293. package/dist/templates/knowledge-base.js.map +1 -0
  294. package/dist/templates/legal-assistant.d.ts.map +1 -0
  295. package/dist/templates/legal-assistant.js.map +1 -0
  296. package/dist/templates/project-manager.d.ts.map +1 -0
  297. package/dist/templates/project-manager.js.map +1 -0
  298. package/dist/templates/sales-assistant.d.ts.map +1 -0
  299. package/dist/templates/sales-assistant.js.map +1 -0
  300. package/dist/templates/teacher.d.ts.map +1 -0
  301. package/dist/templates/teacher.js.map +1 -0
  302. package/dist/testing/index.d.ts.map +1 -0
  303. package/dist/testing/index.js.map +1 -0
  304. package/dist/tools/builtin/browser.d.ts.map +1 -0
  305. package/dist/tools/builtin/browser.js.map +1 -0
  306. package/dist/tools/builtin/datetime.d.ts.map +1 -0
  307. package/dist/tools/builtin/datetime.js.map +1 -0
  308. package/dist/tools/builtin/file.d.ts.map +1 -0
  309. package/dist/tools/builtin/file.js.map +1 -0
  310. package/dist/tools/builtin/home-assistant.d.ts.map +1 -0
  311. package/dist/tools/builtin/home-assistant.js.map +1 -0
  312. package/dist/tools/builtin/index.d.ts.map +1 -0
  313. package/dist/tools/builtin/index.js.map +1 -0
  314. package/dist/tools/builtin/rl-tools.d.ts.map +1 -0
  315. package/dist/tools/builtin/rl-tools.js.map +1 -0
  316. package/dist/tools/builtin/shell.d.ts.map +1 -0
  317. package/dist/tools/builtin/shell.js.map +1 -0
  318. package/dist/tools/builtin/vision.d.ts.map +1 -0
  319. package/dist/tools/builtin/vision.js.map +1 -0
  320. package/dist/tools/builtin/web-search.d.ts.map +1 -0
  321. package/dist/tools/builtin/web-search.js.map +1 -0
  322. package/dist/tools/builtin/web.d.ts.map +1 -0
  323. package/dist/tools/builtin/web.js.map +1 -0
  324. package/dist/tools/calculator.d.ts.map +1 -0
  325. package/dist/tools/calculator.js.map +1 -0
  326. package/dist/tools/datetime.d.ts.map +1 -0
  327. package/dist/tools/datetime.js.map +1 -0
  328. package/dist/tools/document-processor.d.ts.map +1 -0
  329. package/dist/tools/document-processor.js.map +1 -0
  330. package/dist/tools/gateway.d.ts.map +1 -0
  331. package/dist/tools/gateway.js.map +1 -0
  332. package/dist/tools/image-generator.d.ts.map +1 -0
  333. package/dist/tools/image-generator.js.map +1 -0
  334. package/dist/tools/integrations/calendar.d.ts.map +1 -0
  335. package/dist/tools/integrations/calendar.js.map +1 -0
  336. package/dist/tools/integrations/code-exec.d.ts.map +1 -0
  337. package/dist/tools/integrations/code-exec.js.map +1 -0
  338. package/dist/tools/integrations/csv-analyzer.d.ts.map +1 -0
  339. package/dist/tools/integrations/csv-analyzer.js.map +1 -0
  340. package/dist/tools/integrations/database.d.ts.map +1 -0
  341. package/dist/tools/integrations/database.js.map +1 -0
  342. package/dist/tools/integrations/email-send.d.ts.map +1 -0
  343. package/dist/tools/integrations/email-send.js.map +1 -0
  344. package/dist/tools/integrations/git-tool.d.ts.map +1 -0
  345. package/dist/tools/integrations/git-tool.js.map +1 -0
  346. package/dist/tools/integrations/github-tool.d.ts.map +1 -0
  347. package/dist/tools/integrations/github-tool.js.map +1 -0
  348. package/dist/tools/integrations/image-gen.d.ts.map +1 -0
  349. package/dist/tools/integrations/image-gen.js.map +1 -0
  350. package/dist/tools/integrations/index.d.ts.map +1 -0
  351. package/dist/tools/integrations/index.js.map +1 -0
  352. package/dist/tools/integrations/jira.d.ts.map +1 -0
  353. package/dist/tools/integrations/jira.js.map +1 -0
  354. package/dist/tools/integrations/notion.d.ts.map +1 -0
  355. package/dist/tools/integrations/notion.js.map +1 -0
  356. package/dist/tools/integrations/npm-tool.d.ts.map +1 -0
  357. package/dist/tools/integrations/npm-tool.js.map +1 -0
  358. package/dist/tools/integrations/pdf-reader.d.ts.map +1 -0
  359. package/dist/tools/integrations/pdf-reader.js.map +1 -0
  360. package/dist/tools/integrations/slack.d.ts.map +1 -0
  361. package/dist/tools/integrations/slack.js.map +1 -0
  362. package/dist/tools/integrations/summarizer.d.ts.map +1 -0
  363. package/dist/tools/integrations/summarizer.js.map +1 -0
  364. package/dist/tools/integrations/translator.d.ts.map +1 -0
  365. package/dist/tools/integrations/translator.js.map +1 -0
  366. package/dist/tools/integrations/trello.d.ts.map +1 -0
  367. package/dist/tools/integrations/trello.js.map +1 -0
  368. package/dist/tools/integrations/vector-search.d.ts.map +1 -0
  369. package/dist/tools/integrations/vector-search.js.map +1 -0
  370. package/dist/tools/integrations/web-scraper.d.ts.map +1 -0
  371. package/dist/tools/integrations/web-scraper.js.map +1 -0
  372. package/dist/tools/integrations/web-search.d.ts.map +1 -0
  373. package/dist/tools/integrations/web-search.js.map +1 -0
  374. package/dist/tools/integrations/webhook.d.ts.map +1 -0
  375. package/dist/tools/integrations/webhook.js.map +1 -0
  376. package/dist/tools/json-transform.d.ts.map +1 -0
  377. package/dist/tools/json-transform.js.map +1 -0
  378. package/dist/tools/mcp-client.d.ts.map +1 -0
  379. package/dist/tools/mcp-client.js.map +1 -0
  380. package/dist/tools/mcp.d.ts.map +1 -0
  381. package/dist/tools/mcp.js.map +1 -0
  382. package/dist/tools/text-analysis.d.ts.map +1 -0
  383. package/dist/tools/text-analysis.js.map +1 -0
  384. package/dist/tools/web-scraper.d.ts.map +1 -0
  385. package/dist/tools/web-scraper.js.map +1 -0
  386. package/dist/tools/web-search.d.ts.map +1 -0
  387. package/dist/tools/web-search.js.map +1 -0
  388. package/dist/traces/index.d.ts.map +1 -0
  389. package/dist/traces/index.js.map +1 -0
  390. package/dist/ui/components.d.ts.map +1 -0
  391. package/dist/ui/components.js.map +1 -0
  392. package/package.json +1 -1
  393. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -20
  394. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -14
  395. package/.github/PULL_REQUEST_TEMPLATE.md +0 -13
  396. package/.github/workflows/ci.yml +0 -24
  397. package/USABILITY-ISSUES.md +0 -73
  398. package/docs/.vitepress/config.ts +0 -103
  399. package/docs/api/cli.md +0 -48
  400. package/docs/api/oad-schema.md +0 -64
  401. package/docs/api/sdk.md +0 -80
  402. package/docs/guide/concepts.md +0 -51
  403. package/docs/guide/configuration.md +0 -79
  404. package/docs/guide/deployment.md +0 -42
  405. package/docs/guide/getting-started.md +0 -44
  406. package/docs/guide/templates.md +0 -28
  407. package/docs/guide/testing.md +0 -84
  408. package/docs/index.md +0 -27
  409. package/docs/zh/api/cli.md +0 -54
  410. package/docs/zh/api/oad-schema.md +0 -87
  411. package/docs/zh/api/sdk.md +0 -102
  412. package/docs/zh/guide/concepts.md +0 -104
  413. package/docs/zh/guide/configuration.md +0 -135
  414. package/docs/zh/guide/deployment.md +0 -81
  415. package/docs/zh/guide/getting-started.md +0 -82
  416. package/docs/zh/guide/templates.md +0 -84
  417. package/docs/zh/guide/testing.md +0 -88
  418. package/docs/zh/index.md +0 -27
  419. package/fix-sidebar.mjs +0 -188
  420. package/serve-studio.js +0 -13
  421. package/serve-test.js +0 -25
  422. package/src/analytics/index.ts +0 -66
  423. package/src/channels/dingtalk.ts +0 -46
  424. package/src/channels/discord.ts +0 -192
  425. package/src/channels/email.ts +0 -351
  426. package/src/channels/feishu.ts +0 -349
  427. package/src/channels/googlechat.ts +0 -42
  428. package/src/channels/imessage.ts +0 -32
  429. package/src/channels/index.ts +0 -15
  430. package/src/channels/irc.ts +0 -82
  431. package/src/channels/line.ts +0 -33
  432. package/src/channels/matrix.ts +0 -34
  433. package/src/channels/mattermost.ts +0 -57
  434. package/src/channels/msteams.ts +0 -33
  435. package/src/channels/nostr.ts +0 -33
  436. package/src/channels/qq.ts +0 -34
  437. package/src/channels/signal.ts +0 -33
  438. package/src/channels/slack.ts +0 -217
  439. package/src/channels/sms.ts +0 -34
  440. package/src/channels/telegram.ts +0 -616
  441. package/src/channels/twitch.ts +0 -65
  442. package/src/channels/voice-call.ts +0 -100
  443. package/src/channels/voice.ts +0 -471
  444. package/src/channels/web.ts +0 -638
  445. package/src/channels/webhook.ts +0 -199
  446. package/src/channels/websocket.ts +0 -399
  447. package/src/channels/wechat.ts +0 -329
  448. package/src/channels/whatsapp.ts +0 -33
  449. package/src/cli/chat.ts +0 -99
  450. package/src/cli/setup.ts +0 -314
  451. package/src/cli.ts +0 -2826
  452. package/src/core/a2a.ts +0 -203
  453. package/src/core/agent.ts +0 -476
  454. package/src/core/analytics-engine.ts +0 -186
  455. package/src/core/api-server.ts +0 -277
  456. package/src/core/audio.ts +0 -98
  457. package/src/core/auth.ts +0 -57
  458. package/src/core/cache.ts +0 -141
  459. package/src/core/collaboration.ts +0 -275
  460. package/src/core/compose.ts +0 -77
  461. package/src/core/config.ts +0 -14
  462. package/src/core/context-discovery.ts +0 -85
  463. package/src/core/context-refs.ts +0 -140
  464. package/src/core/errors.ts +0 -148
  465. package/src/core/gateway.ts +0 -106
  466. package/src/core/heartbeat.ts +0 -51
  467. package/src/core/hitl.ts +0 -138
  468. package/src/core/hooks.ts +0 -105
  469. package/src/core/ide-bridge.ts +0 -133
  470. package/src/core/knowledge.ts +0 -255
  471. package/src/core/logger.ts +0 -57
  472. package/src/core/node-network.ts +0 -86
  473. package/src/core/orchestrator.ts +0 -215
  474. package/src/core/performance.ts +0 -187
  475. package/src/core/profiles.ts +0 -122
  476. package/src/core/rate-limiter.ts +0 -128
  477. package/src/core/room.ts +0 -109
  478. package/src/core/runtime.ts +0 -435
  479. package/src/core/sandbox.ts +0 -344
  480. package/src/core/scheduler.ts +0 -187
  481. package/src/core/security.ts +0 -171
  482. package/src/core/session-manager.ts +0 -137
  483. package/src/core/streaming.ts +0 -195
  484. package/src/core/subagent.ts +0 -98
  485. package/src/core/types.ts +0 -68
  486. package/src/core/versioning.ts +0 -106
  487. package/src/core/vision.ts +0 -180
  488. package/src/core/watch.ts +0 -178
  489. package/src/core/workflow-graph.ts +0 -365
  490. package/src/core/workflow.ts +0 -235
  491. package/src/daemon.ts +0 -96
  492. package/src/deploy/hermes.ts +0 -156
  493. package/src/deploy/index.ts +0 -255
  494. package/src/deploy/openclaw.ts +0 -190
  495. package/src/doctor.ts +0 -243
  496. package/src/eval/index.ts +0 -211
  497. package/src/eval/suites/basic.json +0 -16
  498. package/src/eval/suites/memory.json +0 -12
  499. package/src/eval/suites/safety.json +0 -14
  500. package/src/hub/brain-seed.ts +0 -54
  501. package/src/hub/client.ts +0 -60
  502. package/src/i18n/index.ts +0 -216
  503. package/src/index.ts +0 -283
  504. package/src/mcp/servers/calculator-mcp.ts +0 -65
  505. package/src/mcp/servers/crypto-mcp.ts +0 -73
  506. package/src/mcp/servers/database-mcp.ts +0 -72
  507. package/src/mcp/servers/datetime-mcp.ts +0 -69
  508. package/src/mcp/servers/filesystem.ts +0 -66
  509. package/src/mcp/servers/github-mcp.ts +0 -58
  510. package/src/mcp/servers/index.ts +0 -63
  511. package/src/mcp/servers/json-mcp.ts +0 -102
  512. package/src/mcp/servers/memory-mcp.ts +0 -56
  513. package/src/mcp/servers/regex-mcp.ts +0 -53
  514. package/src/mcp/servers/web-mcp.ts +0 -49
  515. package/src/memory/context-compressor.ts +0 -189
  516. package/src/memory/deepbrain.ts +0 -202
  517. package/src/memory/index.ts +0 -41
  518. package/src/memory/seed-loader.ts +0 -212
  519. package/src/memory/user-profiler.ts +0 -215
  520. package/src/plugins/content-filter.ts +0 -23
  521. package/src/plugins/index.ts +0 -339
  522. package/src/plugins/logger.ts +0 -18
  523. package/src/plugins/rate-limiter.ts +0 -38
  524. package/src/protocols/a2a/client.ts +0 -132
  525. package/src/protocols/a2a/index.ts +0 -8
  526. package/src/protocols/a2a/server.ts +0 -333
  527. package/src/protocols/a2a/types.ts +0 -88
  528. package/src/protocols/a2a/utils.ts +0 -50
  529. package/src/protocols/agui/client.ts +0 -83
  530. package/src/protocols/agui/index.ts +0 -4
  531. package/src/protocols/agui/server.ts +0 -218
  532. package/src/protocols/agui/types.ts +0 -153
  533. package/src/protocols/index.ts +0 -2
  534. package/src/protocols/mcp/agent-tools.ts +0 -134
  535. package/src/protocols/mcp/index.ts +0 -8
  536. package/src/protocols/mcp/server.ts +0 -262
  537. package/src/protocols/mcp/types.ts +0 -69
  538. package/src/providers/index.ts +0 -632
  539. package/src/publish/index.ts +0 -376
  540. package/src/scheduler/cron-engine.ts +0 -191
  541. package/src/scheduler/index.ts +0 -2
  542. package/src/schema/oad.ts +0 -217
  543. package/src/security/approval.ts +0 -131
  544. package/src/security/approvals.ts +0 -143
  545. package/src/security/elevated.ts +0 -105
  546. package/src/security/guardrails.ts +0 -248
  547. package/src/security/index.ts +0 -9
  548. package/src/security/keys.ts +0 -87
  549. package/src/security/secrets.ts +0 -129
  550. package/src/skills/auto-learn.ts +0 -262
  551. package/src/skills/base.ts +0 -16
  552. package/src/skills/builtin/index.ts +0 -408
  553. package/src/skills/document.ts +0 -100
  554. package/src/skills/http.ts +0 -35
  555. package/src/skills/index.ts +0 -27
  556. package/src/skills/marketplace.ts +0 -113
  557. package/src/skills/scheduler.ts +0 -80
  558. package/src/skills/types.ts +0 -42
  559. package/src/skills/webhook-trigger.ts +0 -59
  560. package/src/studio/server.ts +0 -1791
  561. package/src/studio/templates-data.ts +0 -178
  562. package/src/studio-ui/index.html +0 -3076
  563. package/src/telemetry/index.ts +0 -324
  564. package/src/templates/code-reviewer.ts +0 -30
  565. package/src/templates/content-writer.ts +0 -58
  566. package/src/templates/customer-service.ts +0 -76
  567. package/src/templates/data-analyst.ts +0 -66
  568. package/src/templates/executive-assistant.ts +0 -71
  569. package/src/templates/financial-advisor.ts +0 -60
  570. package/src/templates/hr-recruiter.ts +0 -58
  571. package/src/templates/knowledge-base.ts +0 -27
  572. package/src/templates/legal-assistant.ts +0 -71
  573. package/src/templates/project-manager.ts +0 -58
  574. package/src/templates/sales-assistant.ts +0 -75
  575. package/src/templates/teacher.ts +0 -75
  576. package/src/testing/index.ts +0 -181
  577. package/src/tools/builtin/browser.ts +0 -299
  578. package/src/tools/builtin/datetime.ts +0 -41
  579. package/src/tools/builtin/file.ts +0 -107
  580. package/src/tools/builtin/home-assistant.ts +0 -116
  581. package/src/tools/builtin/index.ts +0 -37
  582. package/src/tools/builtin/rl-tools.ts +0 -243
  583. package/src/tools/builtin/shell.ts +0 -43
  584. package/src/tools/builtin/vision.ts +0 -64
  585. package/src/tools/builtin/web-search.ts +0 -126
  586. package/src/tools/builtin/web.ts +0 -35
  587. package/src/tools/calculator.ts +0 -73
  588. package/src/tools/datetime.ts +0 -149
  589. package/src/tools/document-processor.ts +0 -213
  590. package/src/tools/gateway.ts +0 -220
  591. package/src/tools/image-generator.ts +0 -150
  592. package/src/tools/integrations/calendar.ts +0 -73
  593. package/src/tools/integrations/code-exec.ts +0 -39
  594. package/src/tools/integrations/csv-analyzer.ts +0 -92
  595. package/src/tools/integrations/database.ts +0 -44
  596. package/src/tools/integrations/email-send.ts +0 -76
  597. package/src/tools/integrations/git-tool.ts +0 -42
  598. package/src/tools/integrations/github-tool.ts +0 -76
  599. package/src/tools/integrations/image-gen.ts +0 -56
  600. package/src/tools/integrations/index.ts +0 -92
  601. package/src/tools/integrations/jira.ts +0 -83
  602. package/src/tools/integrations/notion.ts +0 -71
  603. package/src/tools/integrations/npm-tool.ts +0 -48
  604. package/src/tools/integrations/pdf-reader.ts +0 -58
  605. package/src/tools/integrations/slack.ts +0 -65
  606. package/src/tools/integrations/summarizer.ts +0 -49
  607. package/src/tools/integrations/translator.ts +0 -48
  608. package/src/tools/integrations/trello.ts +0 -60
  609. package/src/tools/integrations/vector-search.ts +0 -42
  610. package/src/tools/integrations/web-scraper.ts +0 -47
  611. package/src/tools/integrations/web-search.ts +0 -58
  612. package/src/tools/integrations/webhook.ts +0 -38
  613. package/src/tools/json-transform.ts +0 -187
  614. package/src/tools/mcp-client.ts +0 -131
  615. package/src/tools/mcp.ts +0 -76
  616. package/src/tools/text-analysis.ts +0 -116
  617. package/src/tools/web-scraper.ts +0 -179
  618. package/src/tools/web-search.ts +0 -180
  619. package/src/traces/index.ts +0 -132
  620. package/src/types/agent-workstation.d.ts +0 -2
  621. package/src/ui/components.ts +0 -127
  622. package/srv-err.txt +0 -0
  623. package/srv-out.txt +0 -1
  624. package/test-agent/Dockerfile +0 -9
  625. package/test-agent/README.md +0 -50
  626. package/test-agent/agent.yaml +0 -23
  627. package/test-agent/docker-compose.yml +0 -11
  628. package/test-agent/oad.yaml +0 -31
  629. package/test-agent/package-lock.json +0 -1492
  630. package/test-agent/package.json +0 -18
  631. package/test-agent/src/index.ts +0 -24
  632. package/test-agent/src/skills/echo.ts +0 -15
  633. package/test-agent/tsconfig.json +0 -25
  634. package/test-full.js +0 -43
  635. package/test-sidebar.js +0 -22
  636. package/test-studio3.js +0 -75
  637. package/test-studio4.js +0 -41
  638. package/tests/a2a-protocol.test.ts +0 -285
  639. package/tests/a2a.test.ts +0 -66
  640. package/tests/agent.test.ts +0 -72
  641. package/tests/agui-protocol.test.ts +0 -246
  642. package/tests/analytics.test.ts +0 -50
  643. package/tests/api-server.test.ts +0 -148
  644. package/tests/approvals.test.ts +0 -89
  645. package/tests/audio.test.ts +0 -40
  646. package/tests/auto-learn.test.ts +0 -105
  647. package/tests/brain-seed-extended.test.ts +0 -490
  648. package/tests/brain-seed.test.ts +0 -239
  649. package/tests/browser.test.ts +0 -179
  650. package/tests/builtin-tools.test.ts +0 -83
  651. package/tests/channel.test.ts +0 -39
  652. package/tests/channels/discord.test.ts +0 -79
  653. package/tests/channels/email.test.ts +0 -148
  654. package/tests/channels/feishu.test.ts +0 -123
  655. package/tests/channels/telegram.test.ts +0 -129
  656. package/tests/channels/websocket.test.ts +0 -53
  657. package/tests/channels/wechat.test.ts +0 -170
  658. package/tests/channels-extra.test.ts +0 -45
  659. package/tests/chat-cli.test.ts +0 -160
  660. package/tests/cli.test.ts +0 -46
  661. package/tests/collaboration.test.ts +0 -319
  662. package/tests/context-compressor.test.ts +0 -172
  663. package/tests/context-refs.test.ts +0 -121
  664. package/tests/cron-engine.test.ts +0 -101
  665. package/tests/daemon.test.ts +0 -135
  666. package/tests/deepbrain-wire.test.ts +0 -234
  667. package/tests/deploy-and-dag.test.ts +0 -196
  668. package/tests/doctor.test.ts +0 -38
  669. package/tests/document-processor.test.ts +0 -69
  670. package/tests/e2e-nocode.test.ts +0 -442
  671. package/tests/e2e.test.ts +0 -134
  672. package/tests/elevated.test.ts +0 -69
  673. package/tests/errors.test.ts +0 -83
  674. package/tests/eval.test.ts +0 -173
  675. package/tests/gateway.test.ts +0 -63
  676. package/tests/guardrails.test.ts +0 -177
  677. package/tests/hitl.test.ts +0 -71
  678. package/tests/home-assistant.test.ts +0 -40
  679. package/tests/hooks.test.ts +0 -79
  680. package/tests/i18n.test.ts +0 -41
  681. package/tests/ide-bridge.test.ts +0 -38
  682. package/tests/image-generator.test.ts +0 -84
  683. package/tests/init-role.test.ts +0 -124
  684. package/tests/integrations.test.ts +0 -249
  685. package/tests/mcp-client.test.ts +0 -92
  686. package/tests/mcp-server.test.ts +0 -178
  687. package/tests/mcp-servers.test.ts +0 -260
  688. package/tests/mcp.test.ts +0 -54
  689. package/tests/node-network.test.ts +0 -74
  690. package/tests/oad.test.ts +0 -68
  691. package/tests/performance.test.ts +0 -115
  692. package/tests/plugin-a2a-enhanced.test.ts +0 -230
  693. package/tests/plugin.test.ts +0 -74
  694. package/tests/profiles.test.ts +0 -61
  695. package/tests/publish.test.ts +0 -231
  696. package/tests/rl-tools.test.ts +0 -93
  697. package/tests/room.test.ts +0 -106
  698. package/tests/runtime.test.ts +0 -42
  699. package/tests/sandbox-manager.test.ts +0 -46
  700. package/tests/sandbox.test.ts +0 -46
  701. package/tests/scheduler.test.ts +0 -200
  702. package/tests/secrets.test.ts +0 -107
  703. package/tests/security-enhanced.test.ts +0 -233
  704. package/tests/security.test.ts +0 -60
  705. package/tests/settings-api.test.ts +0 -148
  706. package/tests/setup.test.ts +0 -73
  707. package/tests/skill-learner.test.ts +0 -161
  708. package/tests/streaming.test.ts +0 -109
  709. package/tests/studio.test.ts +0 -402
  710. package/tests/subagent.test.ts +0 -193
  711. package/tests/telegram-discord.test.ts +0 -60
  712. package/tests/telemetry.test.ts +0 -186
  713. package/tests/templates.test.ts +0 -77
  714. package/tests/tools/builtin-extended.test.ts +0 -138
  715. package/tests/user-profiler.test.ts +0 -169
  716. package/tests/v070.test.ts +0 -76
  717. package/tests/v090-features.test.ts +0 -254
  718. package/tests/versioning.test.ts +0 -75
  719. package/tests/vision.test.ts +0 -61
  720. package/tests/voice-call.test.ts +0 -47
  721. package/tests/voice-enhanced.test.ts +0 -169
  722. package/tests/voice-interaction.test.ts +0 -38
  723. package/tests/voice.test.ts +0 -61
  724. package/tests/web-search.test.ts +0 -155
  725. package/tests/webhook.test.ts +0 -29
  726. package/tests/workflow-graph.test.ts +0 -279
  727. package/tests/workflow.test.ts +0 -143
  728. package/tmp-js-test.js +0 -1532
  729. package/tmp-sc.js +0 -1716
  730. package/tutorial/customer-service-agent/README.md +0 -612
  731. package/tutorial/customer-service-agent/SOUL.md +0 -26
  732. package/tutorial/customer-service-agent/agent.yaml +0 -63
  733. package/tutorial/customer-service-agent/package.json +0 -19
  734. package/tutorial/customer-service-agent/src/index.ts +0 -69
  735. package/tutorial/customer-service-agent/src/skills/faq.ts +0 -27
  736. package/tutorial/customer-service-agent/src/skills/ticket.ts +0 -22
  737. package/tutorial/customer-service-agent/tsconfig.json +0 -14
  738. package/vitest.config.ts +0 -9
@@ -1,3076 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>OPC Studio</title>
7
- <style>
8
- * { margin: 0; padding: 0; box-sizing: border-box; }
9
- :root {
10
- --bg: #05051a; --bg-card: rgba(15,15,40,0.6); --bg-hover: rgba(124,92,255,0.08); --bg-input: rgba(15,15,40,0.8);
11
- --border: rgba(124,92,255,0.15); --text: #f0f0ff; --text-muted: #9090cc; --text-dim: #5555aa;
12
- --accent: #8b5cf6; --accent-hover: #7c3aed; --accent-light: rgba(139,92,246,0.15);
13
- --green: #10b981; --red: #f87171; --yellow: #fbbf24; --purple: #c084fc;
14
- --gradient: linear-gradient(135deg, #8b5cf6 0%, #06b6d4 50%, #8b5cf6 100%);
15
- --gradient-btn: linear-gradient(135deg, #8b5cf6 0%, #3b82f6 50%, #06b6d4 100%);
16
- --glow: 0 0 30px rgba(139,92,246,0.4), 0 0 60px rgba(6,182,212,0.2);
17
- --glow-sm: 0 0 15px rgba(139,92,246,0.25);
18
- --card-glow: 0 4px 40px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.05);
19
- --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
20
- --mono: 'JetBrains Mono', 'SF Mono', 'Fira Code', monospace;
21
- --radius: 14px; --radius-lg: 24px;
22
- }
23
- body { font-family: var(--font); background: var(--bg); color: var(--text); min-height: 100vh; font-size: 18px; line-height: 1.6; background-image: radial-gradient(ellipse at 20% 0%, rgba(139,92,246,0.12) 0%, transparent 50%), radial-gradient(ellipse at 80% 100%, rgba(6,182,212,0.08) 0%, transparent 50%), url("data:image/svg+xml,%3Csvg width='60' height='60' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M0 30h60M30 0v60' stroke='rgba(139,92,246,0.04)' stroke-width='0.5'/%3E%3C/svg%3E"); }
24
- a { color: var(--accent); text-decoration: none; }
25
- button { font-family: var(--font); cursor: pointer; border: none; }
26
- input, select, textarea { font-family: var(--font); }
27
-
28
- /* Layout */
29
- .app { display: flex; min-height: 100vh; }
30
- .sidebar {
31
- width: 270px; background: rgba(5,5,30,0.9); backdrop-filter: blur(30px); border-right: 1px solid var(--border);
32
- padding: 24px 16px; display: flex; flex-direction: column; position: fixed; height: 100vh; z-index: 100;
33
- background-image: linear-gradient(180deg, rgba(139,92,246,0.05) 0%, transparent 30%);
34
- }
35
- .sidebar-logo { font-size: 24px; font-weight: 700; padding: 12px 12px; margin-bottom: 28px; display: flex; align-items: center; gap: 10px; background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; letter-spacing: -0.5px; }
36
- .sidebar-logo span { background: var(--gradient); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
37
- .nav-item {
38
- display: flex; align-items: center; gap: 12px; padding: 12px 16px; border-radius: 12px;
39
- cursor: pointer; color: var(--text-muted); transition: all 0.2s ease; font-size: 18px; margin-bottom: 4px; position: relative;
40
- }
41
- .nav-item:hover { background: var(--bg-hover); color: var(--text); transform: translateX(4px); }
42
- .nav-item.active { background: var(--accent-light); color: #fff; font-weight: 600; box-shadow: var(--glow-sm); border: 1px solid var(--border); }
43
- .nav-item .icon { width: 28px; text-align: center; font-size: 22px; }
44
- .main { flex: 1; margin-left: 270px; min-height: 100vh; }
45
-
46
- /* Mobile */
47
- .mobile-header { display: none; background: var(--bg-card); border-bottom: 1px solid var(--border); padding: 12px 16px; position: sticky; top: 0; z-index: 50; }
48
- .mobile-header button { background: none; border: none; color: var(--text); font-size: 20px; }
49
- @media (max-width: 768px) {
50
- .sidebar { transform: translateX(-100%); transition: transform 0.2s; width: 260px; }
51
- .sidebar.open { transform: translateX(0); }
52
- .mobile-header { display: flex; align-items: center; justify-content: space-between; }
53
- .main { margin-left: 0; }
54
- .sidebar-overlay { display: none; position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 99; }
55
- .sidebar-overlay.show { display: block; }
56
- }
57
-
58
- /* Page container */
59
- .page { display: none; padding: 32px; max-width: 1200px; margin: 0 auto; }
60
- .page.active { display: block; }
61
- .page-title { font-size: 32px; font-weight: 700; margin-bottom: 8px; background: var(--gradient); background-size: 200% auto; -webkit-background-clip: text; -webkit-text-fill-color: transparent; animation: shimmer 3s linear infinite; letter-spacing: -0.5px; }
62
- @keyframes shimmer { 0% { background-position: 0% center; } 100% { background-position: 200% center; } }
63
- .page-subtitle { color: var(--text-muted); font-size: 18px; margin-bottom: 24px; }
64
-
65
- /* Cards */
66
- .card { background: var(--bg-card); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; transition: all 0.3s ease; backdrop-filter: blur(10px); box-shadow: var(--card-glow); position: relative; overflow: hidden; }
67
- .card::before { content: ''; position: absolute; top: 0; left: -100%; width: 100%; height: 2px; background: var(--gradient); transition: left 0.4s ease; }
68
- .card:hover { border-color: rgba(139,92,246,0.4); transform: translateY(-3px); box-shadow: 0 12px 50px rgba(0,0,0,0.5), var(--glow); }
69
- .card:hover::before { left: 0; }
70
- .card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; }
71
-
72
- /* Buttons */
73
- .btn { padding: 10px 24px; border-radius: 10px; font-size: 18px; font-weight: 500; transition: all 0.2s ease; display: inline-flex; align-items: center; gap: 8px; height: 44px; line-height: 22px; position: relative; overflow: hidden; }
74
- .btn-primary { background: var(--gradient-btn); background-size: 200% auto; color: white; box-shadow: var(--glow-sm); }
75
- .btn-primary:hover { background-position: right center; transform: translateY(-2px); box-shadow: var(--glow); }
76
- .btn-secondary { background: var(--bg-card); color: var(--text); border: 1px solid var(--border); backdrop-filter: blur(10px); }
77
- .btn-secondary:hover { border-color: var(--accent); box-shadow: var(--glow-sm); transform: translateY(-1px); }
78
- .btn-danger { background: rgba(239,68,68,0.1); color: var(--red); }
79
- .btn-danger:hover { background: rgba(239,68,68,0.2); }
80
- .btn-sm { padding: 8px 16px; font-size: 18px; height: 38px; }
81
- .btn-lg { padding: 16px 32px; font-size: 24px; height: 56px; }
82
-
83
- /* Form elements */
84
- .input { width: 100%; padding: 10px 16px; height: 48px; background: var(--bg-input); border: 1px solid var(--border); border-radius: 10px; color: var(--text); font-size: 18px; outline: none; transition: all 0.2s ease; backdrop-filter: blur(5px); }
85
- .input:focus { border-color: var(--accent); box-shadow: var(--glow-sm); }
86
- .input::placeholder { color: var(--text-dim); }
87
- .label { display: block; font-size: 18px; font-weight: 500; color: var(--text-muted); margin-bottom: 8px; letter-spacing: 0.3px; }
88
- .form-group { margin-bottom: 20px; }
89
- select.input { appearance: none; background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%23737373' viewBox='0 0 16 16'%3E%3Cpath d='M8 11L3 6h10z'/%3E%3C/svg%3E"); background-repeat: no-repeat; background-position: right 12px center; padding-right: 32px; }
90
-
91
- /* Search bar */
92
- .search-bar { position: relative; margin-bottom: 20px; }
93
- .search-bar .input { padding-left: 40px; }
94
- .search-bar::before { content: '🔍'; position: absolute; left: 14px; top: 50%; transform: translateY(-50%); font-size: 14px; }
95
-
96
- /* Tags / Chips */
97
- .chip { display: inline-block; padding: 4px 12px; border-radius: 10px; font-size: 18px; background: var(--bg-hover); color: var(--text-muted); border: 1px solid var(--border); cursor: pointer; transition: all 0.15s; }
98
- .chip:hover, .chip.active { background: var(--accent-light); color: var(--accent); border-color: var(--accent); }
99
- .chip-group { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 20px; }
100
-
101
- /* Template card */
102
- .tpl-card { cursor: pointer; }
103
- .tpl-card .tpl-icon { font-size: 40px; margin-bottom: 12px; }
104
- .tpl-card .tpl-name { font-size: 18px; font-weight: 600; margin-bottom: 4px; }
105
- .tpl-card .tpl-desc { font-size: 18px; color: var(--text-muted); margin-bottom: 12px; line-height: 1.5; }
106
- .tpl-card .tpl-tags { display: flex; gap: 6px; flex-wrap: wrap; }
107
- .tpl-card .tpl-tag { font-size: 18px; padding: 4px 10px; border-radius: 10px; background: var(--bg-hover); color: var(--text-dim); }
108
-
109
- /* Wizard */
110
- .wizard { max-width: 640px; margin: 0 auto; }
111
- .wizard-steps { display: flex; justify-content: center; gap: 8px; margin-bottom: 32px; }
112
- .wizard-step { display: flex; align-items: center; gap: 8px; color: var(--text-dim); font-size: 18px; }
113
- .wizard-step.active { color: var(--accent); }
114
- .wizard-step.done { color: var(--green); }
115
- .wizard-step .step-num { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 13px; font-weight: 600; border: 2px solid var(--border); }
116
- .wizard-step.active .step-num { border-color: var(--accent); background: var(--accent-light); }
117
- .wizard-step.done .step-num { border-color: var(--green); background: rgba(34,197,94,0.1); }
118
- .wizard-step .step-line { width: 40px; height: 2px; background: var(--border); }
119
- .wizard-panel { display: none; }
120
- .wizard-panel.active { display: block; }
121
-
122
- /* Agent card on dashboard */
123
- .agent-card { cursor: pointer; position: relative; }
124
- .agent-card .agent-icon { font-size: 36px; margin-bottom: 12px; }
125
- .agent-card .agent-name { font-size: 16px; font-weight: 600; margin-bottom: 4px; }
126
- .agent-card .agent-template { font-size: 12px; color: var(--text-dim); margin-bottom: 8px; }
127
- .agent-card .agent-stats { display: flex; gap: 16px; font-size: 12px; color: var(--text-muted); }
128
- .agent-card .agent-actions { position: absolute; top: 16px; right: 16px; display: flex; gap: 4px; opacity: 0; transition: opacity 0.15s; }
129
- .agent-card:hover .agent-actions { opacity: 1; }
130
- .agent-card .agent-actions button { background: var(--bg); border: 1px solid var(--border); border-radius: 6px; padding: 4px 8px; font-size: 12px; color: var(--text-muted); }
131
- .agent-card .agent-actions button:hover { color: var(--text); border-color: #444; }
132
-
133
- /* Chat UI */
134
- .chat-container { display: none; flex-direction: column; height: calc(100vh - 0px); }
135
- .chat-container.active { display: flex; }
136
- .chat-header { padding: 16px 24px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 12px; background: var(--bg-card); }
137
- .chat-header .chat-icon { font-size: 28px; }
138
- .chat-header .chat-name { font-size: 16px; font-weight: 600; }
139
- .chat-header .chat-status { font-size: 12px; color: var(--text-muted); }
140
- .chat-header .chat-back { background: none; border: none; color: var(--text-muted); font-size: 18px; cursor: pointer; margin-right: 4px; }
141
- .chat-messages { flex: 1; overflow-y: auto; padding: 24px; display: flex; flex-direction: column; gap: 16px; }
142
- .msg { max-width: 75%; display: flex; flex-direction: column; gap: 4px; }
143
- .msg.user { align-self: flex-end; }
144
- .msg.assistant { align-self: flex-start; }
145
- .msg-bubble { padding: 12px 16px; border-radius: 6px; font-size: 14px; line-height: 1.6; white-space: pre-wrap; }
146
- .msg.user .msg-bubble { background: #238636; color: white; border-bottom-right-radius: 4px; }
147
- .msg.assistant .msg-bubble { background: var(--bg-card); border: 1px solid var(--border); border-bottom-left-radius: 4px; }
148
- .msg-time { font-size: 11px; color: var(--text-dim); }
149
- .msg.user .msg-time { text-align: right; }
150
- .typing-indicator { display: none; align-self: flex-start; }
151
- .typing-indicator.show { display: flex; }
152
- .typing-indicator .dots { display: flex; gap: 4px; padding: 12px 16px; background: var(--bg-card); border: 1px solid var(--border); border-radius: 16px; }
153
- .typing-indicator .dot { width: 8px; height: 8px; border-radius: 50%; background: var(--text-dim); animation: bounce 1.4s infinite ease-in-out; }
154
- .typing-indicator .dot:nth-child(2) { animation-delay: 0.2s; }
155
- .typing-indicator .dot:nth-child(3) { animation-delay: 0.4s; }
156
- @keyframes bounce { 0%,80%,100% { transform: scale(0.6); } 40% { transform: scale(1); } }
157
- .chat-input-bar { padding: 16px 24px; border-top: 1px solid var(--border); background: var(--bg-card); display: flex; gap: 12px; align-items: center; }
158
- .chat-input-bar .input { flex: 1; border-radius: 6px; padding: 5px 12px; height: 32px; }
159
- .chat-input-bar .btn { border-radius: 6px; padding: 6px 16px; }
160
-
161
- /* Memory timeline */
162
- .timeline { position: relative; padding-left: 24px; }
163
- .timeline::before { content: ''; position: absolute; left: 8px; top: 0; bottom: 0; width: 2px; background: var(--border); }
164
- .timeline-item { position: relative; margin-bottom: 24px; }
165
- .timeline-item::before { content: ''; position: absolute; left: -20px; top: 4px; width: 12px; height: 12px; border-radius: 50%; background: var(--accent); border: 2px solid var(--bg); }
166
- .timeline-date { font-size: 12px; color: var(--text-dim); margin-bottom: 4px; }
167
- .timeline-content { font-size: 14px; color: var(--text-muted); line-height: 1.5; }
168
-
169
- /* Empty state */
170
- .empty-state { text-align: center; padding: 60px 20px; color: var(--text-muted); }
171
- .empty-state .empty-icon { font-size: 48px; margin-bottom: 16px; }
172
- .empty-state .empty-title { font-size: 18px; font-weight: 600; color: var(--text); margin-bottom: 8px; }
173
- .empty-state .empty-desc { font-size: 14px; margin-bottom: 24px; }
174
-
175
- /* Confirm dialog */
176
- .dialog-overlay { display: none; position: fixed; inset: 0; background: rgba(5,5,26,0.85); backdrop-filter: blur(12px); z-index: 200; align-items: center; justify-content: center; }
177
- .dialog-overlay.show { display: flex; }
178
- .dialog { background: rgba(15,15,45,0.95); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 32px; max-width: 420px; width: 90%; backdrop-filter: blur(30px); box-shadow: 0 25px 80px rgba(0,0,0,0.6), var(--glow); }
179
- .dialog-title { font-size: 16px; font-weight: 600; margin-bottom: 8px; }
180
- .dialog-desc { font-size: 14px; color: var(--text-muted); margin-bottom: 20px; }
181
- .dialog-actions { display: flex; justify-content: flex-end; gap: 8px; }
182
-
183
- /* Settings layout */
184
- .settings-layout { display: flex; gap: 0; min-height: calc(100vh - 64px); }
185
- .settings-nav { width: 200px; background: var(--bg-card); border-right: 1px solid var(--border); padding: 16px 8px; flex-shrink: 0; }
186
- .settings-nav-item { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: var(--radius); cursor: pointer; color: var(--text-muted); font-size: 14px; margin-bottom: 2px; transition: all 0.15s; }
187
- .settings-nav-item:hover { background: var(--bg-hover); color: var(--text); }
188
- .settings-nav-item.active { background: var(--accent-light); color: var(--accent); font-weight: 500; }
189
- .settings-content { flex: 1; padding: 32px; max-width: 900px; }
190
- .settings-panel { display: none; }
191
- .settings-panel.active { display: block; }
192
- @media (max-width: 768px) {
193
- .settings-layout { flex-direction: column; }
194
- .settings-nav { width: 100%; display: flex; overflow-x: auto; padding: 8px; gap: 4px; border-right: none; border-bottom: 1px solid var(--border); }
195
- .settings-nav-item { white-space: nowrap; font-size: 13px; padding: 8px 12px; }
196
- .settings-content { padding: 16px; }
197
- }
198
-
199
- /* Tabs */
200
- .tabs { display: flex; gap: 0; margin-bottom: 24px; border-bottom: 1px solid var(--border); }
201
- .tab { padding: 10px 20px; cursor: pointer; color: var(--text-muted); font-size: 14px; border-bottom: 2px solid transparent; transition: all 0.15s; }
202
- .tab:hover { color: var(--text); }
203
- .tab.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 500; }
204
- .tab-panel { display: none; }
205
- .tab-panel.active { display: block; }
206
-
207
- /* Status dot */
208
- .status-dot { width: 10px; height: 10px; border-radius: 50%; display: inline-block; animation: pulse 2s ease-in-out infinite; }
209
- .status-dot.green { background: var(--green); box-shadow: 0 0 12px var(--green), 0 0 4px var(--green); }
210
- .status-dot.red { background: var(--red); box-shadow: 0 0 12px var(--red), 0 0 4px var(--red); }
211
- .status-dot.yellow { background: var(--yellow); box-shadow: 0 0 12px var(--yellow), 0 0 4px var(--yellow); }
212
- @keyframes pulse { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: 0.6; transform: scale(0.85); } }
213
-
214
- /* Channel card */
215
- .channel-card { display: flex; align-items: center; gap: 16px; cursor: pointer; }
216
- .channel-card .ch-icon { font-size: 28px; }
217
- .channel-card .ch-info { flex: 1; }
218
- .channel-card .ch-name { font-size: 15px; font-weight: 600; }
219
- .channel-card .ch-status { font-size: 12px; color: var(--text-muted); display: flex; align-items: center; gap: 6px; }
220
-
221
- /* Stat card */
222
- .stat-card { text-align: center; }
223
- .stat-value { font-size: 28px; font-weight: 700; color: var(--accent); }
224
- .stat-label { font-size: 13px; color: var(--text-muted); margin-top: 4px; }
225
-
226
- /* Log viewer */
227
- .log-viewer { background: #0d1117; border: 1px solid var(--border); border-radius: var(--radius); padding: 16px; font-family: var(--mono); font-size: 12px; color: var(--text-muted); max-height: 400px; overflow-y: auto; white-space: pre-wrap; line-height: 1.6; }
228
-
229
- /* Module iframe */
230
- .module-frame-container { position: relative; border-radius: var(--radius-lg); overflow: hidden; border: 1px solid var(--border); }
231
- .module-frame-container iframe { width: 100%; height: 600px; border: none; background: var(--bg); }
232
- .module-frame-fallback { text-align: center; padding: 48px 24px; }
233
- .module-frame-fallback .mf-icon { font-size: 48px; margin-bottom: 16px; }
234
-
235
- /* Ollama tutorial */
236
- .tutorial-steps { counter-reset: step; }
237
- .tutorial-step { display: flex; gap: 16px; margin-bottom: 20px; align-items: flex-start; }
238
- .tutorial-step::before { counter-increment: step; content: counter(step); width: 32px; height: 32px; border-radius: 50%; background: var(--accent); color: white; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 14px; flex-shrink: 0; }
239
- .tutorial-step-content { flex: 1; }
240
- .tutorial-step-content h4 { font-size: 15px; margin-bottom: 4px; }
241
- .tutorial-step-content p { font-size: 13px; color: var(--text-muted); line-height: 1.5; }
242
- .tutorial-step-content code { background: var(--bg-hover); padding: 2px 8px; border-radius: 4px; font-family: var(--mono); font-size: 13px; }
243
-
244
- /* Provider card */
245
- .provider-card { cursor: pointer; transition: all 0.15s; }
246
- .provider-card:hover { border-color: var(--accent); }
247
- .provider-card.configured { border-color: var(--green); }
248
- .provider-card .pv-header { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
249
- .provider-card .pv-name { font-size: 15px; font-weight: 600; }
250
- .provider-card .pv-status { font-size: 12px; }
251
-
252
- /* Bar chart */
253
- .bar-chart { display: flex; align-items: flex-end; gap: 4px; height: 120px; padding-top: 8px; }
254
- .bar-chart .bar { flex: 1; background: var(--accent); border-radius: 3px 3px 0 0; min-height: 4px; transition: height 0.3s; position: relative; }
255
- .bar-chart .bar:hover { opacity: 0.8; }
256
- .bar-chart .bar-label { position: absolute; bottom: -20px; left: 50%; transform: translateX(-50%); font-size: 10px; color: var(--text-dim); white-space: nowrap; }
257
-
258
- /* Scrollbar */
259
- ::-webkit-scrollbar { width: 6px; }
260
- ::-webkit-scrollbar-track { background: transparent; }
261
- ::-webkit-scrollbar-thumb { background: #30363d; border-radius: 3px; }
262
-
263
- /* Settings layout */
264
- .settings-layout { display: flex; gap: 24px; align-items: flex-start; }
265
- .settings-subnav { width: 190px; flex-shrink: 0; }
266
- .snav-item { display: flex; align-items: center; gap: 10px; padding: 10px 12px; border-radius: var(--radius); cursor: pointer; color: var(--text-muted); transition: all 0.15s; font-size: 14px; margin-bottom: 2px; }
267
- .snav-item:hover { background: var(--bg-hover); color: var(--text); }
268
- .snav-item.active { background: var(--accent-light); color: var(--accent); font-weight: 500; }
269
- .settings-content { flex: 1; min-width: 0; }
270
- .settings-section { display: none; }
271
- .settings-section.active { display: block; }
272
- .tabs { display: flex; border-bottom: 1px solid var(--border); margin-bottom: 24px; }
273
- .tab { padding: 10px 20px; font-size: 14px; font-weight: 500; cursor: pointer; color: var(--text-muted); border-bottom: 2px solid transparent; margin-bottom: -1px; transition: all 0.15s; }
274
- .tab:hover { color: var(--text); }
275
- .tab.active { color: var(--accent); border-bottom-color: var(--accent); }
276
- .tab-panel { display: none; }
277
- .tab-panel.active { display: block; }
278
- .status-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; vertical-align: middle; flex-shrink: 0; }
279
- .status-dot.green { background: var(--green); }
280
- .status-dot.red { background: var(--red); }
281
- .status-dot.yellow { background: var(--yellow); animation: sdpulse 1.5s ease-in-out infinite; }
282
- @keyframes sdpulse { 0%,100% { opacity: 1; } 50% { opacity: 0.4; } }
283
- .channel-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
284
- .channel-card { cursor: pointer; text-align: center; padding: 20px 12px; }
285
- .channel-card:hover { border-color: var(--accent); }
286
- .channel-card.connected { border-color: var(--green); }
287
- .log-viewer { background: #0d1117; border: 1px solid var(--border); border-radius: var(--radius); padding: 12px 16px; font-family: var(--mono); font-size: 12px; line-height: 1.7; overflow-y: auto; max-height: 280px; color: #86efac; white-space: pre-wrap; word-break: break-all; }
288
- .bar-chart-wrap { display: flex; align-items: flex-end; gap: 6px; height: 80px; }
289
- .bar-col { flex: 1; display: flex; flex-direction: column; align-items: center; gap: 3px; height: 100%; justify-content: flex-end; }
290
- .bar-fill { width: 100%; background: var(--accent); border-radius: 3px 3px 0 0; min-height: 2px; transition: height 0.4s ease; }
291
- .bar-lbl { font-size: 10px; color: var(--text-dim); }
292
- .iframe-wrap { border: 1px solid var(--border); border-radius: var(--radius-lg); overflow: hidden; }
293
- .iframe-wrap iframe { width: 100%; height: 580px; border: none; display: block; background: var(--bg); }
294
- .help-text { font-size: 12px; color: var(--text-dim); margin-top: 4px; }
295
- @media (max-width: 768px) {
296
- .settings-layout { flex-direction: column; }
297
- .settings-subnav { width: 100%; display: flex; flex-wrap: wrap; gap: 4px; }
298
- .snav-item { padding: 8px 10px; font-size: 12px; flex-direction: column; gap: 4px; text-align: center; min-width: 70px; }
299
- }
300
-
301
- /* Sidebar restructure */
302
- .sidebar-section-title { font-size: 18px; letter-spacing: 0.3px; color: var(--text-dim); margin: 24px 16px 10px; font-weight: 600; }
303
- .sidebar-divider { height: 1px; background: var(--border); margin: 8px 12px; }
304
- .pattern-card.active { border-color: var(--accent); box-shadow: var(--glow-sm); }
305
- .agent-list-container { overflow-y: auto; flex: 1; min-height: 0; }
306
- .agent-list-item {
307
- display: flex; align-items: center; gap: 10px; padding: 10px 16px; border-radius: 12px;
308
- cursor: pointer; color: var(--text-muted); transition: all 0.2s ease; font-size: 18px; margin-bottom: 2px; position: relative;
309
- }
310
- .agent-list-item:hover { background: var(--bg-hover); color: var(--text); transform: translateX(4px); }
311
- .agent-list-item.active { background: var(--accent-light); color: #fff; font-weight: 600; box-shadow: var(--glow-sm); border: 1px solid var(--border); }
312
- .agent-list-item .agent-icon { width: 24px; text-align: center; font-size: 16px; }
313
- .agent-list-item .agent-name { flex: 1; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
314
- .agent-list-item .status-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
315
- .agent-list-item .status-dot.online { background: var(--green); box-shadow: 0 0 6px var(--green); }
316
- .agent-list-item .status-dot.offline { background: var(--text-dim); }
317
- .agent-list-item .status-dot.error { background: var(--red); box-shadow: 0 0 6px var(--red); }
318
- .sidebar-bottom { margin-top: auto; flex-shrink: 0; }
319
- .sidebar-nav { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; }
320
-
321
- /* Agent Detail Page */
322
- .agent-detail-header { display: flex; justify-content: space-between; align-items: center; padding: 20px 28px; border-bottom: 1px solid var(--border); }
323
- .agent-detail-info { display: flex; align-items: center; gap: 12px; }
324
- .agent-detail-icon { font-size: 28px; }
325
- .agent-detail-name { font-size: 20px; font-weight: 700; margin: 0; }
326
- .agent-detail-toggle { background: var(--bg-card); border: 1px solid var(--border); border-radius: 10px; width: 40px; height: 40px; font-size: 18px; cursor: pointer; color: var(--text-muted); transition: all 0.2s; display: flex; align-items: center; justify-content: center; }
327
- .agent-detail-toggle:hover { background: var(--bg-hover); color: var(--text); border-color: var(--accent); }
328
- .agent-detail-toggle.active { background: var(--accent-light); color: var(--accent); border-color: var(--accent); }
329
- #page-agent-detail { display: none; flex-direction: column; height: 100vh; }
330
- #page-agent-detail.active { display: flex; }
331
- .agent-chat-view { display: flex; flex-direction: column; flex: 1; min-height: 0; }
332
- .agent-chat-messages { flex: 1; overflow-y: auto; padding: 24px 28px; display: flex; flex-direction: column; gap: 16px; }
333
- .agent-chat-welcome { display: flex; flex-direction: column; align-items: center; justify-content: center; flex: 1; color: var(--text-dim); }
334
- .agent-chat-input-bar { display: flex; gap: 12px; padding: 16px 28px; border-top: 1px solid var(--border); background: rgba(5,5,30,0.5); backdrop-filter: blur(10px); }
335
- .agent-chat-input { flex: 1; background: var(--bg-input); border: 1px solid var(--border); border-radius: 12px; padding: 12px 16px; color: var(--text); font-size: 14px; resize: none; outline: none; font-family: var(--font); max-height: 120px; }
336
- .agent-chat-input:focus { border-color: var(--accent); }
337
- .agent-chat-send { padding: 12px 20px; border-radius: 12px; font-weight: 600; flex-shrink: 0; }
338
- .agent-chat-msg { max-width: 75%; padding: 12px 16px; border-radius: 16px; font-size: 14px; line-height: 1.6; word-break: break-word; }
339
- .agent-chat-msg.user { align-self: flex-end; background: var(--accent); color: #fff; border-bottom-right-radius: 4px; }
340
- .agent-chat-msg.assistant { align-self: flex-start; background: var(--bg-card); border: 1px solid var(--border); border-bottom-left-radius: 4px; }
341
- .agent-settings-view { flex: 1; display: flex; flex-direction: column; min-height: 0; }
342
- .agent-settings-tabs { display: flex; gap: 4px; padding: 16px 28px 0; border-bottom: 1px solid var(--border); overflow-x: auto; flex-shrink: 0; }
343
- .agent-tab { padding: 10px 16px; border-radius: 10px 10px 0 0; cursor: pointer; color: var(--text-muted); font-size: 13px; white-space: nowrap; transition: all 0.15s; border-bottom: 2px solid transparent; }
344
- .agent-tab:hover { color: var(--text); background: var(--bg-hover); }
345
- .agent-tab.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }
346
- .agent-settings-content { flex: 1; overflow-y: auto; padding: 24px 28px; }
347
- .agent-tab-panel { display: none; }
348
- .agent-tab-panel.active { display: block; }
349
- .agent-tab-panel h3 { margin-bottom: 12px; font-size: 18px; }
350
- </style>
351
- </head>
352
- <body>
353
- <div class="app">
354
- <!-- Sidebar -->
355
- <div class="sidebar-overlay" onclick="toggleSidebar(false)"></div>
356
- <nav class="sidebar">
357
- <div class="sidebar-logo" onclick="navigate('dashboard')" style="cursor:pointer;">⚡ <span>OPC Studio</span></div>
358
- <div class="sidebar-nav">
359
- <!-- Section 1: My Agents -->
360
- <div class="sidebar-section-title">🤖 我的 Agent</div>
361
- <div class="agent-list-container" id="sidebar-agent-list">
362
- <div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">加载中...</div>
363
- </div>
364
-
365
- <!-- Section 2: Collaboration Groups -->
366
- <div class="sidebar-divider"></div>
367
- <div class="sidebar-section-title">👥 协作群组</div>
368
- <div class="agent-list-container" id="groups-list" style="max-height:120px;">
369
- <!-- dynamically loaded -->
370
- </div>
371
- <div class="nav-item" data-page="create-group" onclick="navigate('create-group')">
372
- <span class="icon">➕</span> 新建群组
373
- </div>
374
-
375
- <!-- Section 3: Create Agent -->
376
- <div class="sidebar-divider"></div>
377
- <div class="nav-item" data-page="create" onclick="navigate('create')">
378
- <span class="icon">➕</span> 新建 Agent
379
- </div>
380
-
381
- <!-- Section 3: Global Config -->
382
- <div class="sidebar-bottom">
383
- <div class="sidebar-divider"></div>
384
- <div class="sidebar-section-title">⚙️ 全局配置</div>
385
- <div class="nav-item" data-page="global-runtime" onclick="navigate('global-runtime')">
386
- <span class="icon">🚀</span> Runtime
387
- </div>
388
- <div class="nav-item" data-page="global-models" onclick="navigate('global-models')">
389
- <span class="icon">🧠</span> Models
390
- </div>
391
- <div class="nav-item" data-page="global-channels" onclick="navigate('global-channels')">
392
- <span class="icon">📡</span> Channels
393
- </div>
394
- <div class="nav-item" data-page="global-memory" onclick="navigate('global-memory')">
395
- <span class="icon">💾</span> Memory
396
- </div>
397
- <div class="nav-item" data-page="global-templates" onclick="navigate('global-templates')">
398
- <span class="icon">📋</span> Templates
399
- </div>
400
- </div>
401
- </div>
402
- <div style="padding: 12px; border-top: 1px solid var(--border); font-size: 12px; color: var(--text-dim);">
403
- OPC Studio v2.0
404
- </div>
405
- </nav>
406
-
407
- <!-- Mobile Header -->
408
- <div class="mobile-header">
409
- <button onclick="toggleSidebar(true)">☰</button>
410
- <span style="font-weight:600;">⚡ OPC Studio</span>
411
- <span></span>
412
- </div>
413
-
414
- <!-- Main Content -->
415
- <div class="main">
416
- <!-- Dashboard Page -->
417
- <div class="page active" id="page-dashboard">
418
- <div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px;margin-bottom:24px;">
419
- <div>
420
- <h1 class="page-title">My Agents</h1>
421
- <p class="page-subtitle">Manage and chat with your AI agents</p>
422
- </div>
423
- <button class="btn btn-primary" onclick="navigate('create')">✨ Create New Agent</button>
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>
433
- <!-- Health Status Section -->
434
- <div id="health-section" style="margin-bottom:24px;"></div>
435
- <div id="agents-list" class="card-grid"></div>
436
- <div id="agents-empty" class="empty-state" style="display:none;">
437
- <div class="empty-icon">🤖</div>
438
- <div class="empty-title">No agents yet</div>
439
- <div class="empty-desc">Create your first AI agent in just 3 steps — no coding required!</div>
440
- <button class="btn btn-primary btn-lg" onclick="navigate('create')">✨ Create My First Agent</button>
441
- </div>
442
- </div>
443
-
444
- <!-- Agent Detail Page -->
445
- <div class="page" id="page-agent-detail">
446
- <div class="agent-detail-header">
447
- <div class="agent-detail-info">
448
- <span class="agent-detail-icon" id="agent-detail-icon">🤖</span>
449
- <h1 class="agent-detail-name" id="agent-detail-name">Agent</h1>
450
- <span class="status-dot online" id="agent-detail-status"></span>
451
- </div>
452
- <button class="agent-detail-toggle" id="agent-detail-toggle" onclick="toggleAgentSettings()">⚙️</button>
453
- </div>
454
-
455
- <!-- Chat View (default) -->
456
- <div class="agent-chat-view" id="agent-chat-view">
457
- <div class="agent-chat-messages" id="agent-chat-messages">
458
- <div class="agent-chat-welcome" id="agent-chat-welcome">
459
- <div style="font-size: 48px; margin-bottom: 16px;">💬</div>
460
- <div style="font-size: 18px; font-weight: 600; margin-bottom: 8px;">开始对话</div>
461
- <div style="color: var(--text-muted); font-size: 14px;">向你的 Agent 发送第一条消息</div>
462
- </div>
463
- </div>
464
- <div class="agent-chat-input-bar">
465
- <textarea class="agent-chat-input" id="agent-chat-input" placeholder="输入消息..." rows="1" onkeydown="handleAgentChatKey(event)"></textarea>
466
- <button class="btn btn-primary agent-chat-send" onclick="sendAgentChat()">发送</button>
467
- </div>
468
- </div>
469
-
470
- <!-- Settings View (hidden) -->
471
- <div class="agent-settings-view" id="agent-settings-view" style="display:none;">
472
- <div class="agent-settings-tabs">
473
- <div class="agent-tab active" data-atab="role" onclick="switchAgentTab('role')">👤 角色</div>
474
- <div class="agent-tab" data-atab="models" onclick="switchAgentTab('models')">🤖 模型</div>
475
- <div class="agent-tab" data-atab="channels" onclick="switchAgentTab('channels')">📡 渠道</div>
476
- <div class="agent-tab" data-atab="memory" onclick="switchAgentTab('memory')">🧠 记忆</div>
477
- <div class="agent-tab" data-atab="skills" onclick="switchAgentTab('skills')">🧩 技能</div>
478
- <div class="agent-tab" data-atab="schedules" onclick="switchAgentTab('schedules')">⏰ 定时</div>
479
- <div class="agent-tab" data-atab="usage" onclick="switchAgentTab('usage')">📊 统计</div>
480
- </div>
481
- <div class="agent-settings-content" id="agent-settings-content">
482
- <div class="agent-tab-panel active" id="atab-role">
483
- <h3>角色配置</h3>
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>
489
- </div>
490
- <div class="agent-tab-panel" id="atab-models">
491
- <h3>模型配置</h3>
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>
502
- </div>
503
- <div class="agent-tab-panel" id="atab-channels">
504
- <h3>渠道配置</h3>
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>
508
- </div>
509
- <div class="agent-tab-panel" id="atab-memory">
510
- <h3>记忆管理</h3>
511
- <div id="atab-memory-list" style="font-size:18px;color:var(--text-muted);">加载中...</div>
512
- </div>
513
- <div class="agent-tab-panel" id="atab-skills">
514
- <h3>技能配置</h3>
515
- <div id="atab-skills-list" style="font-size:18px;color:var(--text-muted);">加载中...</div>
516
- </div>
517
- <div class="agent-tab-panel" id="atab-schedules">
518
- <h3>定时任务</h3>
519
- <div id="atab-schedules-list" style="font-size:18px;color:var(--text-muted);">加载中...</div>
520
- </div>
521
- <div class="agent-tab-panel" id="atab-usage">
522
- <h3>用量统计</h3>
523
- <div id="atab-usage-content" style="font-size:18px;color:var(--text-muted);">加载中...</div>
524
- </div>
525
- </div>
526
- </div>
527
- </div>
528
-
529
- <!-- Templates Page -->
530
- <!-- Create Group Page -->
531
- <div class="page" id="page-create-group">
532
- <h1 class="page-title">新建协作群组</h1>
533
- <p class="page-subtitle">选择协作模式,拉入 Agent,开始多角色协作</p>
534
- <div class="card" style="max-width:600px;">
535
- <div class="label">群组名称</div>
536
- <input class="input" id="group-name" placeholder="例如:产品讨论组">
537
- <div class="label" style="margin-top:16px;">协作模式</div>
538
- <div class="card-grid" style="grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap:16px; margin-bottom:16px;">
539
- <div class="card pattern-card active" onclick="selectPattern('debate')" id="pat-debate" style="padding:20px;cursor:pointer;">
540
- <div style="font-size:28px;margin-bottom:8px;">⚔️</div>
541
- <div style="font-size:15px;font-weight:700;margin-bottom:6px;">Debate 辩论模式</div>
542
- <div style="font-size:13px;color:var(--text-muted);line-height:1.5;margin-bottom:10px;">多个 Agent 围绕一个话题正反方辩论,最终由裁判 Agent 综合各方观点给出结论。</div>
543
- <div style="font-size:12px;color:var(--accent);">💡 适合:决策分析、方案对比、风险评估</div>
544
- <div style="font-size:12px;color:var(--green);margin-top:4px;">📤 产出:多角度分析报告 + 最终建议</div>
545
- </div>
546
- <div class="card pattern-card" onclick="selectPattern('voting')" id="pat-voting" style="padding:20px;cursor:pointer;">
547
- <div style="font-size:28px;margin-bottom:8px;">🗳️</div>
548
- <div style="font-size:15px;font-weight:700;margin-bottom:6px;">Voting 投票模式</div>
549
- <div style="font-size:13px;color:var(--text-muted);line-height:1.5;margin-bottom:10px;">每个 Agent 独立给出判断和投票,汇总统计后输出多数意见和少数意见。</div>
550
- <div style="font-size:12px;color:var(--accent);">💡 适合:质量评审、内容审核、多人打分</div>
551
- <div style="font-size:12px;color:var(--green);margin-top:4px;">📤 产出:投票结果 + 各方理由汇总</div>
552
- </div>
553
- <div class="card pattern-card" onclick="selectPattern('pipeline')" id="pat-pipeline" style="padding:20px;cursor:pointer;">
554
- <div style="font-size:28px;margin-bottom:8px;">🔗</div>
555
- <div style="font-size:15px;font-weight:700;margin-bottom:6px;">Pipeline 流水线模式</div>
556
- <div style="font-size:13px;color:var(--text-muted);line-height:1.5;margin-bottom:10px;">Agent A 的输出自动传给 Agent B 作为输入,逐步处理和加工,像工厂流水线。</div>
557
- <div style="font-size:12px;color:var(--accent);">💡 适合:内容创作、数据处理、翻译校对</div>
558
- <div style="font-size:12px;color:var(--green);margin-top:4px;">📤 产出:经过多轮加工的最终成果</div>
559
- </div>
560
- <div class="card pattern-card" onclick="selectPattern('hierarchy')" id="pat-hierarchy" style="padding:20px;cursor:pointer;">
561
- <div style="font-size:28px;margin-bottom:8px;">🏛️</div>
562
- <div style="font-size:15px;font-weight:700;margin-bottom:6px;">Hierarchy 层级模式</div>
563
- <div style="font-size:13px;color:var(--text-muted);line-height:1.5;margin-bottom:10px;">一个主管 Agent 拆解任务分配给下属 Agent,收集结果后汇总报告。</div>
564
- <div style="font-size:12px;color:var(--accent);">💡 适合:项目管理、调研收集、并行执行</div>
565
- <div style="font-size:12px;color:var(--green);margin-top:4px;">📤 产出:汇总报告 + 各子任务结果</div>
566
- </div>
567
- <div class="card pattern-card" onclick="selectPattern('shared-memory')" id="pat-shared-memory" style="padding:20px;cursor:pointer;">
568
- <div style="font-size:28px;margin-bottom:8px;">🧠</div>
569
- <div style="font-size:15px;font-weight:700;margin-bottom:6px;">Shared Memory 共享记忆模式</div>
570
- <div style="font-size:13px;color:var(--text-muted);line-height:1.5;margin-bottom:10px;">所有 Agent 共享一个知识空间,各自贡献信息,互相读取和补充,持续积累。</div>
571
- <div style="font-size:12px;color:var(--accent);">💡 适合:知识构建、团队学习、长期项目</div>
572
- <div style="font-size:12px;color:var(--green);margin-top:4px;">📤 产出:持续进化的共享知识库</div>
573
- </div>
574
- </div>
575
- <div class="label">选择 Agent 成员</div>
576
- <div id="group-agent-select" style="margin-bottom:16px;">
577
- <p style="color:var(--text-muted);font-size:13px;">请先创建 Agent,再拉入群组</p>
578
- </div>
579
- <button class="btn btn-primary" onclick="createGroup()">✨ 创建群组</button>
580
- </div>
581
- </div>
582
-
583
- <div class="page" id="page-templates">
584
- <h1 class="page-title">Template Market</h1>
585
- <p class="page-subtitle">Browse 100+ ready-to-use agent templates across 19 industries</p>
586
- <div class="search-bar">
587
- <input class="input" id="tpl-search" placeholder="Search templates..." oninput="filterTemplates()">
588
- </div>
589
- <div class="chip-group" id="industry-chips"></div>
590
- <div class="card-grid" id="templates-grid"></div>
591
- </div>
592
-
593
- <!-- Skills Marketplace Page -->
594
- <div class="page" id="page-skills">
595
- <h1 class="page-title">🧩 Skill Market</h1>
596
- <p class="page-subtitle">One-click install new capabilities for your agent — no coding required</p>
597
- <div class="search-bar" style="display:flex;gap:12px;align-items:center;flex-wrap:wrap;">
598
- <input class="input" id="skills-search" placeholder="Search skills..." oninput="filterSkills()" style="flex:1;min-width:200px;">
599
- <div class="chip-group" id="skill-category-chips" style="margin:0;"></div>
600
- </div>
601
- <div class="card-grid" id="skills-grid" style="margin-top:16px;"></div>
602
- </div>
603
-
604
- <!-- Create Wizard Page -->
605
- <div class="page" id="page-create">
606
- <div class="wizard">
607
- <div class="wizard-steps">
608
- <div class="wizard-step active" id="ws-1"><div class="step-num">1</div><span>Choose Template</span></div>
609
- <div class="wizard-step" id="ws-2"><div class="step-line"></div><div class="step-num">2</div><span>Configure</span></div>
610
- <div class="wizard-step" id="ws-3"><div class="step-line"></div><div class="step-num">3</div><span>Confirm</span></div>
611
- </div>
612
-
613
- <!-- Step 1: Choose Template -->
614
- <div class="wizard-panel active" id="wp-1">
615
- <h2 style="font-size:20px;margin-bottom:8px;">Choose a Template</h2>
616
- <p style="color:var(--text-muted);font-size:14px;margin-bottom:20px;">Pick a role for your agent. Don't worry, you can customize it later.</p>
617
- <div class="search-bar">
618
- <input class="input" id="wizard-tpl-search" placeholder="Search templates..." oninput="filterWizardTemplates()">
619
- </div>
620
- <div class="chip-group" id="wizard-industry-chips"></div>
621
- <div class="card-grid" id="wizard-tpl-grid" style="max-height:400px;overflow-y:auto;"></div>
622
- </div>
623
-
624
- <!-- Step 2: Configure -->
625
- <div class="wizard-panel" id="wp-2">
626
- <h2 style="font-size:20px;margin-bottom:8px;">Configure Your Agent</h2>
627
- <p style="color:var(--text-muted);font-size:14px;margin-bottom:24px;">Give your agent a name and tell it about your business.</p>
628
- <div class="form-group">
629
- <label class="label">Agent Name *</label>
630
- <input class="input" id="agent-name" placeholder="e.g. My Sales Coach">
631
- </div>
632
- <div class="form-group">
633
- <label class="label">Company / Business Description</label>
634
- <textarea class="input" id="agent-desc" rows="3" placeholder="Brief description of your business so the agent can better help you..."></textarea>
635
- </div>
636
- <div class="form-group">
637
- <label class="label">AI Model</label>
638
- <select class="input" id="agent-model">
639
- <option value="gpt-4o-mini">GPT-4o Mini (Fast & Affordable) ⭐ Recommended</option>
640
- <option value="gpt-4o">GPT-4o (Most Capable)</option>
641
- <option value="claude-sonnet-4">Claude Sonnet (Balanced)</option>
642
- <option value="claude-haiku">Claude Haiku (Fast)</option>
643
- <option value="gemini-2.0-flash">Gemini 2.0 Flash (Google)</option>
644
- <option value="deepseek-v3">DeepSeek V3 (Open Source)</option>
645
- </select>
646
- </div>
647
- <div class="form-group">
648
- <label class="label">Language Preference</label>
649
- <select class="input" id="agent-lang">
650
- <option value="en">English</option>
651
- <option value="zh">中文</option>
652
- <option value="auto">Auto-detect</option>
653
- </select>
654
- </div>
655
- <div style="display:flex;gap:12px;justify-content:flex-end;margin-top:24px;">
656
- <button class="btn btn-secondary" onclick="wizardBack()">← Back</button>
657
- <button class="btn btn-primary" onclick="wizardNext()">Next →</button>
658
- </div>
659
- </div>
660
-
661
- <!-- Step 3: Confirm -->
662
- <div class="wizard-panel" id="wp-3">
663
- <h2 style="font-size:20px;margin-bottom:8px;">Review & Create</h2>
664
- <p style="color:var(--text-muted);font-size:14px;margin-bottom:24px;">Everything looks good? Let's bring your agent to life!</p>
665
- <div class="card" id="confirm-card" style="margin-bottom:24px;"></div>
666
- <div style="display:flex;gap:12px;justify-content:flex-end;">
667
- <button class="btn btn-secondary" onclick="wizardBack()">← Back</button>
668
- <button class="btn btn-primary btn-lg" onclick="createAgent()" id="create-btn">🚀 Create Agent</button>
669
- </div>
670
- </div>
671
- </div>
672
- </div>
673
-
674
- <!-- Chat Page (full height, no padding) -->
675
- <div class="chat-container" id="page-chat">
676
- <div class="chat-header">
677
- <button class="chat-back" onclick="navigate('dashboard')">←</button>
678
- <span class="chat-icon" id="chat-agent-icon">🤖</span>
679
- <div>
680
- <div class="chat-name" id="chat-agent-name">Agent</div>
681
- <div class="chat-status" id="chat-agent-status">Online</div>
682
- </div>
683
- <div style="margin-left:auto;display:flex;gap:8px;align-items:center;">
684
- <select id="chat-agent-select" class="input" style="width:auto;padding:6px 10px;font-size:13px;border-radius:8px;" onchange="switchChatAgent(this.value)"></select>
685
- <span id="streaming-indicator" style="display:none;font-size:16px;" title="Thinking...">⏳</span>
686
- <button class="btn btn-sm btn-secondary" onclick="clearChat()">🗑 Clear</button>
687
- <button class="btn btn-sm btn-secondary" onclick="openMemory()">🧠 Memory</button>
688
- </div>
689
- </div>
690
- <div class="chat-messages" id="chat-messages">
691
- <div class="msg assistant">
692
- <div class="msg-bubble" id="chat-welcome">Hello! How can I help you today?</div>
693
- </div>
694
- </div>
695
- <div class="typing-indicator" id="typing-indicator">
696
- <div class="dots"><div class="dot"></div><div class="dot"></div><div class="dot"></div></div>
697
- </div>
698
- <div class="chat-input-bar">
699
- <input type="file" id="doc-upload-input" style="display:none" accept=".pdf,.txt,.md,.docx,.csv,.json" onchange="handleDocUpload(this)">
700
- <button class="btn" onclick="document.getElementById('doc-upload-input').click()" title="Upload document" style="padding:12px;font-size:18px;background:transparent;border:1px solid var(--border);border-radius:24px;cursor:pointer;">📎</button>
701
- <button class="btn" id="voice-btn" onclick="toggleVoiceInput()" title="Voice input (click to start/stop)" style="padding:12px;font-size:18px;background:transparent;border:1px solid var(--border);border-radius:24px;cursor:pointer;">🎤</button>
702
- <input class="input" id="chat-input" placeholder="Type a message..." onkeydown="if(event.key==='Enter')sendMessage()">
703
- <button class="btn btn-primary" onclick="sendMessage()">Send</button>
704
- </div>
705
- </div>
706
-
707
-
708
- <!-- Memory Page -->
709
- <div class="page" id="page-memory">
710
- <div style="display:flex;align-items:center;gap:12px;margin-bottom:24px;">
711
- <button class="btn btn-secondary btn-sm" onclick="navigateToChat()">← Back to Chat</button>
712
- <div>
713
- <h1 class="page-title" style="margin-bottom:0;">🧠 Agent Memory</h1>
714
- <p class="page-subtitle" style="margin-bottom:0;">Knowledge your agent has learned over time</p>
715
- </div>
716
- </div>
717
- <div id="memory-timeline"></div>
718
- <div id="memory-empty" class="empty-state">
719
- <div class="empty-icon">🧠</div>
720
- <div class="empty-title">No memories yet</div>
721
- <div class="empty-desc">Your agent will learn and remember things as you chat with it.</div>
722
- </div>
723
- </div>
724
-
725
- <!-- Schedules Page -->
726
- <div class="page" id="page-schedules">
727
- <div style="display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px;margin-bottom:24px;">
728
- <div>
729
- <h1 class="page-title">⏰ Scheduled Tasks</h1>
730
- <p class="page-subtitle">Automate recurring agent tasks with cron schedules</p>
731
- </div>
732
- <button class="btn btn-primary" onclick="showScheduleForm()">+ New Task</button>
733
- </div>
734
-
735
- <!-- New/Edit Form (hidden by default) -->
736
- <div id="schedule-form" class="card" style="display:none;margin-bottom:24px;">
737
- <h3 style="font-size:16px;font-weight:600;margin-bottom:16px;" id="schedule-form-title">New Scheduled Task</h3>
738
- <div class="form-group">
739
- <label class="label">Task Name *</label>
740
- <input class="input" id="sched-name" placeholder="e.g. Daily News Summary">
741
- </div>
742
- <div class="form-group">
743
- <label class="label">Frequency</label>
744
- <select class="input" id="sched-frequency" onchange="onSchedFreqChange()">
745
- <option value="daily">Every Day</option>
746
- <option value="weekly">Every Week (Monday)</option>
747
- <option value="monthly">Every Month (1st)</option>
748
- <option value="custom">Custom Cron</option>
749
- </select>
750
- </div>
751
- <div class="form-group" id="sched-time-group">
752
- <label class="label">Time</label>
753
- <input class="input" type="time" id="sched-time" value="09:00">
754
- </div>
755
- <div class="form-group" id="sched-cron-group" style="display:none;">
756
- <label class="label">Cron Expression</label>
757
- <input class="input" id="sched-cron" placeholder="*/5 * * * *">
758
- <div class="help-text">Format: minute hour dayOfMonth month dayOfWeek</div>
759
- </div>
760
- <div class="form-group">
761
- <label class="label">Description</label>
762
- <textarea class="input" id="sched-desc" rows="2" placeholder="e.g. Send a news summary every morning at 8am"></textarea>
763
- </div>
764
- <div class="form-group">
765
- <label class="label">Agent</label>
766
- <select class="input" id="sched-agent"></select>
767
- </div>
768
- <div class="form-group">
769
- <label class="label">Output Channel</label>
770
- <select class="input" id="sched-channel">
771
- <option value="web">Web</option>
772
- <option value="telegram">Telegram</option>
773
- <option value="email">Email</option>
774
- </select>
775
- </div>
776
- <div style="display:flex;gap:8px;">
777
- <button class="btn btn-primary" onclick="saveSchedule()">💾 Save</button>
778
- <button class="btn btn-secondary" onclick="hideScheduleForm()">Cancel</button>
779
- </div>
780
- </div>
781
-
782
- <!-- Task List -->
783
- <div id="schedules-list"></div>
784
- <div id="schedules-empty" class="empty-state" style="display:none;">
785
- <div class="empty-icon">⏰</div>
786
- <div class="empty-title">No scheduled tasks</div>
787
- <div class="empty-desc">Create your first automated task to get started.</div>
788
- <button class="btn btn-primary" onclick="showScheduleForm()">+ Create Task</button>
789
- </div>
790
- </div>
791
- </div>
792
- </div>
793
-
794
- <!-- Channel Config Dialog -->
795
- <div class="dialog-overlay" id="channel-dialog">
796
- <div class="dialog" style="max-width:480px;width:92%;">
797
- <div class="dialog-title" id="channel-dialog-title">配置渠道</div>
798
- <div id="channel-dialog-body" style="margin-bottom:4px;"></div>
799
- <div class="dialog-actions">
800
- <button class="btn btn-secondary btn-sm" onclick="closeChannelDialog()">取消</button>
801
- <button class="btn btn-primary btn-sm" onclick="saveCurrentChannel()">💾 保存</button>
802
- </div>
803
- <!-- Settings Page -->
804
- <div class="page" id="page-settings">
805
- <div class="settings-layout">
806
- <div class="settings-nav">
807
- <div class="settings-nav-item active" data-settings="models" onclick="showSettings('models')">🤖 模型配置</div>
808
- <div class="settings-nav-item" data-settings="channels" onclick="showSettings('channels')">📡 渠道配置</div>
809
- <div class="settings-nav-item" data-settings="memory" onclick="showSettings('memory')">🧠 记忆管理</div>
810
- <div class="settings-nav-item" data-settings="role" onclick="showSettings('role')">👤 角色编辑</div>
811
- <div class="settings-nav-item" data-settings="status" onclick="showSettings('status')">📊 运行状态</div>
812
- <div class="settings-nav-item" data-settings="usage" onclick="showSettings('usage')">💰 用量统计</div>
813
- <div class="settings-nav-item" data-settings="search" onclick="showSettings('search')">🔍 搜索配置</div>
814
- </div>
815
- <div class="settings-content">
816
-
817
- <!-- Models Panel -->
818
- <div class="settings-panel active" id="sp-models">
819
- <h2 style="font-size:20px;font-weight:700;margin-bottom:4px;">🤖 模型配置</h2>
820
- <p style="color:var(--text-muted);font-size:14px;margin-bottom:20px;">选择 AI 模型,默认使用本地模型,完全免费</p>
821
-
822
- <div class="tabs">
823
- <div class="tab active" onclick="switchModelTab('local')">🏠 本地模型</div>
824
- <div class="tab" onclick="switchModelTab('cloud')">☁️ 云端 API</div>
825
- </div>
826
-
827
- <!-- Local Models Tab -->
828
- <div class="tab-panel active" id="mt-local">
829
- <div id="ollama-status" style="margin-bottom:20px;"></div>
830
- <div id="ollama-models" style="margin-bottom:20px;"></div>
831
- <div id="ollama-tutorial" style="display:none;">
832
- <div class="card" style="margin-bottom:20px;">
833
- <h3 style="font-size:16px;margin-bottom:16px;">📖 3 步安装本地模型</h3>
834
- <div class="tutorial-steps">
835
- <div class="tutorial-step"><div class="tutorial-step-content"><h4>下载 Ollama</h4><p>访问 <a href="https://ollama.com" target="_blank">ollama.com</a> 下载安装包,支持 Windows / Mac / Linux</p></div></div>
836
- <div class="tutorial-step"><div class="tutorial-step-content"><h4>安装并启动</h4><p>安装完成后,Ollama 会自动在后台运行</p></div></div>
837
- <div class="tutorial-step"><div class="tutorial-step-content"><h4>拉取推荐模型</h4><p>打开终端,运行:<br><code>ollama pull qwen2.5:7b</code><br><code>ollama pull nomic-embed-text</code></p></div></div>
838
- </div>
839
- <button class="btn btn-primary btn-sm" onclick="detectOllama()" style="margin-top:8px;">🔄 重新检测</button>
840
- </div>
841
- </div>
842
- </div>
843
-
844
- <!-- Cloud API Tab -->
845
- <div class="tab-panel" id="mt-cloud">
846
- <p style="color:var(--text-muted);font-size:13px;margin-bottom:16px;">填入 API Key 即可使用云端模型,按用量付费</p>
847
- <div class="card-grid" id="cloud-providers">
848
- <div class="card provider-card" onclick="configureProvider('openai')">
849
- <div class="pv-header"><span style="font-size:20px;">🟢</span><span class="pv-name">OpenAI</span></div>
850
- <p style="font-size:13px;color:var(--text-muted);">GPT-4o / GPT-4o-mini</p>
851
- <div class="pv-status" id="pv-openai">未配置</div>
852
- </div>
853
- <div class="card provider-card" onclick="configureProvider('deepseek')">
854
- <div class="pv-header"><span style="font-size:20px;">🔵</span><span class="pv-name">DeepSeek</span></div>
855
- <p style="font-size:13px;color:var(--text-muted);">DeepSeek V3 / R1</p>
856
- <div class="pv-status" id="pv-deepseek">未配置</div>
857
- </div>
858
- <div class="card provider-card" onclick="configureProvider('qwen')">
859
- <div class="pv-header"><span style="font-size:20px;">🟣</span><span class="pv-name">通义千问</span></div>
860
- <p style="font-size:13px;color:var(--text-muted);">Qwen-Max / Qwen-Plus</p>
861
- <div class="pv-status" id="pv-qwen">未配置</div>
862
- </div>
863
- <div class="card provider-card" onclick="configureProvider('anthropic')">
864
- <div class="pv-header"><span style="font-size:20px;">🟠</span><span class="pv-name">Anthropic</span></div>
865
- <p style="font-size:13px;color:var(--text-muted);">Claude Sonnet / Haiku</p>
866
- <div class="pv-status" id="pv-anthropic">未配置</div>
867
- </div>
868
- <div class="card provider-card" onclick="configureProvider('openrouter')">
869
- <div class="pv-header"><span style="font-size:20px;">🌐</span><span class="pv-name">OpenRouter</span></div>
870
- <p style="font-size:13px;color:var(--text-muted);">100+ 模型聚合</p>
871
- <div class="pv-status" id="pv-openrouter">未配置</div>
872
- </div>
873
- </div>
874
- </div>
875
-
876
- <!-- Model Assignment -->
877
- <div class="card" style="margin-top:24px;">
878
- <h3 style="font-size:16px;margin-bottom:16px;">🎯 模型用途分配</h3>
879
- <div class="form-group">
880
- <label class="label">聊天模型(必选)</label>
881
- <select class="input" id="cfg-chat-model" onchange="saveModelAssignment()">
882
- <option value="qwen2.5:7b">qwen2.5:7b (本地推荐) ⭐</option>
883
- </select>
884
- <p style="font-size:12px;color:var(--text-dim);margin-top:4px;">用于对话、回答问题、执行任务</p>
885
- </div>
886
- <div class="form-group" style="margin-bottom:0;">
887
- <label class="label">Embedding 模型(记忆用)</label>
888
- <select class="input" id="cfg-embed-model" onchange="saveModelAssignment()">
889
- <option value="nomic-embed-text">nomic-embed-text (本地推荐) ⭐</option>
890
- </select>
891
- <p style="font-size:12px;color:var(--text-dim);margin-top:4px;">用于记忆存储和语义搜索,一般不需要更改</p>
892
- </div>
893
- </div>
894
- </div>
895
-
896
- <!-- Channels Panel -->
897
- <div class="settings-panel" id="sp-channels">
898
- <h2 style="font-size:20px;font-weight:700;margin-bottom:4px;">📡 渠道配置</h2>
899
- <p style="color:var(--text-muted);font-size:14px;margin-bottom:20px;">连接你的聊天平台,让 Agent 在各个渠道工作</p>
900
- <div class="card-grid" id="channels-grid"></div>
901
- </div>
902
-
903
- <!-- Memory Panel (DeepBrain) -->
904
- <div class="settings-panel" id="sp-memory">
905
- <h2 style="font-size:20px;font-weight:700;margin-bottom:4px;">🧠 记忆管理</h2>
906
- <p style="color:var(--text-muted);font-size:14px;margin-bottom:20px;">管理 Agent 的知识库和记忆,由 DeepBrain 提供</p>
907
- <div id="memory-module-frame"></div>
908
- </div>
909
-
910
- <!-- Role Panel (Workstation) -->
911
- <div class="settings-panel" id="sp-role">
912
- <h2 style="font-size:20px;font-weight:700;margin-bottom:4px;">👤 角色编辑</h2>
913
- <p style="color:var(--text-muted);font-size:14px;margin-bottom:20px;">编辑 Agent 的角色设定和技能,由 Workstation 提供</p>
914
- <div id="role-module-frame"></div>
915
- </div>
916
-
917
- <!-- Status Panel -->
918
- <div class="settings-panel" id="sp-status">
919
- <h2 style="font-size:20px;font-weight:700;margin-bottom:4px;">📊 运行状态</h2>
920
- <p style="color:var(--text-muted);font-size:14px;margin-bottom:20px;">查看 Agent 和各模块的运行情况</p>
921
- <div id="status-overview" style="margin-bottom:24px;"></div>
922
- <div class="card" style="margin-bottom:16px;">
923
- <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;">
924
- <h3 style="font-size:16px;">📋 最近日志</h3>
925
- <button class="btn btn-secondary btn-sm" onclick="refreshStatus()">🔄 刷新</button>
926
- </div>
927
- <div class="log-viewer" id="status-logs">暂无日志</div>
928
- </div>
929
- </div>
930
-
931
- <!-- Usage Panel -->
932
- <div class="settings-panel" id="sp-usage">
933
- <h2 style="font-size:20px;font-weight:700;margin-bottom:4px;">💰 用量统计</h2>
934
- <p style="color:var(--text-muted);font-size:14px;margin-bottom:20px;">查看 Token 消耗和费用估算</p>
935
- <div id="usage-stats"></div>
936
- </div>
937
-
938
- <!-- Web Search Panel -->
939
- <div class="settings-panel" id="sp-search">
940
- <h2 style="font-size:20px;font-weight:700;margin-bottom:4px;">🔍 搜索配置</h2>
941
- <p style="color:var(--text-muted);font-size:14px;margin-bottom:20px;">Agent 可以自动搜索互联网回答问题,默认使用 DuckDuckGo(免费,无需配置)</p>
942
-
943
- <div class="form-group">
944
- <label class="label">搜索开关</label>
945
- <label style="display:flex;align-items:center;gap:8px;cursor:pointer;">
946
- <input type="checkbox" id="search-enabled" checked onchange="updateSearchConfig()">
947
- <span style="font-size:14px;">启用 Web 搜索</span>
948
- </label>
949
- </div>
950
-
951
- <div class="form-group">
952
- <label class="label">默认搜索引擎</label>
953
- <select class="input" id="search-engine" onchange="updateSearchConfig()" style="padding:8px 12px;">
954
- <option value="duckduckgo">🦆 DuckDuckGo(免费,默认)</option>
955
- <option value="brave">🦁 Brave Search(需 API Key)</option>
956
- <option value="searxng">🔧 SearXNG(自托管)</option>
957
- <option value="google">🔍 Google Custom Search(需 API Key)</option>
958
- </select>
959
- </div>
960
-
961
- <div class="form-group" id="search-apikey-group" style="display:none;">
962
- <label class="label" id="search-apikey-label">API Key</label>
963
- <input class="input" id="search-apikey" type="password" placeholder="输入 API Key" onchange="updateSearchConfig()">
964
- </div>
965
-
966
- <div class="form-group" id="search-baseurl-group" style="display:none;">
967
- <label class="label">SearXNG URL</label>
968
- <input class="input" id="search-baseurl" placeholder="https://searx.example.com" onchange="updateSearchConfig()">
969
- </div>
970
-
971
- <div style="margin-top:16px;">
972
- <button class="btn btn-secondary btn-sm" onclick="testSearch()">🧪 测试搜索</button>
973
- </div>
974
- <div id="search-test-result" style="margin-top:12px;font-size:13px;"></div>
975
-
976
- <div style="margin-top:24px;padding:16px;background:var(--bg-light);border-radius:8px;font-size:13px;color:var(--text-muted);">
977
- <strong>💡 提示</strong><br>
978
- Agent 会自动判断何时需要搜索,也可以通过 <code>web_search</code> 和 <code>web_read</code> 工具主动调用。<br>
979
- DuckDuckGo 完全免费、无需注册,适合大多数场景。
980
- </div>
981
- </div>
982
-
983
- </div>
984
- </div>
985
- </div>
986
-
987
- <!-- Provider Config Dialog -->
988
- <div class="dialog-overlay" id="provider-dialog">
989
- <div class="dialog" style="max-width:480px;">
990
- <div class="dialog-title" id="pd-title">配置 Provider</div>
991
- <div class="dialog-desc" id="pd-desc">填入 API Key 即可开始使用</div>
992
- <div class="form-group">
993
- <label class="label">API Key</label>
994
- <input class="input" id="pd-apikey" type="password" placeholder="sk-...">
995
- </div>
996
- <div class="form-group" id="pd-baseurl-group" style="display:none;">
997
- <label class="label">自定义 Base URL(可选)</label>
998
- <input class="input" id="pd-baseurl" placeholder="https://api.example.com">
999
- </div>
1000
- <div id="pd-test-result" style="margin-bottom:16px;font-size:13px;"></div>
1001
- <div class="dialog-actions">
1002
- <button class="btn btn-secondary btn-sm" onclick="closeProviderDialog()">取消</button>
1003
- <button class="btn btn-secondary btn-sm" onclick="testProvider()">🔍 测试连接</button>
1004
- <button class="btn btn-primary btn-sm" onclick="saveProvider()">💾 保存</button>
1005
- </div>
1006
- </div>
1007
- </div>
1008
-
1009
- <!-- Channel Config Dialog -->
1010
- <div class="dialog-overlay" id="channel-dialog">
1011
- <div class="dialog" style="max-width:480px;">
1012
- <div class="dialog-title" id="cd-title">配置渠道</div>
1013
- <div class="dialog-desc" id="cd-desc"></div>
1014
- <div id="cd-fields"></div>
1015
- <div class="dialog-actions">
1016
- <button class="btn btn-secondary btn-sm" onclick="closeChannelDialog()">取消</button>
1017
- <button class="btn btn-primary btn-sm" onclick="saveChannel()">💾 保存</button>
1018
- </div>
1019
- </div>
1020
- </div>
1021
-
1022
- </div>
1023
- </div>
1024
-
1025
- <!-- Delete Confirm Dialog -->
1026
- <div class="dialog-overlay" id="delete-dialog">
1027
- <div class="dialog">
1028
- <div class="dialog-title">Delete Agent?</div>
1029
- <div class="dialog-desc">This action cannot be undone. The agent and all its data will be permanently removed.</div>
1030
- <div class="dialog-actions">
1031
- <button class="btn btn-secondary btn-sm" onclick="closeDeleteDialog()">Cancel</button>
1032
- <button class="btn btn-danger btn-sm" onclick="confirmDelete()">Delete</button>
1033
- </div>
1034
- </div>
1035
- </div>
1036
-
1037
- <!-- First Run Wizard Modal -->
1038
- <div id="first-run-overlay" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.85);z-index:600;align-items:center;justify-content:center;">
1039
- <div style="background:var(--bg-card);border:1px solid var(--border);border-radius:var(--radius-lg);padding:40px;max-width:580px;width:94%;max-height:90vh;overflow-y:auto;">
1040
- <!-- Step indicators -->
1041
- <div style="display:flex;justify-content:center;gap:6px;margin-bottom:32px;" id="fr-steps">
1042
- <div class="wizard-step active" id="fr-step-1"><div class="step-num">1</div><span>Welcome</span></div>
1043
- <div class="wizard-step" id="fr-step-2"><div class="step-line"></div><div class="step-num">2</div><span>Models</span></div>
1044
- <div class="wizard-step" id="fr-step-3"><div class="step-line"></div><div class="step-num">3</div><span>Template</span></div>
1045
- <div class="wizard-step" id="fr-step-4"><div class="step-line"></div><div class="step-num">4</div><span>Done</span></div>
1046
- </div>
1047
-
1048
- <!-- Step 1: Welcome -->
1049
- <div class="wizard-panel active" id="fr-panel-1">
1050
- <div style="text-align:center;margin-bottom:32px;">
1051
- <div style="font-size:64px;margin-bottom:16px;">⚡</div>
1052
- <h2 style="font-size:24px;font-weight:700;margin-bottom:8px;">Welcome to OPC Studio</h2>
1053
- <p style="color:var(--text-muted);font-size:15px;line-height:1.6;">Create AI agents in minutes — no coding required.<br>Let's get you set up in 3 quick steps.</p>
1054
- </div>
1055
- <div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:32px;">
1056
- <div class="card" style="text-align:center;padding:16px 8px;"><div style="font-size:28px;margin-bottom:8px;">🤖</div><div style="font-size:13px;font-weight:500;">100+ Templates</div></div>
1057
- <div class="card" style="text-align:center;padding:16px 8px;"><div style="font-size:28px;margin-bottom:8px;">🧠</div><div style="font-size:13px;font-weight:500;">Local AI Models</div></div>
1058
- <div class="card" style="text-align:center;padding:16px 8px;"><div style="font-size:28px;margin-bottom:8px;">💬</div><div style="font-size:13px;font-weight:500;">Multi-channel</div></div>
1059
- </div>
1060
- <button class="btn btn-primary btn-lg" style="width:100%;" onclick="frNext()">Get Started →</button>
1061
- </div>
1062
-
1063
- <!-- Step 2: Model Detection -->
1064
- <div class="wizard-panel" id="fr-panel-2">
1065
- <h2 style="font-size:20px;font-weight:700;margin-bottom:4px;">🤖 Choose Your AI Model</h2>
1066
- <p style="color:var(--text-muted);font-size:14px;margin-bottom:20px;">We recommend using free local models. Cloud APIs also supported.</p>
1067
- <div id="fr-ollama-status" style="padding:16px;border-radius:var(--radius);margin-bottom:16px;background:var(--bg-hover);">
1068
- <div style="display:flex;align-items:center;gap:8px;"><span class="status-dot yellow"></span> Detecting Ollama...</div>
1069
- </div>
1070
- <div id="fr-model-choice" style="display:none;">
1071
- <div class="form-group">
1072
- <label class="label">Select a model to use</label>
1073
- <select class="input" id="fr-model-select">
1074
- <option value="qwen2.5:7b">qwen2.5:7b — Local, free ⭐ Recommended</option>
1075
- <option value="gpt-4o-mini">GPT-4o Mini — Cloud, fast</option>
1076
- <option value="gpt-4o">GPT-4o — Cloud, most capable</option>
1077
- <option value="claude-sonnet-4">Claude Sonnet — Cloud, balanced</option>
1078
- <option value="deepseek-v3">DeepSeek V3 — Cloud, affordable</option>
1079
- </select>
1080
- </div>
1081
- </div>
1082
- <div style="display:flex;gap:12px;justify-content:flex-end;margin-top:24px;">
1083
- <button class="btn btn-secondary" onclick="frBack()">← Back</button>
1084
- <button class="btn btn-primary" onclick="frNext()">Next →</button>
1085
- </div>
1086
- </div>
1087
-
1088
- <!-- Step 3: Choose Template -->
1089
- <div class="wizard-panel" id="fr-panel-3">
1090
- <h2 style="font-size:20px;font-weight:700;margin-bottom:4px;">👤 Pick a Starting Template</h2>
1091
- <p style="color:var(--text-muted);font-size:14px;margin-bottom:20px;">Choose what your first agent will do. You can always change this later.</p>
1092
- <div style="display:flex;flex-direction:column;gap:8px;" id="fr-template-list">
1093
- <div class="card" style="cursor:pointer;display:flex;align-items:center;gap:12px;padding:14px 16px;" onclick="frSelectTemplate('customer-service')" id="fr-tpl-customer-service">
1094
- <span style="font-size:24px;">🎧</span><div><div style="font-weight:500;">Customer Service</div><div style="font-size:12px;color:var(--text-muted);">Answer questions, resolve issues</div></div>
1095
- </div>
1096
- <div class="card" style="cursor:pointer;display:flex;align-items:center;gap:12px;padding:14px 16px;" onclick="frSelectTemplate('executive-assistant')" id="fr-tpl-executive-assistant">
1097
- <span style="font-size:24px;">💼</span><div><div style="font-weight:500;">Personal Assistant</div><div style="font-size:12px;color:var(--text-muted);">Scheduling, email drafting, planning</div></div>
1098
- </div>
1099
- <div class="card" style="cursor:pointer;display:flex;align-items:center;gap:12px;padding:14px 16px;" onclick="frSelectTemplate('content-writer')" id="fr-tpl-content-writer">
1100
- <span style="font-size:24px;">✍️</span><div><div style="font-weight:500;">Content Writer</div><div style="font-size:12px;color:var(--text-muted);">Blog posts, social media, SEO</div></div>
1101
- </div>
1102
- <div class="card" style="cursor:pointer;display:flex;align-items:center;gap:12px;padding:14px 16px;" onclick="frSelectTemplate('data-analyst')" id="fr-tpl-data-analyst">
1103
- <span style="font-size:24px;">📊</span><div><div style="font-weight:500;">Data Analyst</div><div style="font-size:12px;color:var(--text-muted);">Data querying, insights, reports</div></div>
1104
- </div>
1105
- <div class="card" style="cursor:pointer;display:flex;align-items:center;gap:12px;padding:14px 16px;" onclick="frSelectTemplate('teacher')" id="fr-tpl-teacher">
1106
- <span style="font-size:24px;">📚</span><div><div style="font-weight:500;">Translator / Teacher</div><div style="font-size:12px;color:var(--text-muted);">Language learning, translation, explanation</div></div>
1107
- </div>
1108
- </div>
1109
- <div style="display:flex;gap:12px;justify-content:flex-end;margin-top:20px;">
1110
- <button class="btn btn-secondary" onclick="frBack()">← Back</button>
1111
- <button class="btn btn-primary" onclick="frNext()" id="fr-next-3">Next →</button>
1112
- </div>
1113
- </div>
1114
-
1115
- <!-- Step 4: Creating / Done -->
1116
- <div class="wizard-panel" id="fr-panel-4">
1117
- <div style="text-align:center;padding:20px 0;">
1118
- <div id="fr-creating" style="">
1119
- <div style="font-size:48px;margin-bottom:16px;">⏳</div>
1120
- <h2 style="font-size:20px;font-weight:700;margin-bottom:8px;">Creating your agent...</h2>
1121
- <p style="color:var(--text-muted);">Just a moment</p>
1122
- </div>
1123
- <div id="fr-done" style="display:none;">
1124
- <div style="font-size:64px;margin-bottom:16px;">🎉</div>
1125
- <h2 style="font-size:22px;font-weight:700;margin-bottom:8px;">You're all set!</h2>
1126
- <p style="color:var(--text-muted);margin-bottom:24px;">Your agent is ready. Start chatting now!</p>
1127
- <button class="btn btn-primary btn-lg" onclick="frFinish()">Start Chatting →</button>
1128
- </div>
1129
- </div>
1130
- </div>
1131
- </div>
1132
- </div>
1133
-
1134
- <script>
1135
- // === Debug: catch all JS errors ===
1136
- window.onerror = function(msg, url, line, col, err) {
1137
- console.error('JS ERROR:', msg, 'at line', line, ':', col);
1138
- const d = document.createElement('div');
1139
- d.style.cssText = 'position:fixed;bottom:0;left:0;right:0;background:red;color:white;padding:8px;z-index:9999;font-size:12px;';
1140
- d.textContent = 'JS Error: ' + msg + ' (line ' + line + ')';
1141
- document.body.appendChild(d);
1142
- };
1143
- // === State ===
1144
- let templates = [];
1145
- let industries = [];
1146
- let agents = [];
1147
- let selectedTemplate = null;
1148
- let currentAgent = null;
1149
- let chatMessages = [];
1150
- let wizardStep = 1;
1151
- let selectedIndustry = '';
1152
- let deleteTargetId = null;
1153
-
1154
- const API = '';
1155
-
1156
- // === Init ===
1157
- async function init() {
1158
- await Promise.all([loadTemplates(), loadAgents(), loadSidebarAgents()]);
1159
- loadSidebarGroups();
1160
- loadDashboardStats();
1161
- handleRoute();
1162
- window.addEventListener('popstate', handleRoute);
1163
- checkFirstRun();
1164
- }
1165
-
1166
- function handleRoute() {
1167
- const path = location.hash.slice(1) || '/dashboard';
1168
- const parts = path.split('/').filter(Boolean);
1169
- if (parts[0] === 'chat' && parts[1]) {
1170
- openChat(parts[1]);
1171
- } else if (parts[0] === 'agent' && parts[1]) {
1172
- loadSidebarAgents().then(() => navigateToAgent(parts[1]));
1173
- } else if (parts[0] === 'settings') {
1174
- if (parts[1]) currentSettingsTab = parts[1];
1175
- navigate('settings');
1176
- } else if (parts[0] === 'memory' && parts[1]) {
1177
- openMemoryPage(parts[1]);
1178
- } else if (parts[0] === 'create') {
1179
- if (parts[1]) {
1180
- // pre-select template
1181
- selectedTemplate = templates.find(t => t.id === parts[1]) || null;
1182
- if (selectedTemplate) {
1183
- wizardStep = 2;
1184
- }
1185
- }
1186
- showPage('create');
1187
- renderWizard();
1188
- } else {
1189
- navigate(parts[0] || 'dashboard');
1190
- }
1191
- }
1192
-
1193
- // === API ===
1194
- async function loadTemplates() {
1195
- try {
1196
- const res = await fetch(`${API}/api/templates`);
1197
- const data = await res.json();
1198
- templates = data.templates || [];
1199
- industries = data.industries || [];
1200
- renderIndustryChips();
1201
- renderTemplates();
1202
- } catch(e) { console.error('Failed to load templates:', e); }
1203
- }
1204
-
1205
- async function loadAgents() {
1206
- try {
1207
- const res = await fetch(`${API}/api/agents`);
1208
- const data = await res.json();
1209
- agents = data.agents || [];
1210
- renderAgents();
1211
- } catch(e) { console.error('Failed to load agents:', e); }
1212
- }
1213
-
1214
- // === Sidebar Agents ===
1215
- let selectedAgentId = null;
1216
-
1217
- async function loadSidebarAgents() {
1218
- try {
1219
- const res = await fetch('/api/agents');
1220
- const data = await res.json();
1221
- const agents = data.agents || data || [];
1222
- window._sidebarAgents = agents;
1223
- const container = document.getElementById('sidebar-agent-list');
1224
- if (!agents.length) {
1225
- container.innerHTML = '<div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">暂无 Agent</div>';
1226
- return;
1227
- }
1228
- container.innerHTML = agents.map(a => {
1229
- const status = (a.status || 'offline').toLowerCase();
1230
- const icon = a.emoji || a.icon || '🤖';
1231
- const name = a.name || a.id;
1232
- return `<div class="agent-list-item${selectedAgentId === a.id ? ' active' : ''}" data-agent-id="${a.id}" onclick="navigateToAgent('${a.id}')">
1233
- <span class="agent-icon">${icon}</span>
1234
- <span class="agent-name">${name}</span>
1235
- <span class="status-dot ${status}"></span>
1236
- </div>`;
1237
- }).join('');
1238
- } catch(e) {
1239
- console.error('Failed to load sidebar agents:', e);
1240
- const container = document.getElementById('sidebar-agent-list');
1241
- if (container) container.innerHTML = '<div style="padding: 12px 16px; color: var(--text-dim); font-size: 13px;">加载失败</div>';
1242
- }
1243
- }
1244
-
1245
- // --- Collaboration Groups ---
1246
- let selectedPattern = 'debate';
1247
- function selectPattern(pat) {
1248
- selectedPattern = pat;
1249
- document.querySelectorAll('.pattern-card').forEach(c => c.classList.remove('active'));
1250
- const el = document.getElementById('pat-' + pat);
1251
- if (el) el.classList.add('active');
1252
- }
1253
- async function loadGroupAgentSelect() {
1254
- try {
1255
- const res = await fetch('/api/agents');
1256
- const data = await res.json();
1257
- const agents = data.agents || data || [];
1258
- const container = document.getElementById('group-agent-select');
1259
- if (!agents.length) {
1260
- container.innerHTML = '<p style="color:var(--text-muted);font-size:13px;">请先创建 Agent,再拉入群组</p>';
1261
- return;
1262
- }
1263
- container.innerHTML = agents.map(a => `<label style="display:flex;align-items:center;gap:8px;padding:6px 0;cursor:pointer;"><input type="checkbox" class="group-agent-cb" value="${a.id}"> <span>${a.templateIcon || a.icon || '🤖'}</span> <span style="font-size:14px;">${a.name}</span></label>`).join('');
1264
- } catch(e) { console.error('loadGroupAgentSelect error', e); }
1265
- }
1266
- async function createGroup() {
1267
- const name = document.getElementById('group-name').value.trim();
1268
- if (!name) { alert('请输入群组名称'); return; }
1269
- const members = [...document.querySelectorAll('.group-agent-cb:checked')].map(cb => cb.value);
1270
- if (members.length < 2) { alert('至少选择 2 个 Agent'); return; }
1271
- // TODO: POST to /api/groups
1272
- alert('群组 "' + name + '" 创建成功!模式: ' + selectedPattern + ', 成员: ' + members.length + ' 个 Agent');
1273
- navigate('dashboard');
1274
- }
1275
- async function loadSidebarGroups() {
1276
- // TODO: fetch /api/groups and render
1277
- const container = document.getElementById('groups-list');
1278
- if (container) container.innerHTML = '<div style="padding:6px 16px;color:var(--text-dim);font-size:12px;">暂无群组</div>';
1279
- }
1280
-
1281
- function navigateToAgent(agentId) {
1282
- selectedAgentId = agentId;
1283
- // Update sidebar active state
1284
- document.querySelectorAll('.agent-list-item').forEach(el => el.classList.remove('active'));
1285
- document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
1286
- const item = document.querySelector(`.agent-list-item[data-agent-id="${agentId}"]`);
1287
- if (item) item.classList.add('active');
1288
-
1289
- // Find agent data
1290
- const agent = (window._sidebarAgents || []).find(a => a.id === agentId) || { id: agentId, name: agentId };
1291
- document.getElementById('agent-detail-icon').textContent = agent.emoji || agent.icon || '🤖';
1292
- document.getElementById('agent-detail-name').textContent = agent.name || agentId;
1293
- const statusDot = document.getElementById('agent-detail-status');
1294
- const status = (agent.status || 'offline').toLowerCase();
1295
- statusDot.className = 'status-dot ' + status;
1296
-
1297
- // Reset to chat view
1298
- document.getElementById('agent-chat-view').style.display = '';
1299
- document.getElementById('agent-settings-view').style.display = 'none';
1300
- document.getElementById('agent-detail-toggle').classList.remove('active');
1301
- document.getElementById('agent-chat-messages').innerHTML = document.querySelector('.agent-chat-welcome').outerHTML || '<div class="agent-chat-welcome"><div style="font-size:48px;margin-bottom:16px">💬</div><div style="font-size:18px;font-weight:600;margin-bottom:8px">开始对话</div><div style="color:var(--text-muted);font-size:14px">向你的 Agent 发送第一条消息</div></div>';
1302
- document.getElementById('agent-chat-input').value = '';
1303
-
1304
- // Show agent detail page
1305
- document.querySelectorAll('.page, .chat-container').forEach(p => { p.classList.remove('active'); p.style.display = ''; });
1306
- document.getElementById('page-agent-detail').classList.add('active');
1307
- location.hash = `/agent/${agentId}`;
1308
- toggleSidebar(false);
1309
- }
1310
-
1311
- function toggleAgentSettings() {
1312
- const chatView = document.getElementById('agent-chat-view');
1313
- const settingsView = document.getElementById('agent-settings-view');
1314
- const toggleBtn = document.getElementById('agent-detail-toggle');
1315
- const showSettings = chatView.style.display !== 'none';
1316
- chatView.style.display = showSettings ? 'none' : '';
1317
- settingsView.style.display = showSettings ? '' : 'none';
1318
- toggleBtn.classList.toggle('active', showSettings);
1319
- }
1320
-
1321
- function switchAgentTab(tab) {
1322
- document.querySelectorAll('.agent-tab').forEach(t => t.classList.remove('active'));
1323
- document.querySelector(`.agent-tab[data-atab="${tab}"]`)?.classList.add('active');
1324
- document.querySelectorAll('.agent-tab-panel').forEach(p => p.classList.remove('active'));
1325
- document.getElementById(`atab-${tab}`)?.classList.add('active');
1326
- }
1327
-
1328
- let agentChatHistory = [];
1329
-
1330
- async function sendAgentChat() {
1331
- const input = document.getElementById('agent-chat-input');
1332
- const msg = input.value.trim();
1333
- if (!msg || !selectedAgentId) return;
1334
- input.value = '';
1335
- input.style.height = 'auto';
1336
-
1337
- const messagesEl = document.getElementById('agent-chat-messages');
1338
- // Remove welcome screen
1339
- const welcome = messagesEl.querySelector('.agent-chat-welcome');
1340
- if (welcome) welcome.remove();
1341
-
1342
- // Add user message
1343
- const userDiv = document.createElement('div');
1344
- userDiv.className = 'agent-chat-msg user';
1345
- userDiv.textContent = msg;
1346
- messagesEl.appendChild(userDiv);
1347
- messagesEl.scrollTop = messagesEl.scrollHeight;
1348
-
1349
- agentChatHistory.push({ role: 'user', content: msg });
1350
-
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
1358
- try {
1359
- const res = await fetch(`/api/agents/${selectedAgentId}/chat`, {
1360
- method: 'POST',
1361
- headers: { 'Content-Type': 'application/json' },
1362
- body: JSON.stringify({ message: msg, history: agentChatHistory.slice(0, -1) })
1363
- });
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 });
1403
- } catch(e) {
1404
- assistantDiv.style.borderColor = 'var(--red)';
1405
- assistantDiv.textContent = `⚠️ 发送失败: ${e.message}`;
1406
- }
1407
- messagesEl.scrollTop = messagesEl.scrollHeight;
1408
- }
1409
-
1410
- function handleAgentChatKey(e) {
1411
- if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendAgentChat(); }
1412
- // Auto-resize textarea
1413
- e.target.style.height = 'auto';
1414
- e.target.style.height = Math.min(e.target.scrollHeight, 120) + 'px';
1415
- }
1416
-
1417
- // === Navigation ===
1418
- function navigate(page) {
1419
- document.querySelectorAll('.page, .chat-container').forEach(p => { p.classList.remove('active'); p.style.display = ''; });
1420
- document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
1421
- const navItem = document.querySelector(`.nav-item[data-page="${page}"]`);
1422
- if (navItem) navItem.classList.add('active');
1423
-
1424
- if (page === 'dashboard') { loadAgents(); loadHealthDashboard(); loadSidebarAgents(); loadDashboardStats(); }
1425
- if (page === 'create') { renderWizard(); renderWizardTemplates(); }
1426
- if (page === 'settings') { showSettings(currentSettingsTab || 'models'); }
1427
- if (page === 'global-runtime') { currentSettingsTab='status'; showSettings('status'); showPage('settings'); return; }
1428
- if (page === 'global-models') { currentSettingsTab='models'; showSettings('models'); showPage('settings'); return; }
1429
- if (page === 'global-channels') { currentSettingsTab='channels'; showSettings('channels'); showPage('settings'); return; }
1430
- if (page === 'global-memory') { currentSettingsTab='memory'; showSettings('memory'); showPage('settings'); return; }
1431
- if (page === 'global-templates') { navigate('templates'); return; }
1432
- if (page === 'create-group') { loadGroupAgentSelect(); }
1433
- if (page === 'schedules') { loadSchedules(); }
1434
- if (page === 'skills') { loadSkillsMarketplace(); }
1435
-
1436
- showPage(page);
1437
- location.hash = `/${page}`;
1438
- toggleSidebar(false);
1439
- }
1440
-
1441
- function showPage(page) {
1442
- document.querySelectorAll('.page, .chat-container').forEach(p => { p.classList.remove('active'); });
1443
- const el = document.getElementById(`page-${page}`);
1444
- if (el) el.classList.add('active');
1445
- }
1446
-
1447
- function toggleSidebar(open) {
1448
- document.querySelector('.sidebar').classList.toggle('open', open);
1449
- document.querySelector('.sidebar-overlay').classList.toggle('show', open);
1450
- }
1451
-
1452
- // === Templates Rendering ===
1453
- function renderIndustryChips() {
1454
- const html = `<span class="chip active" onclick="filterByIndustry('')">All</span>` +
1455
- industries.map(i => `<span class="chip" onclick="filterByIndustry('${i.id}')">${i.nameZh} ${i.name}</span>`).join('');
1456
- document.getElementById('industry-chips').innerHTML = html;
1457
- document.getElementById('wizard-industry-chips').innerHTML = html.replace(/filterByIndustry/g, 'filterWizardByIndustry');
1458
- }
1459
-
1460
- function filterByIndustry(id) {
1461
- selectedIndustry = id;
1462
- document.querySelectorAll('#industry-chips .chip').forEach(c => c.classList.remove('active'));
1463
- event.target.classList.add('active');
1464
- renderTemplates();
1465
- }
1466
-
1467
- function filterWizardByIndustry(id) {
1468
- selectedIndustry = id;
1469
- document.querySelectorAll('#wizard-industry-chips .chip').forEach(c => c.classList.remove('active'));
1470
- event.target.classList.add('active');
1471
- renderWizardTemplates();
1472
- }
1473
-
1474
- function filterTemplates() {
1475
- renderTemplates();
1476
- }
1477
-
1478
- // === Skills Marketplace ===
1479
- let allSkills = [];
1480
- let selectedSkillCategory = '';
1481
- const SKILL_CATEGORIES = [
1482
- { id: '', label: 'All', labelZh: '全部' },
1483
- { id: 'productivity', label: 'Productivity', labelZh: '效率' },
1484
- { id: 'knowledge', label: 'Knowledge', labelZh: '知识' },
1485
- { id: 'creative', label: 'Creative', labelZh: '创作' },
1486
- { id: 'developer', label: 'Developer', labelZh: '开发' },
1487
- { id: 'lifestyle', label: 'Lifestyle', labelZh: '生活' },
1488
- { id: 'business', label: 'Business', labelZh: '业务' },
1489
- ];
1490
-
1491
- async function loadSkillsMarketplace() {
1492
- try {
1493
- const res = await fetch('/api/skills/marketplace');
1494
- allSkills = await res.json();
1495
- } catch(e) { console.error('Failed to load skills:', e); allSkills = []; }
1496
- renderSkillCategoryChips();
1497
- renderSkills();
1498
- }
1499
-
1500
- function renderSkillCategoryChips() {
1501
- const el = document.getElementById('skill-category-chips');
1502
- if (!el) return;
1503
- el.innerHTML = SKILL_CATEGORIES.map(c =>
1504
- `<span class="chip ${selectedSkillCategory === c.id ? 'active' : ''}" onclick="selectSkillCategory('${c.id}')">${c.labelZh}</span>`
1505
- ).join('');
1506
- }
1507
-
1508
- function selectSkillCategory(cat) {
1509
- selectedSkillCategory = cat;
1510
- renderSkillCategoryChips();
1511
- renderSkills();
1512
- }
1513
-
1514
- function filterSkills() {
1515
- renderSkills();
1516
- }
1517
-
1518
- function renderSkills() {
1519
- const q = (document.getElementById('skills-search')?.value || '').toLowerCase();
1520
- let filtered = allSkills;
1521
- if (selectedSkillCategory) {
1522
- filtered = filtered.filter(s => s.category === selectedSkillCategory);
1523
- }
1524
- if (q) {
1525
- filtered = filtered.filter(s =>
1526
- s.name.toLowerCase().includes(q) || s.nameZh.includes(q) ||
1527
- s.description.toLowerCase().includes(q) || s.descriptionZh.includes(q)
1528
- );
1529
- }
1530
- const grid = document.getElementById('skills-grid');
1531
- if (!grid) return;
1532
- grid.innerHTML = filtered.map(s => `
1533
- <div class="card" style="cursor:default;position:relative;">
1534
- <div style="font-size:36px;margin-bottom:8px;">${s.icon}</div>
1535
- <div style="font-weight:600;font-size:15px;">${s.nameZh}</div>
1536
- <div style="font-size:12px;color:var(--text-dim);margin-bottom:4px;">${s.name}</div>
1537
- <div style="font-size:13px;color:var(--text-muted);margin-bottom:12px;line-height:1.4;">${s.descriptionZh}</div>
1538
- <div style="display:flex;flex-wrap:wrap;gap:4px;margin-bottom:12px;">
1539
- ${s.tools.slice(0,3).map(t => `<span style="font-size:11px;padding:2px 6px;background:var(--bg-hover);border-radius:4px;color:var(--text-dim);">${t}</span>`).join('')}
1540
- ${s.tools.length > 3 ? `<span style="font-size:11px;color:var(--text-dim);">+${s.tools.length-3}</span>` : ''}
1541
- </div>
1542
- ${s.installed
1543
- ? `<button class="btn" style="width:100%;background:var(--bg-hover);color:var(--text-muted);cursor:pointer;" onclick="uninstallSkill('${s.id}',this)">✓ Installed</button>`
1544
- : `<button class="btn btn-primary" style="width:100%;" onclick="installSkill('${s.id}',this)">Install</button>`
1545
- }
1546
- </div>
1547
- `).join('');
1548
- }
1549
-
1550
- async function installSkill(id, btn) {
1551
- btn.disabled = true; btn.textContent = 'Installing...';
1552
- try {
1553
- const res = await fetch(`/api/skills/marketplace/${id}/install`, { method: 'POST' });
1554
- const data = await res.json();
1555
- if (data.success) {
1556
- const skill = allSkills.find(s => s.id === id);
1557
- if (skill) skill.installed = true;
1558
- renderSkills();
1559
- }
1560
- } catch(e) { console.error(e); btn.disabled = false; btn.textContent = 'Install'; }
1561
- }
1562
-
1563
- async function uninstallSkill(id, btn) {
1564
- btn.disabled = true; btn.textContent = 'Removing...';
1565
- try {
1566
- const res = await fetch(`/api/skills/marketplace/${id}/uninstall`, { method: 'DELETE' });
1567
- const data = await res.json();
1568
- if (data.success) {
1569
- const skill = allSkills.find(s => s.id === id);
1570
- if (skill) skill.installed = false;
1571
- renderSkills();
1572
- }
1573
- } catch(e) { console.error(e); btn.disabled = false; btn.textContent = '✓ Installed'; }
1574
- }
1575
-
1576
- function filterWizardTemplates() {
1577
- renderWizardTemplates();
1578
- }
1579
-
1580
- function getFilteredTemplates(searchId) {
1581
- const q = (document.getElementById(searchId)?.value || '').toLowerCase();
1582
- return templates.filter(t => {
1583
- if (selectedIndustry && t.industry !== selectedIndustry) return false;
1584
- if (q && !t.name.toLowerCase().includes(q) && !t.nameZh.includes(q) && !t.description.toLowerCase().includes(q)) return false;
1585
- return true;
1586
- });
1587
- }
1588
-
1589
- function renderTemplates() {
1590
- const filtered = getFilteredTemplates('tpl-search');
1591
- document.getElementById('templates-grid').innerHTML = filtered.map(t => `
1592
- <div class="card tpl-card" onclick="selectTemplateAndCreate('${t.id}')">
1593
- <div class="tpl-icon">${t.icon}</div>
1594
- <div class="tpl-name">${t.name}</div>
1595
- <div style="font-size:13px;color:var(--text-dim);margin-bottom:6px;">${t.nameZh}</div>
1596
- <div class="tpl-desc">${t.description}</div>
1597
- <div class="tpl-tags">
1598
- <span class="tpl-tag">${t.industryZh}</span>
1599
- ${t.tags.map(tag => `<span class="tpl-tag">${tag}</span>`).join('')}
1600
- </div>
1601
- </div>
1602
- `).join('');
1603
- }
1604
-
1605
- function renderWizardTemplates() {
1606
- const filtered = getFilteredTemplates('wizard-tpl-search');
1607
- document.getElementById('wizard-tpl-grid').innerHTML = filtered.map(t => `
1608
- <div class="card tpl-card ${selectedTemplate?.id === t.id ? 'selected' : ''}" onclick="selectWizardTemplate('${t.id}')"
1609
- style="${selectedTemplate?.id === t.id ? 'border-color:var(--accent);background:var(--accent-light);' : ''}">
1610
- <div class="tpl-icon">${t.icon}</div>
1611
- <div class="tpl-name">${t.name}</div>
1612
- <div style="font-size:12px;color:var(--text-dim);">${t.nameZh} · ${t.industryZh}</div>
1613
- </div>
1614
- `).join('');
1615
- }
1616
-
1617
- function selectTemplateAndCreate(id) {
1618
- selectedTemplate = templates.find(t => t.id === id);
1619
- wizardStep = 2;
1620
- navigate('create');
1621
- }
1622
-
1623
- function selectWizardTemplate(id) {
1624
- selectedTemplate = templates.find(t => t.id === id);
1625
- renderWizardTemplates();
1626
- // Auto-advance after selection
1627
- setTimeout(() => wizardNext(), 300);
1628
- }
1629
-
1630
- // === Wizard ===
1631
- function renderWizard() {
1632
- for (let i = 1; i <= 3; i++) {
1633
- const ws = document.getElementById(`ws-${i}`);
1634
- const wp = document.getElementById(`wp-${i}`);
1635
- ws.className = 'wizard-step' + (i < wizardStep ? ' done' : i === wizardStep ? ' active' : '');
1636
- wp.className = 'wizard-panel' + (i === wizardStep ? ' active' : '');
1637
- }
1638
- if (wizardStep === 2 && selectedTemplate) {
1639
- document.getElementById('agent-name').placeholder = selectedTemplate.name;
1640
- document.getElementById('agent-model').value = selectedTemplate.suggestedModel;
1641
- }
1642
- if (wizardStep === 3) {
1643
- renderConfirmCard();
1644
- }
1645
- }
1646
-
1647
- function wizardNext() {
1648
- if (wizardStep === 1 && !selectedTemplate) { alert('Please select a template first'); return; }
1649
- if (wizardStep < 3) { wizardStep++; renderWizard(); }
1650
- }
1651
-
1652
- function wizardBack() {
1653
- if (wizardStep > 1) { wizardStep--; renderWizard(); }
1654
- }
1655
-
1656
- function renderConfirmCard() {
1657
- const name = document.getElementById('agent-name').value || selectedTemplate?.name || 'My Agent';
1658
- const model = document.getElementById('agent-model').value;
1659
- const lang = document.getElementById('agent-lang').selectedOptions[0]?.text || 'English';
1660
- document.getElementById('confirm-card').innerHTML = `
1661
- <div style="display:flex;align-items:center;gap:16px;margin-bottom:16px;">
1662
- <span style="font-size:48px;">${selectedTemplate?.icon || '🤖'}</span>
1663
- <div>
1664
- <div style="font-size:20px;font-weight:700;">${name}</div>
1665
- <div style="color:var(--text-muted);font-size:14px;">Based on: ${selectedTemplate?.name || 'Custom'} (${selectedTemplate?.nameZh || ''})</div>
1666
- </div>
1667
- </div>
1668
- <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;font-size:14px;">
1669
- <div><span style="color:var(--text-dim);">Model:</span> ${model}</div>
1670
- <div><span style="color:var(--text-dim);">Language:</span> ${lang}</div>
1671
- <div style="grid-column:span 2;"><span style="color:var(--text-dim);">Industry:</span> ${selectedTemplate?.industryZh || ''} (${selectedTemplate?.industry || ''})</div>
1672
- </div>
1673
- `;
1674
- }
1675
-
1676
- async function createAgent() {
1677
- const btn = document.getElementById('create-btn');
1678
- btn.textContent = '⏳ Creating...';
1679
- btn.disabled = true;
1680
- try {
1681
- const res = await fetch(`${API}/api/agents`, {
1682
- method: 'POST',
1683
- headers: { 'Content-Type': 'application/json' },
1684
- body: JSON.stringify({
1685
- name: document.getElementById('agent-name').value || selectedTemplate?.name,
1686
- templateId: selectedTemplate?.id,
1687
- description: document.getElementById('agent-desc').value,
1688
- model: document.getElementById('agent-model').value,
1689
- language: document.getElementById('agent-lang').value,
1690
- }),
1691
- });
1692
- const agent = await res.json();
1693
- // Reset wizard
1694
- wizardStep = 1;
1695
- selectedTemplate = null;
1696
- document.getElementById('agent-name').value = '';
1697
- document.getElementById('agent-desc').value = '';
1698
- // Navigate to chat
1699
- openChat(agent.id);
1700
- } catch(e) {
1701
- alert('Failed to create agent: ' + e.message);
1702
- }
1703
- btn.textContent = '🚀 Create Agent';
1704
- btn.disabled = false;
1705
- }
1706
-
1707
- // === Dashboard ===
1708
- function renderAgents() {
1709
- if (agents.length === 0) {
1710
- document.getElementById('agents-list').style.display = 'none';
1711
- document.getElementById('agents-empty').style.display = 'block';
1712
- return;
1713
- }
1714
- document.getElementById('agents-list').style.display = '';
1715
- document.getElementById('agents-empty').style.display = 'none';
1716
- document.getElementById('agents-list').innerHTML = agents.map(a => {
1717
- const timeAgo = getTimeAgo(a.lastActive || a.created);
1718
- return `
1719
- <div class="card agent-card" onclick="openChat('${a.id}')">
1720
- <div class="agent-actions">
1721
- <button onclick="event.stopPropagation();openDeleteDialog('${a.id}')">🗑️</button>
1722
- </div>
1723
- <div class="agent-icon">${a.templateIcon || '🤖'}</div>
1724
- <div class="agent-name">${a.name}</div>
1725
- <div class="agent-template">${a.templateName || 'Custom'}</div>
1726
- <div class="agent-stats">
1727
- <span>💬 ${a.messageCount || 0} messages</span>
1728
- <span>⏰ ${timeAgo}</span>
1729
- </div>
1730
- </div>
1731
- `;
1732
- }).join('');
1733
- }
1734
-
1735
- function getTimeAgo(dateStr) {
1736
- const diff = Date.now() - new Date(dateStr).getTime();
1737
- const mins = Math.floor(diff / 60000);
1738
- if (mins < 1) return 'just now';
1739
- if (mins < 60) return `${mins}m ago`;
1740
- const hours = Math.floor(mins / 60);
1741
- if (hours < 24) return `${hours}h ago`;
1742
- const days = Math.floor(hours / 24);
1743
- return `${days}d ago`;
1744
- }
1745
-
1746
- // === Delete ===
1747
- function openDeleteDialog(id) { deleteTargetId = id; document.getElementById('delete-dialog').classList.add('show'); }
1748
- function closeDeleteDialog() { deleteTargetId = null; document.getElementById('delete-dialog').classList.remove('show'); }
1749
- async function confirmDelete() {
1750
- if (!deleteTargetId) return;
1751
- await fetch(`${API}/api/agents/${deleteTargetId}`, { method: 'DELETE' });
1752
- closeDeleteDialog();
1753
- loadAgents();
1754
- }
1755
-
1756
- // === Chat ===
1757
- async function openLastChat() {
1758
- if (currentAgent) { openChat(currentAgent.id); return; }
1759
- const agentsRes = await fetch(`${API}/api/agents`).catch(() => null);
1760
- if (agentsRes) {
1761
- const data = await agentsRes.json().catch(() => ({}));
1762
- const list = data.agents || [];
1763
- if (list.length > 0) { openChat(list[0].id); return; }
1764
- }
1765
- navigate('dashboard');
1766
- }
1767
-
1768
- async function switchChatAgent(agentId) {
1769
- if (agentId && agentId !== currentAgent?.id) openChat(agentId);
1770
- }
1771
-
1772
- async function openChat(agentId) {
1773
- try {
1774
- const res = await fetch(`${API}/api/agents/${agentId}`);
1775
- currentAgent = await res.json();
1776
- if (currentAgent.error) { navigate('dashboard'); return; }
1777
- } catch { navigate('dashboard'); return; }
1778
-
1779
- // Load history from localStorage, fallback to empty
1780
- const stored = localStorage.getItem(`opc-chat-${agentId}`);
1781
- chatMessages = stored ? JSON.parse(stored) : [];
1782
-
1783
- document.getElementById('chat-agent-icon').textContent = currentAgent.templateIcon || '🤖';
1784
- document.getElementById('chat-agent-name').textContent = currentAgent.name;
1785
- document.getElementById('chat-agent-status').textContent = `${currentAgent.templateName || 'Custom'} · ${currentAgent.model}`;
1786
-
1787
- // Populate agent selector
1788
- const sel = document.getElementById('chat-agent-select');
1789
- sel.innerHTML = agents.map(a => `<option value="${a.id}" ${a.id === agentId ? 'selected' : ''}>${a.templateIcon || '🤖'} ${a.name}</option>`).join('');
1790
-
1791
- // Render messages
1792
- const msgEl = document.getElementById('chat-messages');
1793
- if (chatMessages.length > 0) {
1794
- msgEl.innerHTML = chatMessages.map(m => `
1795
- <div class="msg ${m.role}">
1796
- <div class="msg-bubble">${m.content.replace(/</g,'&lt;')}</div>
1797
- </div>
1798
- `).join('');
1799
- } else {
1800
- msgEl.innerHTML = `
1801
- <div class="msg assistant">
1802
- <div class="msg-bubble">Hello! I'm ${currentAgent.name}. ${currentAgent.description ? 'I specialize in: ' + currentAgent.description : 'How can I help you today?'}</div>
1803
- </div>
1804
- `;
1805
- }
1806
- document.getElementById('chat-input').value = '';
1807
-
1808
- showPage('chat');
1809
- location.hash = `/chat/${agentId}`;
1810
- document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
1811
- const chatNav = document.querySelector('.nav-item[data-page="chat"]');
1812
- if (chatNav) chatNav.classList.add('active');
1813
- msgEl.scrollTop = msgEl.scrollHeight;
1814
- document.getElementById('chat-input').focus();
1815
- }
1816
-
1817
- function clearChat() {
1818
- if (!currentAgent) return;
1819
- chatMessages = [];
1820
- localStorage.removeItem(`opc-chat-${currentAgent.id}`);
1821
- document.getElementById('chat-messages').innerHTML = `
1822
- <div class="msg assistant">
1823
- <div class="msg-bubble">Hello! I'm ${currentAgent.name}. How can I help you today?</div>
1824
- </div>
1825
- `;
1826
- }
1827
-
1828
- async function handleDocUpload(input) {
1829
- const file = input.files[0];
1830
- if (!file || !currentAgent) return;
1831
- input.value = '';
1832
-
1833
- // Show uploading status in chat
1834
- appendMessage('user', `📎 Uploading: ${file.name}`);
1835
- const statusEl = appendMessage('assistant', '⏳ Processing document...');
1836
-
1837
- try {
1838
- const formData = new FormData();
1839
- formData.append('file', file);
1840
-
1841
- const res = await fetch(`${API}/api/agents/${currentAgent.id}/upload`, {
1842
- method: 'POST',
1843
- body: formData,
1844
- });
1845
-
1846
- const data = await res.json();
1847
- if (data.error) {
1848
- statusEl.textContent = `❌ ${data.error}`;
1849
- } else {
1850
- statusEl.textContent = `✅ Learned ${data.learnedCount} knowledge chunks from "${file.name}"`;
1851
- }
1852
- } catch (e) {
1853
- statusEl.textContent = `❌ Upload failed: ${e.message}`;
1854
- }
1855
- }
1856
-
1857
- async function sendMessage() {
1858
- const input = document.getElementById('chat-input');
1859
- const text = input.value.trim();
1860
- if (!text || !currentAgent) return;
1861
-
1862
- input.value = '';
1863
- chatMessages.push({ role: 'user', content: text });
1864
-
1865
- // Render user message
1866
- appendMessage('user', text);
1867
-
1868
- // Show typing + streaming indicator
1869
- document.getElementById('typing-indicator').classList.add('show');
1870
- document.getElementById('streaming-indicator').style.display = 'inline';
1871
- const msgContainer = document.getElementById('chat-messages');
1872
- msgContainer.scrollTop = msgContainer.scrollHeight;
1873
-
1874
- try {
1875
- const res = await fetch(`${API}/api/agents/${currentAgent.id}/chat`, {
1876
- method: 'POST',
1877
- headers: { 'Content-Type': 'application/json' },
1878
- body: JSON.stringify({ messages: chatMessages }),
1879
- });
1880
-
1881
- document.getElementById('typing-indicator').classList.remove('show');
1882
-
1883
- if (true) { // Always try SSE first — server returns text/event-stream for chat
1884
- // SSE streaming
1885
- const reader = res.body.getReader();
1886
- const decoder = new TextDecoder();
1887
- let assistantText = '';
1888
- const bubbleEl = appendMessage('assistant', '');
1889
-
1890
- while (true) {
1891
- const { done, value } = await reader.read();
1892
- if (done) break;
1893
- const chunk = decoder.decode(value);
1894
- const lines = chunk.split('\n');
1895
- for (const line of lines) {
1896
- if (line.startsWith('data: ')) {
1897
- const data = line.slice(6);
1898
- if (data === '[DONE]') break;
1899
- try {
1900
- const parsed = JSON.parse(data);
1901
- const content = parsed.choices?.[0]?.delta?.content || parsed.content || '';
1902
- assistantText += content;
1903
- bubbleEl.textContent = assistantText;
1904
- msgContainer.scrollTop = msgContainer.scrollHeight;
1905
- } catch {}
1906
- }
1907
- }
1908
- }
1909
- chatMessages.push({ role: 'assistant', content: assistantText });
1910
- } else {
1911
- const data = await res.json();
1912
- const reply = data.response || data.error || 'No response';
1913
- appendMessage('assistant', reply);
1914
- chatMessages.push({ role: 'assistant', content: reply });
1915
- }
1916
- // Persist to localStorage
1917
- if (currentAgent) {
1918
- try { localStorage.setItem(`opc-chat-${currentAgent.id}`, JSON.stringify(chatMessages.slice(-100))); } catch {}
1919
- }
1920
- } catch(e) {
1921
- document.getElementById('typing-indicator').classList.remove('show');
1922
- appendMessage('assistant', `Error: ${e.message}`);
1923
- } finally {
1924
- document.getElementById('streaming-indicator').style.display = 'none';
1925
- }
1926
- msgContainer.scrollTop = msgContainer.scrollHeight;
1927
- }
1928
-
1929
- function appendMessage(role, text) {
1930
- const msgContainer = document.getElementById('chat-messages');
1931
- const time = new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
1932
- const div = document.createElement('div');
1933
- div.className = `msg ${role}`;
1934
- const bubble = document.createElement('div');
1935
- bubble.className = 'msg-bubble';
1936
- bubble.textContent = text;
1937
- div.appendChild(bubble);
1938
- const timeEl = document.createElement('div');
1939
- timeEl.className = 'msg-time';
1940
- timeEl.textContent = time;
1941
- div.appendChild(timeEl);
1942
- msgContainer.appendChild(div);
1943
- msgContainer.scrollTop = msgContainer.scrollHeight;
1944
- return bubble;
1945
- }
1946
-
1947
- // === Memory ===
1948
- function openMemory() {
1949
- if (currentAgent) openMemoryPage(currentAgent.id);
1950
- }
1951
-
1952
- async function openMemoryPage(agentId) {
1953
- showPage('memory');
1954
- location.hash = `/memory/${agentId}`;
1955
- try {
1956
- const res = await fetch(`${API}/api/agents/${agentId}/memory`);
1957
- const data = await res.json();
1958
- if (data.entries && data.entries.length > 0) {
1959
- document.getElementById('memory-empty').style.display = 'none';
1960
- document.getElementById('memory-timeline').innerHTML = `
1961
- <div class="timeline">
1962
- ${data.entries.map(e => `
1963
- <div class="timeline-item">
1964
- <div class="timeline-date">${new Date(e.timestamp).toLocaleDateString()} ${new Date(e.timestamp).toLocaleTimeString()}</div>
1965
- <div class="timeline-content">${e.summary || e.content || 'Learned something new'}</div>
1966
- </div>
1967
- `).join('')}
1968
- </div>
1969
- `;
1970
- } else {
1971
- document.getElementById('memory-empty').style.display = 'block';
1972
- document.getElementById('memory-timeline').innerHTML = '';
1973
- }
1974
- } catch {
1975
- document.getElementById('memory-empty').style.display = 'block';
1976
- }
1977
- }
1978
-
1979
- function navigateToChat() {
1980
- if (currentAgent) openChat(currentAgent.id);
1981
- else navigate('dashboard');
1982
- }
1983
-
1984
- // === Settings ===
1985
- let currentSettingsTab = 'models';
1986
- let currentProvider = null;
1987
- let currentChannel = null;
1988
- let modelConfig = {};
1989
- let statusRefreshTimer = null;
1990
-
1991
- function showSettings(tab) {
1992
- currentSettingsTab = tab;
1993
- document.querySelectorAll('.settings-nav-item').forEach(n => n.classList.remove('active'));
1994
- document.querySelector(`.settings-nav-item[data-settings="${tab}"]`)?.classList.add('active');
1995
- document.querySelectorAll('.settings-panel').forEach(p => p.classList.remove('active'));
1996
- document.getElementById(`sp-${tab}`)?.classList.add('active');
1997
-
1998
- if (tab === 'models') initModelsPanel();
1999
- if (tab === 'channels') initChannelsPanel();
2000
- if (tab === 'memory') initMemoryPanel();
2001
- if (tab === 'role') initRolePanel();
2002
- if (tab === 'status') refreshStatus();
2003
- if (tab === 'usage') refreshUsage();
2004
- if (tab === 'search') initSearchPanel();
2005
- }
2006
-
2007
- function switchModelTab(tab) {
2008
- document.querySelectorAll('#sp-models .tab').forEach(t => t.classList.remove('active'));
2009
- document.querySelectorAll('#sp-models .tab-panel').forEach(p => p.classList.remove('active'));
2010
- if (tab === 'local') {
2011
- document.querySelector('#sp-models .tab:first-child').classList.add('active');
2012
- document.getElementById('mt-local').classList.add('active');
2013
- } else {
2014
- document.querySelector('#sp-models .tab:last-child').classList.add('active');
2015
- document.getElementById('mt-cloud').classList.add('active');
2016
- }
2017
- }
2018
-
2019
- // --- Models Panel ---
2020
- async function initModelsPanel() {
2021
- try {
2022
- const res = await fetch(`${API}/api/settings/models`);
2023
- modelConfig = await res.json();
2024
- } catch { modelConfig = {}; }
2025
- detectOllama();
2026
- updateProviderStatuses();
2027
- updateModelDropdowns();
2028
- }
2029
-
2030
- async function detectOllama() {
2031
- const statusEl = document.getElementById('ollama-status');
2032
- const modelsEl = document.getElementById('ollama-models');
2033
- const tutorialEl = document.getElementById('ollama-tutorial');
2034
- statusEl.innerHTML = '<div style="display:flex;align-items:center;gap:8px;"><span class="status-dot yellow"></span> 正在检测本地 Ollama...</div>';
2035
- try {
2036
- const res = await fetch(`${API}/api/settings/models/local`);
2037
- const data = await res.json();
2038
- if (data.running) {
2039
- statusEl.innerHTML = '<div style="display:flex;align-items:center;gap:8px;"><span class="status-dot green"></span> <b>Ollama 运行中</b> — 本地模型可用,完全免费</div>';
2040
- tutorialEl.style.display = 'none';
2041
- if (data.models && data.models.length > 0) {
2042
- modelsEl.innerHTML = '<div class="card"><h3 style="font-size:15px;margin-bottom:12px;">已安装的模型</h3>' +
2043
- data.models.map(m => {
2044
- const size = m.size ? `${(m.size / 1e9).toFixed(1)}GB` : '';
2045
- const isChat = modelConfig.chatModel === m.name;
2046
- const isEmbed = modelConfig.embeddingModel === m.name;
2047
- const badge = isChat ? ' <span style="color:var(--accent);font-size:11px;">● 聊天</span>' : isEmbed ? ' <span style="color:var(--green);font-size:11px;">● 记忆</span>' : '';
2048
- return `<div style="display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:1px solid var(--border);">
2049
- <div><span style="font-weight:500;">${m.name}</span>${badge}</div>
2050
- <span style="font-size:12px;color:var(--text-dim);">${size}</span>
2051
- </div>`;
2052
- }).join('') + '</div>';
2053
- // Update dropdowns with local models
2054
- updateModelDropdowns(data.models);
2055
- } else {
2056
- modelsEl.innerHTML = '<div class="card"><p style="color:var(--text-muted);font-size:14px;">Ollama 已运行但没有安装任何模型。请在终端运行:<br><code style="background:var(--bg-hover);padding:2px 8px;border-radius:4px;font-family:var(--mono);">ollama pull qwen2.5:7b</code></p></div>';
2057
- }
2058
- } else {
2059
- statusEl.innerHTML = '<div style="display:flex;align-items:center;gap:8px;"><span class="status-dot red"></span> <b>Ollama 未运行</b> — 按照下面的教程安装</div>';
2060
- modelsEl.innerHTML = '';
2061
- tutorialEl.style.display = 'block';
2062
- }
2063
- } catch {
2064
- statusEl.innerHTML = '<div style="display:flex;align-items:center;gap:8px;"><span class="status-dot red"></span> 无法检测 Ollama</div>';
2065
- tutorialEl.style.display = 'block';
2066
- }
2067
- }
2068
-
2069
- function updateModelDropdowns(localModels) {
2070
- const chatSel = document.getElementById('cfg-chat-model');
2071
- const embedSel = document.getElementById('cfg-embed-model');
2072
- if (localModels && localModels.length > 0) {
2073
- const chatOpts = localModels.map(m => `<option value="${m.name}" ${modelConfig.chatModel === m.name ? 'selected' : ''}>${m.name}${m.name === 'qwen2.5:7b' ? ' ⭐ 推荐' : ''}</option>`).join('');
2074
- const embedOpts = localModels.map(m => `<option value="${m.name}" ${modelConfig.embeddingModel === m.name ? 'selected' : ''}>${m.name}${m.name === 'nomic-embed-text' ? ' ⭐ 推荐' : ''}</option>`).join('');
2075
- chatSel.innerHTML = chatOpts;
2076
- embedSel.innerHTML = embedOpts;
2077
- }
2078
- // Add cloud models if configured
2079
- const providers = modelConfig.providers || {};
2080
- const cloudModels = [];
2081
- if (providers.openai?.apiKey) cloudModels.push({name:'gpt-4o',label:'GPT-4o (OpenAI)'},{name:'gpt-4o-mini',label:'GPT-4o Mini (OpenAI)'});
2082
- if (providers.deepseek?.apiKey) cloudModels.push({name:'deepseek-chat',label:'DeepSeek V3'},{name:'deepseek-reasoner',label:'DeepSeek R1'});
2083
- if (providers.anthropic?.apiKey) cloudModels.push({name:'claude-sonnet-4-20250514',label:'Claude Sonnet (Anthropic)'});
2084
- if (providers.openrouter?.apiKey) cloudModels.push({name:'openrouter/auto',label:'OpenRouter Auto'});
2085
- cloudModels.forEach(m => {
2086
- chatSel.innerHTML += `<option value="${m.name}" ${modelConfig.chatModel === m.name ? 'selected' : ''}>${m.label}</option>`;
2087
- });
2088
- }
2089
-
2090
- function updateProviderStatuses() {
2091
- const providers = modelConfig.providers || {};
2092
- ['openai','deepseek','qwen','anthropic','openrouter'].forEach(p => {
2093
- const el = document.getElementById(`pv-${p}`);
2094
- if (!el) return;
2095
- if (providers[p]?.apiKey) {
2096
- el.innerHTML = '<span style="color:var(--green);">✅ 已配置</span>';
2097
- el.closest('.provider-card')?.classList.add('configured');
2098
- } else {
2099
- el.innerHTML = '未配置';
2100
- el.closest('.provider-card')?.classList.remove('configured');
2101
- }
2102
- });
2103
- }
2104
-
2105
- async function saveModelAssignment() {
2106
- const chatModel = document.getElementById('cfg-chat-model').value;
2107
- const embeddingModel = document.getElementById('cfg-embed-model').value;
2108
- try {
2109
- await fetch(`${API}/api/settings/models`, {
2110
- method: 'PUT', headers: {'Content-Type':'application/json'},
2111
- body: JSON.stringify({ chatModel, embeddingModel })
2112
- });
2113
- modelConfig.chatModel = chatModel;
2114
- modelConfig.embeddingModel = embeddingModel;
2115
- } catch {}
2116
- }
2117
-
2118
- // --- Provider Dialog ---
2119
- const PROVIDER_INFO = {
2120
- openai: { name: 'OpenAI', desc: '需要 OpenAI 账号。获取 Key: platform.openai.com/api-keys', placeholder: 'sk-...' },
2121
- deepseek: { name: 'DeepSeek', desc: '国产大模型,性价比极高。获取 Key: platform.deepseek.com', placeholder: 'sk-...' },
2122
- qwen: { name: '通义千问', desc: '阿里云大模型。获取 Key: dashscope.console.aliyun.com', placeholder: 'sk-...' },
2123
- anthropic: { name: 'Anthropic', desc: 'Claude 系列模型。获取 Key: console.anthropic.com', placeholder: 'sk-ant-...' },
2124
- openrouter: { name: 'OpenRouter', desc: '100+ 模型聚合平台。获取 Key: openrouter.ai/keys', placeholder: 'sk-or-...' },
2125
- };
2126
-
2127
- function configureProvider(provider) {
2128
- currentProvider = provider;
2129
- const info = PROVIDER_INFO[provider] || {};
2130
- document.getElementById('pd-title').textContent = `配置 ${info.name || provider}`;
2131
- document.getElementById('pd-desc').textContent = info.desc || '';
2132
- document.getElementById('pd-apikey').placeholder = info.placeholder || 'API Key';
2133
- document.getElementById('pd-apikey').value = modelConfig.providers?.[provider]?.apiKey || '';
2134
- document.getElementById('pd-baseurl').value = modelConfig.providers?.[provider]?.baseUrl || '';
2135
- document.getElementById('pd-test-result').innerHTML = '';
2136
- document.getElementById('pd-baseurl-group').style.display = (provider === 'qwen' || provider === 'openrouter') ? 'block' : 'none';
2137
- document.getElementById('provider-dialog').classList.add('show');
2138
- }
2139
- function closeProviderDialog() { document.getElementById('provider-dialog').classList.remove('show'); currentProvider = null; }
2140
-
2141
- async function testProvider() {
2142
- const apiKey = document.getElementById('pd-apikey').value.trim();
2143
- const baseUrl = document.getElementById('pd-baseurl').value.trim();
2144
- const resultEl = document.getElementById('pd-test-result');
2145
- if (!apiKey) { resultEl.innerHTML = '<span style="color:var(--yellow);">请先填入 API Key</span>'; return; }
2146
- resultEl.innerHTML = '<span style="color:var(--text-muted);">⏳ 测试中...</span>';
2147
- try {
2148
- const res = await fetch(`${API}/api/settings/models/test`, {
2149
- method: 'POST', headers: {'Content-Type':'application/json'},
2150
- body: JSON.stringify({ provider: currentProvider, apiKey, baseUrl: baseUrl || undefined })
2151
- });
2152
- const data = await res.json();
2153
- resultEl.innerHTML = data.success ? '<span style="color:var(--green);">✅ 连接成功!</span>' : `<span style="color:var(--red);">❌ 连接失败 (${data.error || data.statusCode})</span>`;
2154
- } catch(e) {
2155
- resultEl.innerHTML = `<span style="color:var(--red);">❌ 网络错误</span>`;
2156
- }
2157
- }
2158
-
2159
- async function saveProvider() {
2160
- const apiKey = document.getElementById('pd-apikey').value.trim();
2161
- const baseUrl = document.getElementById('pd-baseurl').value.trim();
2162
- if (!modelConfig.providers) modelConfig.providers = {};
2163
- modelConfig.providers[currentProvider] = { apiKey, baseUrl: baseUrl || undefined };
2164
- try {
2165
- await fetch(`${API}/api/settings/models`, {
2166
- method: 'PUT', headers: {'Content-Type':'application/json'},
2167
- body: JSON.stringify({ providers: modelConfig.providers })
2168
- });
2169
- } catch {}
2170
- updateProviderStatuses();
2171
- updateModelDropdowns();
2172
- closeProviderDialog();
2173
- }
2174
-
2175
- // --- Channels Panel ---
2176
- const CHANNELS = [
2177
- { id: 'telegram', name: 'Telegram', icon: '✈️', fields: [{key:'botToken',label:'Bot Token',placeholder:'123456:ABC-DEF...',help:'从 @BotFather 获取。<a href="https://t.me/botfather" target="_blank">打开 BotFather →</a>'}] },
2178
- { id: 'wechat', name: '微信', icon: '💬', fields: [], comingSoon: true },
2179
- { id: 'feishu', name: '飞书', icon: '🐦', fields: [{key:'appId',label:'App ID',placeholder:'cli_...'},{key:'appSecret',label:'App Secret',placeholder:'',type:'password'}] },
2180
- { id: 'discord', name: 'Discord', icon: '🎮', fields: [{key:'botToken',label:'Bot Token',placeholder:'',type:'password'}] },
2181
- { id: 'slack', name: 'Slack', icon: '💼', fields: [{key:'botToken',label:'Bot Token',placeholder:'xoxb-...',type:'password'}] },
2182
- { id: 'email', name: 'Email', icon: '📧', fields: [{key:'imapHost',label:'IMAP Host',placeholder:'imap.gmail.com'},{key:'smtpHost',label:'SMTP Host',placeholder:'smtp.gmail.com'},{key:'email',label:'Email',placeholder:'agent@example.com'},{key:'password',label:'Password',placeholder:'',type:'password'}] },
2183
- { id: 'web', name: 'Web', icon: '🌐', fields: [], alwaysOn: true },
2184
- { id: 'whatsapp', name: 'WhatsApp', icon: '📱', fields: [{key:'phoneId',label:'Phone Number ID',placeholder:''},{key:'accessToken',label:'Access Token',placeholder:'',type:'password'}] },
2185
- ];
2186
-
2187
- let channelConfigs = {};
2188
-
2189
- async function initChannelsPanel() {
2190
- try {
2191
- const res = await fetch(`${API}/api/settings/channels`);
2192
- channelConfigs = await res.json();
2193
- } catch { channelConfigs = {}; }
2194
- renderChannels();
2195
- }
2196
-
2197
- function renderChannels() {
2198
- document.getElementById('channels-grid').innerHTML = CHANNELS.map(ch => {
2199
- const cfg = channelConfigs[ch.id] || {};
2200
- const connected = ch.alwaysOn || (cfg && Object.keys(cfg).some(k => k !== 'updated' && cfg[k]));
2201
- const statusDot = ch.comingSoon ? 'yellow' : connected ? 'green' : 'red';
2202
- const statusText = ch.comingSoon ? '即将支持' : connected ? '已连接' : '未配置';
2203
- return `<div class="card channel-card" onclick="${ch.comingSoon ? '' : `configureChannel('${ch.id}')`}" style="${ch.comingSoon ? 'opacity:0.6;cursor:default;' : ''}">
2204
- <div class="ch-icon">${ch.icon}</div>
2205
- <div class="ch-info">
2206
- <div class="ch-name">${ch.name}</div>
2207
- <div class="ch-status"><span class="status-dot ${statusDot}"></span> ${statusText}</div>
2208
- </div>
2209
- ${!ch.comingSoon && !ch.alwaysOn ? '<span style="color:var(--text-dim);font-size:18px;">›</span>' : ''}
2210
- ${ch.alwaysOn ? '<span style="font-size:12px;color:var(--green);">默认开启</span>' : ''}
2211
- </div>`;
2212
- }).join('');
2213
- }
2214
-
2215
- function configureChannel(chId) {
2216
- const ch = CHANNELS.find(c => c.id === chId);
2217
- if (!ch || ch.comingSoon) return;
2218
- if (ch.alwaysOn) return;
2219
- currentChannel = chId;
2220
- const cfg = channelConfigs[chId] || {};
2221
- document.getElementById('cd-title').textContent = `配置 ${ch.name}`;
2222
- document.getElementById('cd-desc').textContent = '';
2223
- document.getElementById('cd-fields').innerHTML = ch.fields.map(f =>
2224
- `<div class="form-group">
2225
- <label class="label">${f.label}</label>
2226
- <input class="input" id="cf-${f.key}" type="${f.type || 'text'}" placeholder="${f.placeholder || ''}" value="${cfg[f.key] || ''}">
2227
- ${f.help ? `<p style="font-size:12px;color:var(--text-dim);margin-top:4px;">${f.help}</p>` : ''}
2228
- </div>`
2229
- ).join('');
2230
- document.getElementById('channel-dialog').classList.add('show');
2231
- }
2232
- function closeChannelDialog() { document.getElementById('channel-dialog').classList.remove('show'); currentChannel = null; }
2233
-
2234
- async function saveChannel() {
2235
- const ch = CHANNELS.find(c => c.id === currentChannel);
2236
- if (!ch) return;
2237
- const cfg = {};
2238
- ch.fields.forEach(f => { cfg[f.key] = document.getElementById(`cf-${f.key}`)?.value?.trim() || ''; });
2239
- try {
2240
- await fetch(`${API}/api/settings/channels/${currentChannel}`, {
2241
- method: 'PUT', headers: {'Content-Type':'application/json'},
2242
- body: JSON.stringify(cfg)
2243
- });
2244
- channelConfigs[currentChannel] = cfg;
2245
- } catch {}
2246
- renderChannels();
2247
- closeChannelDialog();
2248
- }
2249
-
2250
- // --- Memory Panel (DeepBrain iframe) ---
2251
- async function initMemoryPanel() {
2252
- const container = document.getElementById('memory-module-frame');
2253
- const running = await checkModulePort(4001);
2254
- if (running) {
2255
- container.innerHTML = `<div class="module-frame-container"><iframe src="http://localhost:4001" title="DeepBrain 记忆管理"></iframe></div>`;
2256
- } else {
2257
- container.innerHTML = `<div class="card module-frame-fallback">
2258
- <div class="mf-icon">🧠</div>
2259
- <h3 style="margin-bottom:8px;">DeepBrain 未运行</h3>
2260
- <p style="color:var(--text-muted);font-size:14px;margin-bottom:16px;">记忆管理由 DeepBrain 模块提供(端口 4001)</p>
2261
- <a href="http://localhost:4001" target="_blank" class="btn btn-primary">🔗 打开记忆管理</a>
2262
- <p style="color:var(--text-dim);font-size:12px;margin-top:12px;">如果按钮无法打开,请先启动 DeepBrain 服务</p>
2263
- </div>`;
2264
- }
2265
- }
2266
-
2267
- // --- Role Panel (Workstation iframe) ---
2268
- async function initRolePanel() {
2269
- const container = document.getElementById('role-module-frame');
2270
- const running = await checkModulePort(4003);
2271
- if (running) {
2272
- container.innerHTML = `<div class="module-frame-container"><iframe src="http://localhost:4003" title="Workstation 角色编辑"></iframe></div>`;
2273
- } else {
2274
- container.innerHTML = `<div class="card module-frame-fallback">
2275
- <div class="mf-icon">👤</div>
2276
- <h3 style="margin-bottom:8px;">Workstation 未运行</h3>
2277
- <p style="color:var(--text-muted);font-size:14px;margin-bottom:16px;">角色编辑由 Workstation 模块提供(端口 4003)</p>
2278
- <a href="http://localhost:4003" target="_blank" class="btn btn-primary">🔗 打开角色编辑</a>
2279
- <p style="color:var(--text-dim);font-size:12px;margin-top:12px;">如果按钮无法打开,请先启动 Workstation 服务</p>
2280
- </div>`;
2281
- }
2282
- }
2283
-
2284
- async function checkModulePort(port) {
2285
- try {
2286
- const res = await fetch(`${API}/api/modules`);
2287
- const data = await res.json();
2288
- const mod = (data.modules || []).find(m => m.port === port);
2289
- return mod?.running || false;
2290
- } catch { return false; }
2291
- }
2292
-
2293
- // --- Status Panel ---
2294
- async function refreshStatus() {
2295
- try {
2296
- const res = await fetch(`${API}/api/settings/status`);
2297
- const data = await res.json();
2298
-
2299
- // Overview cards
2300
- const upHrs = Math.floor(data.uptime / 3600);
2301
- const upMins = Math.floor((data.uptime % 3600) / 60);
2302
- const memMB = Math.round((data.memory?.rss || 0) / 1048576);
2303
-
2304
- document.getElementById('status-overview').innerHTML = `
2305
- <div class="card-grid" style="margin-bottom:16px;">
2306
- <div class="card stat-card">
2307
- <div style="display:flex;align-items:center;justify-content:center;gap:8px;margin-bottom:8px;">
2308
- <span class="status-dot green"></span><span style="font-size:14px;font-weight:600;">运行中</span>
2309
- </div>
2310
- <div class="stat-value">${upHrs}h ${upMins}m</div>
2311
- <div class="stat-label">运行时间</div>
2312
- </div>
2313
- <div class="card stat-card">
2314
- <div class="stat-value">${memMB} MB</div>
2315
- <div class="stat-label">内存占用</div>
2316
- </div>
2317
- <div class="card stat-card">
2318
- <div class="stat-value">${(data.modules || []).filter(m => m.running).length}/${(data.modules || []).length}</div>
2319
- <div class="stat-label">模块在线</div>
2320
- </div>
2321
- </div>
2322
- <div class="card" style="margin-bottom:16px;">
2323
- <h3 style="font-size:15px;margin-bottom:12px;">模块状态</h3>
2324
- ${(data.modules || []).map(m => `<div style="display:flex;align-items:center;gap:10px;padding:6px 0;">
2325
- <span class="status-dot ${m.running ? 'green' : 'red'}"></span>
2326
- <span>${m.icon} ${m.name}</span>
2327
- <span style="color:var(--text-dim);font-size:12px;margin-left:auto;">:${m.port}</span>
2328
- </div>`).join('')}
2329
- </div>
2330
- `;
2331
-
2332
- // Logs
2333
- const logsEl = document.getElementById('status-logs');
2334
- if (data.logs && data.logs.length > 0) {
2335
- logsEl.textContent = data.logs.join('\n');
2336
- logsEl.scrollTop = logsEl.scrollHeight;
2337
- } else {
2338
- logsEl.textContent = '暂无日志。Agent 运行后日志会显示在这里。';
2339
- }
2340
- } catch {
2341
- document.getElementById('status-overview').innerHTML = '<div class="card"><p style="color:var(--text-muted);">无法获取状态信息</p></div>';
2342
- }
2343
- }
2344
-
2345
- // --- Usage Panel ---
2346
- async function refreshUsage() {
2347
- try {
2348
- const res = await fetch(`${API}/api/settings/usage`);
2349
- const data = await res.json();
2350
- const totalTokens = data.totalTokens || 0;
2351
- const totalCost = data.totalCost || 0;
2352
- const byModel = data.byModel || {};
2353
- const daily = data.daily || [];
2354
-
2355
- document.getElementById('usage-stats').innerHTML = `
2356
- <div class="card-grid" style="margin-bottom:24px;">
2357
- <div class="card stat-card">
2358
- <div class="stat-value">${totalTokens > 1000 ? (totalTokens/1000).toFixed(1) + 'K' : totalTokens}</div>
2359
- <div class="stat-label">总 Token 消耗</div>
2360
- </div>
2361
- <div class="card stat-card">
2362
- <div class="stat-value">$${totalCost.toFixed(4)}</div>
2363
- <div class="stat-label">估算费用</div>
2364
- </div>
2365
- <div class="card stat-card">
2366
- <div class="stat-value">${Object.keys(byModel).length || 0}</div>
2367
- <div class="stat-label">使用模型数</div>
2368
- </div>
2369
- </div>
2370
- ${Object.keys(byModel).length > 0 ? `
2371
- <div class="card" style="margin-bottom:16px;">
2372
- <h3 style="font-size:15px;margin-bottom:12px;">按模型分布</h3>
2373
- ${Object.entries(byModel).map(([m, v]) => `<div style="display:flex;justify-content:space-between;padding:6px 0;border-bottom:1px solid var(--border);">
2374
- <span style="font-size:14px;">${m}</span>
2375
- <span style="font-size:13px;color:var(--text-muted);">${v.tokens || 0} tokens · $${(v.cost || 0).toFixed(4)}</span>
2376
- </div>`).join('')}
2377
- </div>
2378
- ` : ''}
2379
- ${totalTokens === 0 ? `
2380
- <div class="card" style="text-align:center;padding:40px;">
2381
- <div style="font-size:36px;margin-bottom:12px;">📊</div>
2382
- <p style="color:var(--text-muted);">还没有使用记录。开始和 Agent 聊天后,用量数据会自动记录在这里。</p>
2383
- ${modelConfig.mode === 'local' || !modelConfig.mode ? '<p style="color:var(--green);font-size:13px;margin-top:8px;">💡 使用本地模型完全免费,不产生费用</p>' : ''}
2384
- </div>
2385
- ` : ''}
2386
- `;
2387
- } catch {
2388
- document.getElementById('usage-stats').innerHTML = '<div class="card"><p style="color:var(--text-muted);">无法获取用量数据</p></div>';
2389
- }
2390
- }
2391
-
2392
- // === Web Search Settings ===
2393
- async function initSearchPanel() {
2394
- try {
2395
- const res = await fetch(`${API}/api/settings/search`);
2396
- const cfg = await res.json();
2397
- document.getElementById('search-enabled').checked = cfg.enabled !== false;
2398
- document.getElementById('search-engine').value = cfg.defaultEngine || 'duckduckgo';
2399
- updateSearchEngineUI(cfg.defaultEngine || 'duckduckgo');
2400
- if (cfg.engines) {
2401
- const eng = cfg.engines[cfg.defaultEngine];
2402
- if (eng?.apiKey) document.getElementById('search-apikey').value = eng.apiKey;
2403
- if (eng?.baseUrl) document.getElementById('search-baseurl').value = eng.baseUrl;
2404
- }
2405
- } catch { /* defaults are fine */ }
2406
- }
2407
-
2408
- function updateSearchEngineUI(engine) {
2409
- const needsKey = ['brave', 'google'].includes(engine);
2410
- const needsUrl = engine === 'searxng';
2411
- document.getElementById('search-apikey-group').style.display = needsKey ? '' : 'none';
2412
- document.getElementById('search-baseurl-group').style.display = needsUrl ? '' : 'none';
2413
- if (engine === 'brave') document.getElementById('search-apikey-label').textContent = 'Brave Search API Key';
2414
- if (engine === 'google') document.getElementById('search-apikey-label').textContent = 'Google API Key:CX';
2415
- }
2416
-
2417
- async function updateSearchConfig() {
2418
- const engine = document.getElementById('search-engine').value;
2419
- updateSearchEngineUI(engine);
2420
- const cfg = {
2421
- enabled: document.getElementById('search-enabled').checked,
2422
- defaultEngine: engine,
2423
- engines: {}
2424
- };
2425
- cfg.engines[engine] = { enabled: true };
2426
- const apiKey = document.getElementById('search-apikey').value;
2427
- const baseUrl = document.getElementById('search-baseurl').value;
2428
- if (apiKey) cfg.engines[engine].apiKey = apiKey;
2429
- if (baseUrl) cfg.engines[engine].baseUrl = baseUrl;
2430
- cfg.engines.duckduckgo = { enabled: true };
2431
- try {
2432
- await fetch(`${API}/api/settings/search`, {
2433
- method: 'PUT', headers: { 'Content-Type': 'application/json' },
2434
- body: JSON.stringify(cfg)
2435
- });
2436
- } catch { /* silent */ }
2437
- }
2438
-
2439
- async function testSearch() {
2440
- const el = document.getElementById('search-test-result');
2441
- el.innerHTML = '<span style="color:var(--yellow);">🔍 正在搜索...</span>';
2442
- try {
2443
- const res = await fetch(`${API}/api/settings/search/test`, {
2444
- method: 'POST', headers: { 'Content-Type': 'application/json' },
2445
- body: JSON.stringify({ query: 'hello world test' })
2446
- });
2447
- const data = await res.json();
2448
- if (data.success && data.results?.length) {
2449
- el.innerHTML = `<span style="color:var(--green);">✅ 搜索成功!找到 ${data.results.length} 条结果</span><br>` +
2450
- data.results.map(r => `<div style="margin-top:8px;font-size:12px;"><a href="${r.url}" target="_blank">${r.title}</a><br><span style="color:var(--text-muted);">${r.snippet?.slice(0,100)}</span></div>`).join('');
2451
- } else {
2452
- el.innerHTML = `<span style="color:var(--red);">❌ ${data.error || '未找到结果'}</span>`;
2453
- }
2454
- } catch (e) {
2455
- el.innerHTML = `<span style="color:var(--red);">❌ 测试失败: ${e.message}</span>`;
2456
- }
2457
- }
2458
-
2459
- // === Health Dashboard ===
2460
- async function loadHealthDashboard() {
2461
- const el = document.getElementById('health-section');
2462
- if (!el) return;
2463
- try {
2464
- const [modRes, ollamaRes] = await Promise.all([
2465
- fetch(`${API}/api/modules`),
2466
- fetch(`${API}/api/settings/models/local`),
2467
- ]);
2468
- const modData = await modRes.json();
2469
- const ollamaData = await ollamaRes.json();
2470
- const modules = modData.modules || [];
2471
- const runningCount = modules.filter(m => m.running).length;
2472
- el.innerHTML = `
2473
- <div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:12px;">
2474
- ${modules.map(m => `
2475
- <div class="card" style="flex:1;min-width:140px;display:flex;align-items:center;gap:10px;padding:12px 14px;">
2476
- <span class="status-dot ${m.running ? 'green' : 'red'}"></span>
2477
- <span style="font-size:13px;">${m.icon} ${m.name}</span>
2478
- <span style="font-size:11px;color:var(--text-dim);margin-left:auto;">:${m.port}</span>
2479
- </div>
2480
- `).join('')}
2481
- <div class="card" style="flex:1;min-width:140px;display:flex;align-items:center;gap:10px;padding:12px 14px;">
2482
- <span class="status-dot ${ollamaData.running ? 'green' : 'red'}"></span>
2483
- <span style="font-size:13px;">🦙 Ollama</span>
2484
- <span style="font-size:11px;color:var(--text-dim);margin-left:auto;">${ollamaData.running ? (ollamaData.models?.length || 0) + ' models' : 'offline'}</span>
2485
- </div>
2486
- </div>
2487
- `;
2488
- } catch {
2489
- el.innerHTML = '';
2490
- }
2491
- }
2492
-
2493
- // === First Run Wizard ===
2494
- let frStep = 1;
2495
- let frSelectedTemplate = null;
2496
- let frCreatedAgentId = null;
2497
-
2498
- async function checkFirstRun() {
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
- }
2524
- }
2525
-
2526
- function showFirstRunWizard(data) {
2527
- frStep = 1;
2528
- const overlay = document.getElementById('first-run-overlay');
2529
- overlay.style.display = 'flex';
2530
- frRenderStep();
2531
- if (data?.ollamaDetected) {
2532
- const statusEl = document.getElementById('fr-ollama-status');
2533
- if (statusEl) {
2534
- statusEl.innerHTML = `<div style="display:flex;align-items:center;gap:8px;color:var(--green);"><span class="status-dot green"></span> <b>Ollama detected!</b> ${data.ollamaModels?.length ? data.ollamaModels.length + ' models available.' : ''} Local AI is free.</div>`;
2535
- const choiceEl = document.getElementById('fr-model-choice');
2536
- if (choiceEl) choiceEl.style.display = 'block';
2537
- const sel = document.getElementById('fr-model-select');
2538
- if (sel && data.ollamaModels?.length) {
2539
- sel.innerHTML = data.ollamaModels.map(m => `<option value="${m.name}">${m.name} (local)</option>`).join('') + '<option value="gpt-4o-mini">GPT-4o Mini (cloud)</option>';
2540
- }
2541
- }
2542
- } else {
2543
- detectFrOllama();
2544
- }
2545
- }
2546
-
2547
- async function detectFrOllama() {
2548
- try {
2549
- const res = await fetch(`${API}/api/settings/models/local`);
2550
- const data = await res.json();
2551
- const statusEl = document.getElementById('fr-ollama-status');
2552
- const choiceEl = document.getElementById('fr-model-choice');
2553
- if (!statusEl) return;
2554
- if (data.running) {
2555
- statusEl.innerHTML = `<div style="display:flex;align-items:center;gap:8px;color:var(--green);"><span class="status-dot green"></span> <b>Ollama running</b> — free local models available!</div>`;
2556
- if (choiceEl) choiceEl.style.display = 'block';
2557
- const sel = document.getElementById('fr-model-select');
2558
- if (sel && data.models?.length) {
2559
- sel.innerHTML = data.models.map(m => `<option value="${m.name}">${m.name} (local)</option>`).join('') + '<option value="gpt-4o-mini">GPT-4o Mini (cloud)</option>';
2560
- }
2561
- } else {
2562
- statusEl.innerHTML = `<div style="display:flex;align-items:center;gap:8px;"><span class="status-dot red"></span> Ollama not detected — you can use cloud models or <a href="https://ollama.com" target="_blank">install Ollama</a> for free local AI.</div>`;
2563
- if (choiceEl) choiceEl.style.display = 'block';
2564
- }
2565
- } catch {}
2566
- }
2567
-
2568
- function frRenderStep() {
2569
- for (let i = 1; i <= 4; i++) {
2570
- const stepEl = document.getElementById(`fr-step-${i}`);
2571
- const panelEl = document.getElementById(`fr-panel-${i}`);
2572
- if (stepEl) stepEl.className = 'wizard-step' + (i < frStep ? ' done' : i === frStep ? ' active' : '');
2573
- if (panelEl) panelEl.className = 'wizard-panel' + (i === frStep ? ' active' : '');
2574
- }
2575
- }
2576
-
2577
- function frNext() {
2578
- if (frStep === 3 && !frSelectedTemplate) {
2579
- frSelectedTemplate = 'customer-service';
2580
- }
2581
- if (frStep === 3) {
2582
- frStep = 4;
2583
- frRenderStep();
2584
- frCreateAgent();
2585
- return;
2586
- }
2587
- if (frStep < 4) { frStep++; frRenderStep(); }
2588
- }
2589
-
2590
- function frBack() {
2591
- if (frStep > 1) { frStep--; frRenderStep(); }
2592
- }
2593
-
2594
- function frSelectTemplate(id) {
2595
- frSelectedTemplate = id;
2596
- document.querySelectorAll('#fr-template-list .card').forEach(c => {
2597
- c.style.borderColor = '';
2598
- c.style.background = '';
2599
- });
2600
- const el = document.getElementById(`fr-tpl-${id}`);
2601
- if (el) { el.style.borderColor = 'var(--accent)'; el.style.background = 'var(--accent-light)'; }
2602
- }
2603
-
2604
- async function frCreateAgent() {
2605
- const model = document.getElementById('fr-model-select')?.value || 'qwen2.5:7b';
2606
- try {
2607
- // Save first-run complete
2608
- await fetch(`${API}/api/first-run/complete`, {
2609
- method: 'POST',
2610
- headers: { 'Content-Type': 'application/json' },
2611
- body: JSON.stringify({ templateId: frSelectedTemplate, model }),
2612
- });
2613
- // Create the agent
2614
- const res = await fetch(`${API}/api/agents`, {
2615
- method: 'POST',
2616
- headers: { 'Content-Type': 'application/json' },
2617
- body: JSON.stringify({ name: '', templateId: frSelectedTemplate || 'customer-service', model }),
2618
- });
2619
- const agent = await res.json();
2620
- frCreatedAgentId = agent.id;
2621
- document.getElementById('fr-creating').style.display = 'none';
2622
- document.getElementById('fr-done').style.display = 'block';
2623
- await loadAgents();
2624
- } catch(e) {
2625
- document.getElementById('fr-creating').innerHTML = `<div style="color:var(--red);">Error: ${e.message}</div>`;
2626
- }
2627
- }
2628
-
2629
- function frFinish() {
2630
- document.getElementById('first-run-overlay').style.display = 'none';
2631
- if (frCreatedAgentId) openChat(frCreatedAgentId);
2632
- else navigate('dashboard');
2633
- }
2634
-
2635
- // === Drag & drop document upload ===
2636
- const chatArea = document.getElementById('chat-messages');
2637
- if (chatArea) {
2638
- chatArea.addEventListener('dragover', (e) => { e.preventDefault(); chatArea.style.outline = '2px dashed var(--primary)'; });
2639
- chatArea.addEventListener('dragleave', () => { chatArea.style.outline = ''; });
2640
- chatArea.addEventListener('drop', (e) => {
2641
- e.preventDefault();
2642
- chatArea.style.outline = '';
2643
- const file = e.dataTransfer?.files?.[0];
2644
- if (file) {
2645
- const dt = new DataTransfer();
2646
- dt.items.add(file);
2647
- const inp = document.getElementById('doc-upload-input');
2648
- inp.files = dt.files;
2649
- handleDocUpload(inp);
2650
- }
2651
- });
2652
- }
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
-
2815
- // === Start ===
2816
- init();
2817
-
2818
- // =============================================
2819
- // === Schedules Management ===
2820
- // =============================================
2821
- let editingScheduleId = null;
2822
-
2823
- async function loadSchedules() {
2824
- try {
2825
- const res = await fetch('/api/schedules');
2826
- const tasks = await res.json();
2827
- const list = Array.isArray(tasks) ? tasks : [];
2828
- renderSchedules(list);
2829
- } catch(e) {
2830
- console.error('Failed to load schedules:', e);
2831
- renderSchedules([]);
2832
- }
2833
- }
2834
-
2835
- function renderSchedules(tasks) {
2836
- const listEl = document.getElementById('schedules-list');
2837
- const emptyEl = document.getElementById('schedules-empty');
2838
- if (!tasks.length) {
2839
- listEl.innerHTML = '';
2840
- emptyEl.style.display = '';
2841
- return;
2842
- }
2843
- emptyEl.style.display = 'none';
2844
- listEl.innerHTML = tasks.map(t => `
2845
- <div class="card" style="margin-bottom:12px;display:flex;align-items:center;gap:16px;">
2846
- <div style="font-size:28px;">⏰</div>
2847
- <div style="flex:1;">
2848
- <div style="font-size:15px;font-weight:600;">${esc(t.name)}</div>
2849
- <div style="font-size:12px;color:var(--text-muted);">${esc(t.description || '')}</div>
2850
- <div style="font-size:11px;color:var(--text-dim);margin-top:4px;">
2851
- ${esc(t.schedule)} · ${t.outputChannel || 'web'} · Next: ${t.nextRun ? new Date(t.nextRun).toLocaleString() : 'N/A'}
2852
- </div>
2853
- </div>
2854
- <div style="display:flex;gap:8px;align-items:center;">
2855
- <label style="position:relative;display:inline-block;width:40px;height:22px;cursor:pointer;">
2856
- <input type="checkbox" ${t.enabled ? 'checked' : ''} onchange="toggleSchedule('${t.id}', this.checked)" style="opacity:0;width:0;height:0;">
2857
- <span style="position:absolute;inset:0;border-radius:11px;background:${t.enabled ? 'var(--green)' : 'var(--border)'};transition:0.3s;"></span>
2858
- <span style="position:absolute;top:2px;left:${t.enabled ? '20px' : '2px'};width:18px;height:18px;border-radius:50%;background:white;transition:0.3s;"></span>
2859
- </label>
2860
- <button class="btn btn-sm btn-secondary" onclick="runScheduleNow('${t.id}')" title="Run now">▶️</button>
2861
- <button class="btn btn-sm btn-secondary" onclick="editSchedule('${t.id}')" title="Edit">✏️</button>
2862
- <button class="btn btn-sm btn-danger" onclick="deleteSchedule('${t.id}')" title="Delete">🗑</button>
2863
- </div>
2864
- </div>
2865
- `).join('');
2866
- }
2867
-
2868
- function esc(s) { const d = document.createElement('div'); d.textContent = s; return d.innerHTML; }
2869
-
2870
- function showScheduleForm(task) {
2871
- editingScheduleId = task ? task.id : null;
2872
- document.getElementById('schedule-form').style.display = '';
2873
- document.getElementById('schedule-form-title').textContent = task ? 'Edit Task' : 'New Scheduled Task';
2874
- document.getElementById('sched-name').value = task ? task.name : '';
2875
- document.getElementById('sched-frequency').value = task ? task.frequency : 'daily';
2876
- document.getElementById('sched-time').value = task ? (task.time || '09:00') : '09:00';
2877
- document.getElementById('sched-cron').value = task ? task.schedule : '';
2878
- document.getElementById('sched-desc').value = task ? task.description : '';
2879
- document.getElementById('sched-channel').value = task ? task.outputChannel : 'web';
2880
- onSchedFreqChange();
2881
- populateSchedAgentSelect().then(() => {
2882
- if (task?.agentId) document.getElementById('sched-agent').value = task.agentId;
2883
- });
2884
- }
2885
-
2886
- function hideScheduleForm() {
2887
- document.getElementById('schedule-form').style.display = 'none';
2888
- editingScheduleId = null;
2889
- }
2890
-
2891
- function onSchedFreqChange() {
2892
- const freq = document.getElementById('sched-frequency').value;
2893
- document.getElementById('sched-time-group').style.display = freq === 'custom' ? 'none' : '';
2894
- document.getElementById('sched-cron-group').style.display = freq === 'custom' ? '' : 'none';
2895
- }
2896
-
2897
- async function saveSchedule() {
2898
- const data = {
2899
- name: document.getElementById('sched-name').value.trim(),
2900
- frequency: document.getElementById('sched-frequency').value,
2901
- time: document.getElementById('sched-time').value,
2902
- schedule: document.getElementById('sched-frequency').value === 'custom' ? document.getElementById('sched-cron').value.trim() : '',
2903
- description: document.getElementById('sched-desc').value.trim(),
2904
- agentId: document.getElementById('sched-agent').value,
2905
- outputChannel: document.getElementById('sched-channel').value,
2906
- enabled: true,
2907
- };
2908
- if (!data.name) { alert('Task name is required'); return; }
2909
- try {
2910
- if (editingScheduleId) {
2911
- await fetch(`/api/schedules/${editingScheduleId}`, { method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify(data) });
2912
- } else {
2913
- await fetch('/api/schedules', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(data) });
2914
- }
2915
- hideScheduleForm();
2916
- loadSchedules();
2917
- } catch(e) { alert('Failed to save: ' + e.message); }
2918
- }
2919
-
2920
- async function toggleSchedule(id, enabled) {
2921
- await fetch(`/api/schedules/${id}`, { method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify({ enabled }) });
2922
- loadSchedules();
2923
- }
2924
-
2925
- async function deleteSchedule(id) {
2926
- if (!confirm('Delete this task?')) return;
2927
- await fetch(`/api/schedules/${id}`, { method: 'DELETE' });
2928
- loadSchedules();
2929
- }
2930
-
2931
- async function runScheduleNow(id) {
2932
- await fetch(`/api/schedules/${id}/run`, { method: 'POST' });
2933
- alert('Task executed!');
2934
- loadSchedules();
2935
- }
2936
-
2937
- async function editSchedule(id) {
2938
- const res = await fetch('/api/schedules');
2939
- const tasks = await res.json();
2940
- const list = Array.isArray(tasks) ? tasks : [];
2941
- const task = list.find(t => t.id === id);
2942
- if (task) showScheduleForm(task);
2943
- }
2944
-
2945
- // =============================================
2946
- // === Voice Interaction ===
2947
- // =============================================
2948
- let voiceRecognition = null;
2949
- let isRecording = false;
2950
-
2951
- function toggleVoiceInput() {
2952
- if (isRecording) {
2953
- stopVoiceInput();
2954
- } else {
2955
- startVoiceInput();
2956
- }
2957
- }
2958
-
2959
- function startVoiceInput() {
2960
- const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
2961
- if (!SpeechRecognition) {
2962
- alert('Speech recognition is not supported in this browser. Try Chrome.');
2963
- return;
2964
- }
2965
- voiceRecognition = new SpeechRecognition();
2966
- voiceRecognition.continuous = false;
2967
- voiceRecognition.interimResults = true;
2968
- voiceRecognition.lang = navigator.language || 'en-US';
2969
-
2970
- voiceRecognition.onstart = () => {
2971
- isRecording = true;
2972
- const btn = document.getElementById('voice-btn');
2973
- btn.style.background = 'var(--red)';
2974
- btn.style.color = 'white';
2975
- btn.style.borderColor = 'var(--red)';
2976
- btn.textContent = '⏹';
2977
- };
2978
-
2979
- voiceRecognition.onresult = (event) => {
2980
- let transcript = '';
2981
- for (let i = event.resultIndex; i < event.results.length; i++) {
2982
- transcript += event.results[i][0].transcript;
2983
- }
2984
- document.getElementById('chat-input').value = transcript;
2985
- };
2986
-
2987
- voiceRecognition.onend = () => {
2988
- isRecording = false;
2989
- const btn = document.getElementById('voice-btn');
2990
- btn.style.background = 'transparent';
2991
- btn.style.color = '';
2992
- btn.style.borderColor = 'var(--border)';
2993
- btn.textContent = '🎤';
2994
- // Auto-send if we got text
2995
- const input = document.getElementById('chat-input');
2996
- if (input.value.trim()) {
2997
- sendMessage();
2998
- }
2999
- };
3000
-
3001
- voiceRecognition.onerror = (event) => {
3002
- console.error('Speech recognition error:', event.error);
3003
- isRecording = false;
3004
- const btn = document.getElementById('voice-btn');
3005
- btn.style.background = 'transparent';
3006
- btn.style.color = '';
3007
- btn.style.borderColor = 'var(--border)';
3008
- btn.textContent = '🎤';
3009
- };
3010
-
3011
- voiceRecognition.start();
3012
- }
3013
-
3014
- function stopVoiceInput() {
3015
- if (voiceRecognition) {
3016
- voiceRecognition.stop();
3017
- }
3018
- }
3019
-
3020
- function speakText(text) {
3021
- if (!window.speechSynthesis) return;
3022
- window.speechSynthesis.cancel();
3023
- const utterance = new SpeechSynthesisUtterance(text);
3024
- utterance.lang = navigator.language || 'en-US';
3025
- utterance.rate = 1.0;
3026
- window.speechSynthesis.speak(utterance);
3027
- }
3028
-
3029
- // Patch message rendering to add TTS button to assistant messages
3030
- const _origAppendMsg = typeof appendMessage === 'function' ? appendMessage : null;
3031
- if (typeof window._patchedMsgRender === 'undefined') {
3032
- window._patchedMsgRender = true;
3033
- const observer = new MutationObserver((mutations) => {
3034
- for (const m of mutations) {
3035
- for (const node of m.addedNodes) {
3036
- if (node.nodeType === 1 && node.classList?.contains('msg') && node.classList?.contains('assistant')) {
3037
- const bubble = node.querySelector('.msg-bubble');
3038
- if (bubble && !node.querySelector('.tts-btn')) {
3039
- const btn = document.createElement('button');
3040
- btn.className = 'tts-btn';
3041
- btn.textContent = '🔊';
3042
- btn.title = 'Read aloud';
3043
- btn.style.cssText = 'background:none;border:1px solid var(--border);border-radius:50%;padding:4px 6px;cursor:pointer;font-size:14px;margin-top:4px;color:var(--text-muted);';
3044
- btn.onclick = () => speakText(bubble.textContent);
3045
- node.appendChild(btn);
3046
- }
3047
- }
3048
- }
3049
- }
3050
- });
3051
- const chatMsgs = document.getElementById('chat-messages');
3052
- if (chatMsgs) observer.observe(chatMsgs, { childList: true });
3053
- }
3054
-
3055
- // =============================================
3056
- // === Image Generation Config ===
3057
- // =============================================
3058
- async function saveImageGenConfig() {
3059
- const data = {
3060
- openaiApiKey: document.getElementById('ig-openai-key').value.trim(),
3061
- sdApiUrl: document.getElementById('ig-sd-url').value.trim(),
3062
- replicateApiKey: document.getElementById('ig-replicate-key').value.trim(),
3063
- };
3064
- try {
3065
- await fetch('/api/image-gen/config', { method: 'PUT', headers: {'Content-Type':'application/json'}, body: JSON.stringify(data) });
3066
- document.getElementById('ig-status').textContent = '✅ Configuration saved!';
3067
- document.getElementById('ig-status').style.color = 'var(--green)';
3068
- } catch(e) {
3069
- document.getElementById('ig-status').textContent = '❌ Failed: ' + e.message;
3070
- document.getElementById('ig-status').style.color = 'var(--red)';
3071
- }
3072
- }
3073
-
3074
- </script>
3075
- </body>
3076
- </html>