agent-orcha 0.0.5 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +250 -1275
- package/dist/lib/agents/agent-executor.d.ts +4 -2
- package/dist/lib/agents/agent-executor.d.ts.map +1 -1
- package/dist/lib/agents/agent-executor.js +85 -53
- package/dist/lib/agents/agent-executor.js.map +1 -1
- package/dist/lib/agents/agent-loader.d.ts +3 -0
- package/dist/lib/agents/agent-loader.d.ts.map +1 -1
- package/dist/lib/agents/agent-loader.js +10 -1
- package/dist/lib/agents/agent-loader.js.map +1 -1
- package/dist/lib/agents/react-loop.d.ts.map +1 -1
- package/dist/lib/agents/react-loop.js +207 -142
- package/dist/lib/agents/react-loop.js.map +1 -1
- package/dist/lib/agents/types.d.ts +181 -18
- package/dist/lib/agents/types.d.ts.map +1 -1
- package/dist/lib/agents/types.js +18 -2
- package/dist/lib/agents/types.js.map +1 -1
- package/dist/lib/functions/function-loader.d.ts +2 -0
- package/dist/lib/functions/function-loader.d.ts.map +1 -1
- package/dist/lib/functions/function-loader.js +10 -0
- package/dist/lib/functions/function-loader.js.map +1 -1
- package/dist/lib/functions/simple-function-wrapper.js +3 -3
- package/dist/lib/functions/simple-function-wrapper.js.map +1 -1
- package/dist/lib/integrations/email.d.ts +38 -0
- package/dist/lib/integrations/email.d.ts.map +1 -0
- package/dist/lib/integrations/email.js +249 -0
- package/dist/lib/integrations/email.js.map +1 -0
- package/dist/lib/integrations/integration-manager.d.ts +5 -0
- package/dist/lib/integrations/integration-manager.d.ts.map +1 -1
- package/dist/lib/integrations/integration-manager.js +53 -3
- package/dist/lib/integrations/integration-manager.js.map +1 -1
- package/dist/lib/integrations/types.d.ts +187 -4
- package/dist/lib/integrations/types.d.ts.map +1 -1
- package/dist/lib/integrations/types.js +24 -1
- package/dist/lib/integrations/types.js.map +1 -1
- package/dist/lib/knowledge/knowledge-store.d.ts +7 -1
- package/dist/lib/knowledge/knowledge-store.d.ts.map +1 -1
- package/dist/lib/knowledge/knowledge-store.js +96 -8
- package/dist/lib/knowledge/knowledge-store.js.map +1 -1
- package/dist/lib/knowledge/loaders/file-loaders.d.ts +8 -3
- package/dist/lib/knowledge/loaders/file-loaders.d.ts.map +1 -1
- package/dist/lib/knowledge/loaders/file-loaders.js +96 -75
- package/dist/lib/knowledge/loaders/file-loaders.js.map +1 -1
- package/dist/lib/knowledge/loaders/web-loader.d.ts +12 -3
- package/dist/lib/knowledge/loaders/web-loader.d.ts.map +1 -1
- package/dist/lib/knowledge/loaders/web-loader.js +56 -22
- package/dist/lib/knowledge/loaders/web-loader.js.map +1 -1
- package/dist/lib/knowledge/sqlite-store.d.ts.map +1 -1
- package/dist/lib/knowledge/sqlite-store.js +19 -10
- package/dist/lib/knowledge/sqlite-store.js.map +1 -1
- package/dist/lib/knowledge/types.d.ts +69 -33
- package/dist/lib/knowledge/types.d.ts.map +1 -1
- package/dist/lib/knowledge/types.js +18 -3
- package/dist/lib/knowledge/types.js.map +1 -1
- package/dist/lib/llm/index.d.ts +1 -1
- package/dist/lib/llm/index.d.ts.map +1 -1
- package/dist/lib/llm/index.js +1 -1
- package/dist/lib/llm/index.js.map +1 -1
- package/dist/lib/llm/llm-call-logger.d.ts +3 -1
- package/dist/lib/llm/llm-call-logger.d.ts.map +1 -1
- package/dist/lib/llm/llm-call-logger.js +31 -26
- package/dist/lib/llm/llm-call-logger.js.map +1 -1
- package/dist/lib/llm/llm-config.d.ts +59 -8
- package/dist/lib/llm/llm-config.d.ts.map +1 -1
- package/dist/lib/llm/llm-config.js +163 -17
- package/dist/lib/llm/llm-config.js.map +1 -1
- package/dist/lib/llm/llm-factory.d.ts +1 -2
- package/dist/lib/llm/llm-factory.d.ts.map +1 -1
- package/dist/lib/llm/llm-factory.js +44 -8
- package/dist/lib/llm/llm-factory.js.map +1 -1
- package/dist/lib/llm/providers/anthropic-chat-model.d.ts +5 -1
- package/dist/lib/llm/providers/anthropic-chat-model.d.ts.map +1 -1
- package/dist/lib/llm/providers/anthropic-chat-model.js +118 -42
- package/dist/lib/llm/providers/anthropic-chat-model.js.map +1 -1
- package/dist/lib/llm/providers/gemini-chat-model.d.ts +3 -2
- package/dist/lib/llm/providers/gemini-chat-model.d.ts.map +1 -1
- package/dist/lib/llm/providers/gemini-chat-model.js +83 -24
- package/dist/lib/llm/providers/gemini-chat-model.js.map +1 -1
- package/dist/lib/llm/providers/openai-chat-model.d.ts +20 -1
- package/dist/lib/llm/providers/openai-chat-model.d.ts.map +1 -1
- package/dist/lib/llm/providers/openai-chat-model.js +265 -32
- package/dist/lib/llm/providers/openai-chat-model.js.map +1 -1
- package/dist/lib/llm/providers/openai-embeddings.d.ts.map +1 -1
- package/dist/lib/llm/providers/openai-embeddings.js +41 -10
- package/dist/lib/llm/providers/openai-embeddings.js.map +1 -1
- package/dist/lib/local-llm/binary-manager.d.ts +66 -0
- package/dist/lib/local-llm/binary-manager.d.ts.map +1 -0
- package/dist/lib/local-llm/binary-manager.js +441 -0
- package/dist/lib/local-llm/binary-manager.js.map +1 -0
- package/dist/lib/local-llm/engine-interface.d.ts +47 -0
- package/dist/lib/local-llm/engine-interface.d.ts.map +1 -0
- package/dist/lib/local-llm/engine-interface.js +2 -0
- package/dist/lib/local-llm/engine-interface.js.map +1 -0
- package/dist/lib/local-llm/engine-registry.d.ts +20 -0
- package/dist/lib/local-llm/engine-registry.d.ts.map +1 -0
- package/dist/lib/local-llm/engine-registry.js +56 -0
- package/dist/lib/local-llm/engine-registry.js.map +1 -0
- package/dist/lib/local-llm/engines/llama-cpp-engine.d.ts +31 -0
- package/dist/lib/local-llm/engines/llama-cpp-engine.d.ts.map +1 -0
- package/dist/lib/local-llm/engines/llama-cpp-engine.js +164 -0
- package/dist/lib/local-llm/engines/llama-cpp-engine.js.map +1 -0
- package/dist/lib/local-llm/engines/mlx-serve-engine.d.ts +31 -0
- package/dist/lib/local-llm/engines/mlx-serve-engine.d.ts.map +1 -0
- package/dist/lib/local-llm/engines/mlx-serve-engine.js +161 -0
- package/dist/lib/local-llm/engines/mlx-serve-engine.js.map +1 -0
- package/dist/lib/local-llm/gguf-reader.d.ts +20 -0
- package/dist/lib/local-llm/gguf-reader.d.ts.map +1 -0
- package/dist/lib/local-llm/gguf-reader.js +190 -0
- package/dist/lib/local-llm/gguf-reader.js.map +1 -0
- package/dist/lib/local-llm/index.d.ts +9 -0
- package/dist/lib/local-llm/index.d.ts.map +1 -0
- package/dist/lib/local-llm/index.js +6 -0
- package/dist/lib/local-llm/index.js.map +1 -0
- package/dist/lib/local-llm/llama-server-process.d.ts +42 -0
- package/dist/lib/local-llm/llama-server-process.d.ts.map +1 -0
- package/dist/lib/local-llm/llama-server-process.js +237 -0
- package/dist/lib/local-llm/llama-server-process.js.map +1 -0
- package/dist/lib/local-llm/mlx-binary-manager.d.ts +33 -0
- package/dist/lib/local-llm/mlx-binary-manager.d.ts.map +1 -0
- package/dist/lib/local-llm/mlx-binary-manager.js +211 -0
- package/dist/lib/local-llm/mlx-binary-manager.js.map +1 -0
- package/dist/lib/local-llm/mlx-server-process.d.ts +26 -0
- package/dist/lib/local-llm/mlx-server-process.d.ts.map +1 -0
- package/dist/lib/local-llm/mlx-server-process.js +210 -0
- package/dist/lib/local-llm/mlx-server-process.js.map +1 -0
- package/dist/lib/local-llm/model-manager.d.ts +33 -0
- package/dist/lib/local-llm/model-manager.d.ts.map +1 -0
- package/dist/lib/local-llm/model-manager.js +591 -0
- package/dist/lib/local-llm/model-manager.js.map +1 -0
- package/dist/lib/local-llm/types.d.ts +51 -0
- package/dist/lib/local-llm/types.d.ts.map +1 -0
- package/dist/lib/local-llm/types.js +2 -0
- package/dist/lib/local-llm/types.js.map +1 -0
- package/dist/lib/logger.d.ts +2 -0
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +68 -6
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/mcp/mcp-client.d.ts.map +1 -1
- package/dist/lib/mcp/mcp-client.js +5 -3
- package/dist/lib/mcp/mcp-client.js.map +1 -1
- package/dist/lib/mcp/types.d.ts +0 -9
- package/dist/lib/mcp/types.d.ts.map +1 -1
- package/dist/lib/mcp/types.js +1 -2
- package/dist/lib/mcp/types.js.map +1 -1
- package/dist/lib/memory/memory-manager.d.ts +1 -0
- package/dist/lib/memory/memory-manager.d.ts.map +1 -1
- package/dist/lib/memory/memory-manager.js +9 -0
- package/dist/lib/memory/memory-manager.js.map +1 -1
- package/dist/lib/orchestrator.d.ts +11 -8
- package/dist/lib/orchestrator.d.ts.map +1 -1
- package/dist/lib/orchestrator.js +246 -5
- package/dist/lib/orchestrator.js.map +1 -1
- package/dist/lib/sandbox/cdp-client.d.ts +15 -0
- package/dist/lib/sandbox/cdp-client.d.ts.map +1 -0
- package/dist/lib/sandbox/cdp-client.js +139 -0
- package/dist/lib/sandbox/cdp-client.js.map +1 -0
- package/dist/lib/sandbox/html-to-markdown.d.ts +9 -1
- package/dist/lib/sandbox/html-to-markdown.d.ts.map +1 -1
- package/dist/lib/sandbox/html-to-markdown.js +67 -10
- package/dist/lib/sandbox/html-to-markdown.js.map +1 -1
- package/dist/lib/sandbox/index.d.ts +6 -0
- package/dist/lib/sandbox/index.d.ts.map +1 -1
- package/dist/lib/sandbox/index.js +5 -0
- package/dist/lib/sandbox/index.js.map +1 -1
- package/dist/lib/sandbox/page-readiness.d.ts +37 -0
- package/dist/lib/sandbox/page-readiness.d.ts.map +1 -0
- package/dist/lib/sandbox/page-readiness.js +268 -0
- package/dist/lib/sandbox/page-readiness.js.map +1 -0
- package/dist/lib/sandbox/sandbox-browser.d.ts +4 -0
- package/dist/lib/sandbox/sandbox-browser.d.ts.map +1 -0
- package/dist/lib/sandbox/sandbox-browser.js +316 -0
- package/dist/lib/sandbox/sandbox-browser.js.map +1 -0
- package/dist/lib/sandbox/sandbox-container.d.ts +39 -0
- package/dist/lib/sandbox/sandbox-container.d.ts.map +1 -0
- package/dist/lib/sandbox/sandbox-container.js +176 -0
- package/dist/lib/sandbox/sandbox-container.js.map +1 -0
- package/dist/lib/sandbox/sandbox-file.d.ts +4 -0
- package/dist/lib/sandbox/sandbox-file.d.ts.map +1 -0
- package/dist/lib/sandbox/sandbox-file.js +169 -0
- package/dist/lib/sandbox/sandbox-file.js.map +1 -0
- package/dist/lib/sandbox/sandbox-shell.d.ts +5 -0
- package/dist/lib/sandbox/sandbox-shell.d.ts.map +1 -0
- package/dist/lib/sandbox/sandbox-shell.js +111 -0
- package/dist/lib/sandbox/sandbox-shell.js.map +1 -0
- package/dist/lib/sandbox/sandbox-web.d.ts.map +1 -1
- package/dist/lib/sandbox/sandbox-web.js +64 -24
- package/dist/lib/sandbox/sandbox-web.js.map +1 -1
- package/dist/lib/sandbox/types.d.ts +9 -0
- package/dist/lib/sandbox/types.d.ts.map +1 -1
- package/dist/lib/sandbox/types.js +1 -0
- package/dist/lib/sandbox/types.js.map +1 -1
- package/dist/lib/sandbox/vision-browser.d.ts +4 -0
- package/dist/lib/sandbox/vision-browser.d.ts.map +1 -0
- package/dist/lib/sandbox/vision-browser.js +298 -0
- package/dist/lib/sandbox/vision-browser.js.map +1 -0
- package/dist/lib/sea/app-window.d.ts +7 -0
- package/dist/lib/sea/app-window.d.ts.map +1 -0
- package/dist/lib/sea/app-window.js +95 -0
- package/dist/lib/sea/app-window.js.map +1 -0
- package/dist/lib/sea/bootstrap.d.ts +18 -0
- package/dist/lib/sea/bootstrap.d.ts.map +1 -0
- package/dist/lib/sea/bootstrap.js +103 -0
- package/dist/lib/sea/bootstrap.js.map +1 -0
- package/dist/lib/sea/sqlite-vec-shim.d.ts +3 -0
- package/dist/lib/sea/sqlite-vec-shim.d.ts.map +1 -0
- package/dist/lib/sea/sqlite-vec-shim.js +10 -0
- package/dist/lib/sea/sqlite-vec-shim.js.map +1 -0
- package/dist/lib/skills/skill-loader.d.ts +2 -0
- package/dist/lib/skills/skill-loader.d.ts.map +1 -1
- package/dist/lib/skills/skill-loader.js +12 -1
- package/dist/lib/skills/skill-loader.js.map +1 -1
- package/dist/lib/tasks/task-manager.d.ts +3 -1
- package/dist/lib/tasks/task-manager.d.ts.map +1 -1
- package/dist/lib/tasks/task-manager.js +11 -0
- package/dist/lib/tasks/task-manager.js.map +1 -1
- package/dist/lib/tasks/task-store.d.ts +1 -1
- package/dist/lib/tasks/task-store.d.ts.map +1 -1
- package/dist/lib/tasks/task-store.js.map +1 -1
- package/dist/lib/tasks/types.d.ts +18 -0
- package/dist/lib/tasks/types.d.ts.map +1 -1
- package/dist/lib/tools/built-in/integration-tools.d.ts +4 -0
- package/dist/lib/tools/built-in/integration-tools.d.ts.map +1 -0
- package/dist/lib/tools/built-in/integration-tools.js +47 -0
- package/dist/lib/tools/built-in/integration-tools.js.map +1 -0
- package/dist/lib/tools/built-in/knowledge-entity-lookup.tool.d.ts +1 -2
- package/dist/lib/tools/built-in/knowledge-entity-lookup.tool.d.ts.map +1 -1
- package/dist/lib/tools/built-in/knowledge-entity-lookup.tool.js +17 -17
- package/dist/lib/tools/built-in/knowledge-entity-lookup.tool.js.map +1 -1
- package/dist/lib/tools/built-in/knowledge-graph-schema.tool.d.ts.map +1 -1
- package/dist/lib/tools/built-in/knowledge-graph-schema.tool.js +2 -4
- package/dist/lib/tools/built-in/knowledge-graph-schema.tool.js.map +1 -1
- package/dist/lib/tools/built-in/knowledge-search.tool.js +4 -4
- package/dist/lib/tools/built-in/knowledge-search.tool.js.map +1 -1
- package/dist/lib/tools/built-in/knowledge-sql.tool.d.ts.map +1 -1
- package/dist/lib/tools/built-in/knowledge-sql.tool.js +74 -40
- package/dist/lib/tools/built-in/knowledge-sql.tool.js.map +1 -1
- package/dist/lib/tools/built-in/knowledge-tools-factory.js +2 -2
- package/dist/lib/tools/built-in/knowledge-tools-factory.js.map +1 -1
- package/dist/lib/tools/built-in/knowledge-traverse.tool.d.ts +1 -2
- package/dist/lib/tools/built-in/knowledge-traverse.tool.d.ts.map +1 -1
- package/dist/lib/tools/built-in/knowledge-traverse.tool.js +5 -11
- package/dist/lib/tools/built-in/knowledge-traverse.tool.js.map +1 -1
- package/dist/lib/tools/built-in/query-validators.d.ts.map +1 -1
- package/dist/lib/tools/built-in/query-validators.js +4 -0
- package/dist/lib/tools/built-in/query-validators.js.map +1 -1
- package/dist/lib/tools/workspace/workspace-tools.d.ts +1 -0
- package/dist/lib/tools/workspace/workspace-tools.d.ts.map +1 -1
- package/dist/lib/tools/workspace/workspace-tools.js +44 -4
- package/dist/lib/tools/workspace/workspace-tools.js.map +1 -1
- package/dist/lib/triggers/cron-trigger.d.ts +1 -1
- package/dist/lib/triggers/cron-trigger.d.ts.map +1 -1
- package/dist/lib/triggers/cron-trigger.js.map +1 -1
- package/dist/lib/triggers/trigger-manager.d.ts +1 -0
- package/dist/lib/triggers/trigger-manager.d.ts.map +1 -1
- package/dist/lib/triggers/trigger-manager.js +26 -0
- package/dist/lib/triggers/trigger-manager.js.map +1 -1
- package/dist/lib/triggers/webhook-trigger.d.ts +1 -1
- package/dist/lib/triggers/webhook-trigger.d.ts.map +1 -1
- package/dist/lib/triggers/webhook-trigger.js.map +1 -1
- package/dist/lib/types/llm-types.d.ts +22 -4
- package/dist/lib/types/llm-types.d.ts.map +1 -1
- package/dist/lib/types/llm-types.js +50 -0
- package/dist/lib/types/llm-types.js.map +1 -1
- package/dist/lib/types/tool-factory.d.ts +2 -2
- package/dist/lib/types/tool-factory.d.ts.map +1 -1
- package/dist/lib/types/tool-factory.js +9 -2
- package/dist/lib/types/tool-factory.js.map +1 -1
- package/dist/lib/utils/document-extract.d.ts +10 -0
- package/dist/lib/utils/document-extract.d.ts.map +1 -0
- package/dist/lib/utils/document-extract.js +149 -0
- package/dist/lib/utils/document-extract.js.map +1 -0
- package/dist/lib/utils/env-substitution.d.ts +6 -0
- package/dist/lib/utils/env-substitution.d.ts.map +1 -0
- package/dist/lib/utils/env-substitution.js +15 -0
- package/dist/lib/utils/env-substitution.js.map +1 -0
- package/dist/lib/workflows/react-workflow-executor.d.ts.map +1 -1
- package/dist/lib/workflows/react-workflow-executor.js +23 -17
- package/dist/lib/workflows/react-workflow-executor.js.map +1 -1
- package/dist/lib/workflows/types.d.ts +81 -55
- package/dist/lib/workflows/types.d.ts.map +1 -1
- package/dist/lib/workflows/types.js +10 -0
- package/dist/lib/workflows/types.js.map +1 -1
- package/dist/lib/workflows/workflow-loader.d.ts +3 -0
- package/dist/lib/workflows/workflow-loader.d.ts.map +1 -1
- package/dist/lib/workflows/workflow-loader.js +10 -1
- package/dist/lib/workflows/workflow-loader.js.map +1 -1
- package/dist/public/assets/logo.png +0 -0
- package/dist/public/chat.html +39 -0
- package/dist/public/index.html +6 -176
- package/dist/public/src/components/AgentComposer.js +807 -0
- package/dist/public/src/components/AgentsView.js +1812 -508
- package/dist/public/src/components/AppRoot.js +125 -38
- package/dist/public/src/components/GraphView.js +382 -300
- package/dist/public/src/components/IdeView.js +277 -86
- package/dist/public/src/components/KnowledgeView.js +94 -130
- package/dist/public/src/components/LlmView.js +15 -19
- package/dist/public/src/components/LocalLlmView.js +2440 -0
- package/dist/public/src/components/LogViewer.js +155 -0
- package/dist/public/src/components/McpView.js +41 -49
- package/dist/public/src/components/MonitorView.js +174 -83
- package/dist/public/src/components/NavBar.js +16 -26
- package/dist/public/src/components/StandaloneChat.js +875 -0
- package/dist/public/src/services/ApiService.js +203 -4
- package/dist/public/src/services/SessionStore.js +86 -0
- package/dist/public/src/services/StreamManager.js +183 -0
- package/dist/public/src/store.js +1 -3
- package/dist/public/src/utils/card.js +21 -0
- package/dist/public/src/utils/markdown.js +7 -0
- package/dist/public/styles.css +2777 -0
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +7 -1
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/start.d.ts.map +1 -1
- package/dist/src/cli/commands/start.js +28 -5
- package/dist/src/cli/commands/start.js.map +1 -1
- package/dist/src/cli/index.js +19 -5
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/index.js +7 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/middleware/auth.d.ts.map +1 -1
- package/dist/src/middleware/auth.js +28 -6
- package/dist/src/middleware/auth.js.map +1 -1
- package/dist/src/middleware/rate-limit.d.ts +8 -0
- package/dist/src/middleware/rate-limit.d.ts.map +1 -0
- package/dist/src/middleware/rate-limit.js +21 -0
- package/dist/src/middleware/rate-limit.js.map +1 -0
- package/dist/src/routes/agents.route.d.ts.map +1 -1
- package/dist/src/routes/agents.route.js +138 -10
- package/dist/src/routes/agents.route.js.map +1 -1
- package/dist/src/routes/chat.route.d.ts +3 -0
- package/dist/src/routes/chat.route.d.ts.map +1 -0
- package/dist/src/routes/chat.route.js +156 -0
- package/dist/src/routes/chat.route.js.map +1 -0
- package/dist/src/routes/files.route.d.ts.map +1 -1
- package/dist/src/routes/files.route.js +37 -2
- package/dist/src/routes/files.route.js.map +1 -1
- package/dist/src/routes/llm.route.d.ts.map +1 -1
- package/dist/src/routes/llm.route.js +263 -8
- package/dist/src/routes/llm.route.js.map +1 -1
- package/dist/src/routes/local-llm.route.d.ts +3 -0
- package/dist/src/routes/local-llm.route.d.ts.map +1 -0
- package/dist/src/routes/local-llm.route.js +688 -0
- package/dist/src/routes/local-llm.route.js.map +1 -0
- package/dist/src/routes/logs.route.d.ts +3 -0
- package/dist/src/routes/logs.route.d.ts.map +1 -0
- package/dist/src/routes/logs.route.js +24 -0
- package/dist/src/routes/logs.route.js.map +1 -0
- package/dist/src/routes/tasks.route.d.ts.map +1 -1
- package/dist/src/routes/tasks.route.js +15 -1
- package/dist/src/routes/tasks.route.js.map +1 -1
- package/dist/src/routes/vnc.route.d.ts +12 -0
- package/dist/src/routes/vnc.route.d.ts.map +1 -0
- package/dist/src/routes/vnc.route.js +74 -0
- package/dist/src/routes/vnc.route.js.map +1 -0
- package/dist/src/routes/workflows.route.d.ts.map +1 -1
- package/dist/src/routes/workflows.route.js +24 -0
- package/dist/src/routes/workflows.route.js.map +1 -1
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +29 -3
- package/dist/src/server.js.map +1 -1
- package/dist/templates/Demo.md +152 -0
- package/dist/templates/README.md +12 -3
- package/dist/templates/agents/actor.agent.yaml +34 -0
- package/dist/templates/agents/architect.agent.yaml +20 -13
- package/dist/templates/agents/chatbot.agent.yaml +23 -27
- package/dist/templates/agents/corporate.agent.yaml +64 -0
- package/dist/templates/agents/functions.agent.yaml +29 -0
- package/dist/templates/agents/investment-analyst.agent.yaml +79 -0
- package/dist/templates/agents/music-librarian.agent.yaml +46 -0
- package/dist/templates/agents/network-security.agent.yaml +81 -0
- package/dist/templates/agents/transport-security.agent.yaml +69 -0
- package/dist/templates/agents/web-engineer.agent.yaml +98 -0
- package/dist/templates/agents/web-pilot.agent.yaml +57 -0
- package/dist/templates/knowledge/music-store/LICENSE.md +11 -0
- package/dist/templates/knowledge/music-store/musicstore.sqlite +0 -0
- package/dist/templates/knowledge/music-store/tables.png +0 -0
- package/dist/templates/knowledge/music-store.knowledge.yaml +138 -0
- package/dist/templates/knowledge/org-chart/personnel.csv +21 -21
- package/dist/templates/knowledge/org-chart.knowledge.yaml +4 -0
- package/dist/templates/knowledge/patient-records.knowledge.yaml +20 -0
- package/dist/templates/knowledge/pdf-patients/PDF_Deid_Deidentification_0.pdf +0 -0
- package/dist/templates/knowledge/pdf-patients/PDF_Deid_Deidentification_1.pdf +0 -0
- package/dist/templates/knowledge/pdf-patients/PDF_Deid_Deidentification_10.pdf +0 -0
- package/dist/templates/knowledge/pdf-patients/PDF_Deid_Deidentification_11.pdf +0 -0
- package/dist/templates/knowledge/pet-store.knowledge.yaml +3 -0
- package/dist/templates/knowledge/security-incidents/incidents.json +55935 -0
- package/dist/templates/knowledge/security-incidents.knowledge.yaml +46 -0
- package/dist/templates/knowledge/{example.knowledge.yaml → transcripts.knowledge.yaml} +9 -5
- package/dist/templates/knowledge/transport-ot/systems.csv +117 -0
- package/dist/templates/knowledge/transport-ot.knowledge.yaml +55 -0
- package/dist/templates/knowledge/web-docs.knowledge.yaml +1 -1
- package/dist/templates/llm.json +62 -22
- package/dist/templates/mcp.json +7 -4
- package/dist/templates/skills/orcha-builder/SKILL.md +148 -215
- package/dist/templates/skills/pii-guard/SKILL.md +22 -0
- package/dist/templates/skills/sandbox/SKILL.md +25 -48
- package/dist/templates/skills/web-pilot/SKILL.md +51 -0
- package/dist/templates/workflows/example.workflow.yaml +27 -35
- package/dist/templates/workflows/react-example.workflow.yaml +14 -19
- package/dist/templates/workflows/team-chat.workflow.yaml +47 -0
- package/package.json +17 -4
- package/dist/public/src/components/SkillsView.js +0 -137
- package/dist/public/src/components/WorkflowsView.js +0 -416
- package/dist/templates/agents/knowledge-broker.agent.yaml +0 -39
- package/dist/templates/agents/sandbox.agent.yaml +0 -56
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
|
|
2
2
|
import { Component } from '../utils/Component.js';
|
|
3
3
|
import { api } from '../services/ApiService.js';
|
|
4
|
+
import { sessionStore } from '../services/SessionStore.js';
|
|
5
|
+
import { streamManager } from '../services/StreamManager.js';
|
|
6
|
+
import { escapeHtml as sharedEscapeHtml } from '../utils/card.js';
|
|
4
7
|
import { store } from '../store.js';
|
|
5
8
|
import { markdownRenderer } from '../utils/markdown.js';
|
|
6
9
|
|
|
10
|
+
// Survives component remount (tab navigation) but not page refresh
|
|
11
|
+
const workflowTasks = new Map();
|
|
12
|
+
|
|
7
13
|
export class AgentsView extends Component {
|
|
8
14
|
constructor() {
|
|
9
15
|
super();
|
|
@@ -12,19 +18,28 @@ export class AgentsView extends Component {
|
|
|
12
18
|
this.streamStartTime = null;
|
|
13
19
|
this.streamTimerInterval = null;
|
|
14
20
|
this.streamUsageData = null;
|
|
21
|
+
this.pendingAttachments = [];
|
|
22
|
+
this._streamUnsubscribe = null;
|
|
15
23
|
}
|
|
16
24
|
|
|
17
25
|
async connectedCallback() {
|
|
18
26
|
super.connectedCallback();
|
|
19
|
-
await Promise.all([this.loadAgents(), this.loadLLMs()]);
|
|
27
|
+
await Promise.all([this.loadAgents(), this.loadLLMs(), this.loadWorkflows()]);
|
|
28
|
+
this.restoreActiveSession();
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
disconnectedCallback() {
|
|
23
|
-
this.
|
|
24
|
-
|
|
25
|
-
this.
|
|
26
|
-
|
|
32
|
+
if (this._streamUnsubscribe) {
|
|
33
|
+
this._streamUnsubscribe();
|
|
34
|
+
this._streamUnsubscribe = null;
|
|
35
|
+
}
|
|
36
|
+
if (this.streamTimerInterval) {
|
|
37
|
+
clearInterval(this.streamTimerInterval);
|
|
38
|
+
this.streamTimerInterval = null;
|
|
27
39
|
}
|
|
40
|
+
this.currentAbortController = null;
|
|
41
|
+
// Workflow streams continue via AbortController — no cleanup needed on tab switch
|
|
42
|
+
// (state is preserved in the module-level workflowTasks map)
|
|
28
43
|
}
|
|
29
44
|
|
|
30
45
|
formatElapsedTime(ms) {
|
|
@@ -41,13 +56,33 @@ export class AgentsView extends Component {
|
|
|
41
56
|
}
|
|
42
57
|
|
|
43
58
|
cancelCurrentStream() {
|
|
59
|
+
const activeId = sessionStore.getActiveId();
|
|
60
|
+
|
|
61
|
+
// Cancel via server tasks API if we have a task ID
|
|
62
|
+
if (activeId) {
|
|
63
|
+
const streamState = streamManager.getState(activeId);
|
|
64
|
+
const wfState = workflowTasks.get(activeId);
|
|
65
|
+
const taskId = streamState?.taskId || wfState?.taskId;
|
|
66
|
+
if (taskId) {
|
|
67
|
+
api.cancelTask(taskId).catch(() => {});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
44
71
|
if (this.currentAbortController) {
|
|
45
72
|
this.currentAbortController.abort();
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (!activeId) return;
|
|
76
|
+
const wfState = workflowTasks.get(activeId);
|
|
77
|
+
if (wfState?.abortController) {
|
|
78
|
+
wfState.abortController.abort();
|
|
79
|
+
return;
|
|
46
80
|
}
|
|
81
|
+
streamManager.cancel(activeId);
|
|
47
82
|
}
|
|
48
83
|
|
|
49
|
-
startStreamTimer(responseId) {
|
|
50
|
-
this.streamStartTime = Date.now();
|
|
84
|
+
startStreamTimer(responseId, startTime) {
|
|
85
|
+
this.streamStartTime = startTime || Date.now();
|
|
51
86
|
this.streamTimerInterval = setInterval(() => {
|
|
52
87
|
const elapsed = Date.now() - this.streamStartTime;
|
|
53
88
|
const bubble = this.querySelector(`#${responseId}`);
|
|
@@ -75,7 +110,7 @@ export class AgentsView extends Component {
|
|
|
75
110
|
const statusBar = wrapper.querySelector('.stream-status-bar');
|
|
76
111
|
const statsBar = wrapper.querySelector('.stream-stats-bar');
|
|
77
112
|
|
|
78
|
-
if (statusBar) statusBar.
|
|
113
|
+
if (statusBar) statusBar.remove();
|
|
79
114
|
|
|
80
115
|
if (statsBar) {
|
|
81
116
|
const elapsedEl = statsBar.querySelector('.stats-elapsed');
|
|
@@ -104,27 +139,20 @@ export class AgentsView extends Component {
|
|
|
104
139
|
|
|
105
140
|
if (wasCancelled) {
|
|
106
141
|
const cancelBadge = document.createElement('span');
|
|
107
|
-
cancelBadge.className = '
|
|
142
|
+
cancelBadge.className = 'badge badge-amber';
|
|
108
143
|
cancelBadge.textContent = 'Cancelled';
|
|
109
144
|
statsBar.appendChild(cancelBadge);
|
|
110
145
|
}
|
|
111
146
|
|
|
112
|
-
statsBar.classList.
|
|
147
|
+
statsBar.classList.add('visible');
|
|
113
148
|
}
|
|
114
149
|
}
|
|
115
150
|
|
|
116
151
|
async loadAgents() {
|
|
117
152
|
try {
|
|
118
153
|
const agents = await api.getAgents();
|
|
154
|
+
agents.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
|
|
119
155
|
store.set('agents', agents);
|
|
120
|
-
|
|
121
|
-
if (agents.length > 0 && !store.get('selectedAgent') && !store.get('selectedLlm')) {
|
|
122
|
-
store.set('selectedAgent', agents[agents.length - 1]);
|
|
123
|
-
store.set('selectionType', 'agent');
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
this.renderAgentDropdown();
|
|
127
|
-
this.updateSelectedAgentUI();
|
|
128
156
|
} catch (e) {
|
|
129
157
|
console.error('Failed to load agents', e);
|
|
130
158
|
}
|
|
@@ -132,494 +160,1653 @@ export class AgentsView extends Component {
|
|
|
132
160
|
|
|
133
161
|
async loadLLMs() {
|
|
134
162
|
try {
|
|
135
|
-
const llms = await api.getLLMs();
|
|
163
|
+
const [llms, llmConfig] = await Promise.all([api.getLLMs(), api.getLlmConfig()]);
|
|
164
|
+
// Resolve the default model name from the config pointer
|
|
165
|
+
const defaultPointer = llmConfig?.models?.default;
|
|
166
|
+
const defaultLlmName = typeof defaultPointer === 'string' ? defaultPointer : null;
|
|
136
167
|
store.set('llms', llms);
|
|
137
|
-
|
|
168
|
+
store.set('defaultLlmName', defaultLlmName);
|
|
138
169
|
} catch (e) {
|
|
139
170
|
console.error('Failed to load LLMs', e);
|
|
140
171
|
}
|
|
141
172
|
}
|
|
142
173
|
|
|
143
|
-
|
|
144
|
-
|
|
174
|
+
async loadWorkflows() {
|
|
175
|
+
try {
|
|
176
|
+
const workflows = await api.getWorkflows();
|
|
177
|
+
workflows.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
|
|
178
|
+
store.set('workflows', workflows);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
console.error('Failed to load workflows', e);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// --- Sidebar toggle (mobile) ---
|
|
185
|
+
|
|
186
|
+
_isMobile() {
|
|
187
|
+
return !window.matchMedia('(min-width: 768px)').matches;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
toggleSidebar(show) {
|
|
191
|
+
const sidebar = this.querySelector('#sidebar');
|
|
192
|
+
const backdrop = this.querySelector('#sidebarBackdrop');
|
|
193
|
+
if (!sidebar || !backdrop) return;
|
|
194
|
+
|
|
195
|
+
if (show) {
|
|
196
|
+
sidebar.classList.add('open');
|
|
197
|
+
backdrop.classList.add('visible');
|
|
198
|
+
} else {
|
|
199
|
+
sidebar.classList.remove('open');
|
|
200
|
+
backdrop.classList.remove('visible');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// --- Session management ---
|
|
205
|
+
|
|
206
|
+
restoreActiveSession() {
|
|
207
|
+
const activeId = sessionStore.getActiveId();
|
|
208
|
+
if (activeId && sessionStore.get(activeId)) {
|
|
209
|
+
this.switchToSession(activeId);
|
|
210
|
+
} else {
|
|
211
|
+
this.showEmptyState();
|
|
212
|
+
}
|
|
213
|
+
this.renderSessionList();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
renderSessionList() {
|
|
217
|
+
const list = this.querySelector('#sessionList');
|
|
218
|
+
if (!list) return;
|
|
219
|
+
|
|
220
|
+
const sessions = sessionStore.getAll();
|
|
221
|
+
const activeId = sessionStore.getActiveId();
|
|
222
|
+
|
|
223
|
+
if (sessions.length === 0) {
|
|
224
|
+
list.innerHTML = '<div class="text-muted text-sm text-center py-8">No conversations yet</div>';
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
list.innerHTML = sessions.map(s => {
|
|
229
|
+
const isActive = s.id === activeId;
|
|
230
|
+
let displayName, icon;
|
|
231
|
+
if (s.agentType === 'workflow') {
|
|
232
|
+
displayName = s.workflowName || 'Workflow';
|
|
233
|
+
icon = 'fa-project-diagram';
|
|
234
|
+
} else if (s.agentType === 'agent') {
|
|
235
|
+
displayName = s.agentName || 'Agent';
|
|
236
|
+
icon = 'fa-robot';
|
|
237
|
+
} else {
|
|
238
|
+
displayName = s.llmName || 'LLM';
|
|
239
|
+
icon = 'fa-microchip';
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return `
|
|
243
|
+
<div data-session-id="${s.id}" class="session-item${isActive ? ' active' : ''}">
|
|
244
|
+
<div class="flex-1 min-w-0">
|
|
245
|
+
<div class="text-sm text-primary truncate">${this.escapeHtml(s.title)}</div>
|
|
246
|
+
<div class="flex items-center gap-1 mt-1 text-xs text-muted">
|
|
247
|
+
<i class="fas ${icon} text-2xs"></i>
|
|
248
|
+
<span class="truncate">${this.escapeHtml(displayName)}</span>
|
|
249
|
+
</div>
|
|
250
|
+
</div>
|
|
251
|
+
<button data-delete-id="${s.id}" class="session-delete-btn" title="Delete">
|
|
252
|
+
<i class="fas fa-xmark text-xs"></i>
|
|
253
|
+
</button>
|
|
254
|
+
</div>
|
|
255
|
+
`;
|
|
256
|
+
}).join('');
|
|
257
|
+
|
|
258
|
+
// Event listeners
|
|
259
|
+
list.querySelectorAll('.session-item').forEach(item => {
|
|
260
|
+
item.addEventListener('click', (e) => {
|
|
261
|
+
if (e.target.closest('.session-delete-btn')) return;
|
|
262
|
+
this.switchToSession(item.dataset.sessionId);
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
list.querySelectorAll('.session-delete-btn').forEach(btn => {
|
|
267
|
+
btn.addEventListener('click', (e) => {
|
|
268
|
+
e.stopPropagation();
|
|
269
|
+
this.deleteSession(btn.dataset.deleteId);
|
|
270
|
+
});
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
switchToSession(sessionId) {
|
|
275
|
+
// Detach from current stream rendering (don't abort — let it continue in background)
|
|
276
|
+
if (this._streamUnsubscribe) {
|
|
277
|
+
this._streamUnsubscribe();
|
|
278
|
+
this._streamUnsubscribe = null;
|
|
279
|
+
}
|
|
280
|
+
if (this.streamTimerInterval) {
|
|
281
|
+
clearInterval(this.streamTimerInterval);
|
|
282
|
+
this.streamTimerInterval = null;
|
|
283
|
+
}
|
|
284
|
+
this.streamStartTime = null;
|
|
285
|
+
this.currentAbortController = null;
|
|
286
|
+
this.isLoading = false;
|
|
287
|
+
|
|
288
|
+
const session = sessionStore.get(sessionId);
|
|
289
|
+
if (!session) return;
|
|
290
|
+
|
|
291
|
+
sessionStore.setActiveId(sessionId);
|
|
292
|
+
|
|
293
|
+
// Update store with this session's agent/LLM
|
|
145
294
|
const agents = store.get('agents') || [];
|
|
146
295
|
const llms = store.get('llms') || [];
|
|
147
|
-
const selectionType = store.get('selectionType');
|
|
148
|
-
const selectedAgent = store.get('selectedAgent');
|
|
149
|
-
const selectedLlm = store.get('selectedLlm');
|
|
150
296
|
|
|
151
|
-
if (
|
|
297
|
+
if (session.agentType === 'workflow') {
|
|
298
|
+
const workflows = store.get('workflows') || [];
|
|
299
|
+
const wf = workflows.find(w => w.name === session.workflowName);
|
|
300
|
+
store.set('selectedWorkflow', wf || null);
|
|
301
|
+
store.set('selectedAgent', null);
|
|
302
|
+
store.set('selectedLlm', null);
|
|
303
|
+
store.set('selectionType', 'workflow');
|
|
304
|
+
} else if (session.agentType === 'agent') {
|
|
305
|
+
const agent = agents.find(a => a.name === session.agentName);
|
|
306
|
+
store.set('selectedAgent', agent || null);
|
|
307
|
+
store.set('selectedLlm', null);
|
|
308
|
+
store.set('selectedWorkflow', null);
|
|
309
|
+
store.set('selectionType', 'agent');
|
|
310
|
+
} else {
|
|
311
|
+
const llm = llms.find(l => l.name === session.llmName);
|
|
312
|
+
store.set('selectedLlm', llm || null);
|
|
313
|
+
store.set('selectedAgent', null);
|
|
314
|
+
store.set('selectedWorkflow', null);
|
|
315
|
+
store.set('selectionType', 'llm');
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
this.restoreMessages(session);
|
|
319
|
+
this.updateChatHeader(session);
|
|
320
|
+
this.renderSessionList();
|
|
321
|
+
|
|
322
|
+
// Reconnect to active stream if one exists for this session
|
|
323
|
+
const wfState = workflowTasks.get(sessionId);
|
|
324
|
+
if (wfState && wfState.status !== 'done') {
|
|
325
|
+
this._reconnectWorkflowStream(sessionId);
|
|
326
|
+
} else if (streamManager.isActive(sessionId)) {
|
|
327
|
+
this._reconnectToStream(sessionId);
|
|
328
|
+
}
|
|
152
329
|
|
|
153
|
-
|
|
154
|
-
|
|
330
|
+
this.updateUiState();
|
|
331
|
+
|
|
332
|
+
// Close sidebar on mobile after selecting
|
|
333
|
+
if (this._isMobile()) {
|
|
334
|
+
this.toggleSidebar(false);
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const input = this.querySelector('#chatInput');
|
|
338
|
+
if (input) {
|
|
339
|
+
input.disabled = false;
|
|
340
|
+
input.readOnly = false;
|
|
341
|
+
input.classList.remove('cursor-pointer');
|
|
342
|
+
input.focus();
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async restoreMessages(session) {
|
|
347
|
+
const container = this.querySelector('#chatMessages');
|
|
348
|
+
if (!container) return;
|
|
349
|
+
container.innerHTML = '';
|
|
350
|
+
|
|
351
|
+
if (session.messages.length === 0) {
|
|
352
|
+
this._appendWelcomeMessage(container);
|
|
155
353
|
return;
|
|
156
354
|
}
|
|
157
355
|
|
|
158
|
-
|
|
356
|
+
for (const msg of session.messages) {
|
|
357
|
+
if (msg.role === 'user') {
|
|
358
|
+
this.appendMessage('user', msg.content);
|
|
359
|
+
} else {
|
|
360
|
+
this.appendRestoredAssistantMessage(msg.content, msg.meta);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Check if server still has this session (survives restarts)
|
|
365
|
+
try {
|
|
366
|
+
const exists = await api.checkSession(session.id);
|
|
367
|
+
if (!exists) {
|
|
368
|
+
this._appendSessionResetBanner(container);
|
|
369
|
+
}
|
|
370
|
+
} catch {
|
|
371
|
+
// Server unreachable — skip banner
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
appendRestoredAssistantMessage(content, meta) {
|
|
376
|
+
const container = this.querySelector('#chatMessages');
|
|
377
|
+
const div = document.createElement('div');
|
|
378
|
+
div.className = 'response-wrapper';
|
|
379
|
+
|
|
380
|
+
const bubble = document.createElement('div');
|
|
381
|
+
bubble.className = 'flex justify-start';
|
|
382
|
+
bubble.innerHTML = `
|
|
383
|
+
<div class="response-bubble-inner group">
|
|
384
|
+
<div class="response-content markdown-content"></div>
|
|
385
|
+
<div class="tool-invocations"></div>
|
|
386
|
+
</div>
|
|
387
|
+
`;
|
|
388
|
+
|
|
389
|
+
const contentDiv = bubble.querySelector('.response-content');
|
|
390
|
+
if (content) {
|
|
391
|
+
const rendered = markdownRenderer.render(content);
|
|
392
|
+
contentDiv.innerHTML = rendered;
|
|
393
|
+
markdownRenderer.highlightCode(contentDiv);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Render persisted thinking/tool pills
|
|
397
|
+
if (meta) {
|
|
398
|
+
const toolsDiv = bubble.querySelector('.tool-invocations');
|
|
399
|
+
|
|
400
|
+
if (meta.thinking) {
|
|
401
|
+
for (const thinkingContent of meta.thinking) {
|
|
402
|
+
this._createThinkingPill(toolsDiv, thinkingContent);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
if (meta.tools) {
|
|
407
|
+
for (const t of meta.tools) {
|
|
408
|
+
if (t.output !== undefined) {
|
|
409
|
+
this._createToolPill(toolsDiv, t.runId, t.tool, t.input, t.output);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// Hide empty tool-invocations div
|
|
415
|
+
if (!toolsDiv.children.length) {
|
|
416
|
+
toolsDiv.classList.add('hidden');
|
|
417
|
+
}
|
|
418
|
+
} else {
|
|
419
|
+
bubble.querySelector('.tool-invocations').classList.add('hidden');
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
div.appendChild(bubble);
|
|
423
|
+
|
|
424
|
+
if (meta?.stats) {
|
|
425
|
+
const s = meta.stats;
|
|
426
|
+
const prefix = s.estimated ? '~' : '';
|
|
427
|
+
const tps = s.elapsed > 0 ? (s.outputTokens / (s.elapsed / 1000)).toFixed(1) : '0';
|
|
428
|
+
const statsBar = document.createElement('div');
|
|
429
|
+
statsBar.className = 'stream-stats-bar visible';
|
|
430
|
+
statsBar.innerHTML = `
|
|
431
|
+
<span class="flex items-center gap-1">
|
|
432
|
+
<i class="far fa-clock"></i>
|
|
433
|
+
<span>${this.formatElapsedTime(s.elapsed)}</span>
|
|
434
|
+
</span>
|
|
435
|
+
<span class="divider">|</span>
|
|
436
|
+
<span class="flex items-center gap-1">
|
|
437
|
+
<i class="fas fa-arrow-up text-2xs"></i>
|
|
438
|
+
<span>${prefix}${s.inputTokens} input</span>
|
|
439
|
+
</span>
|
|
440
|
+
<span class="divider">|</span>
|
|
441
|
+
<span class="flex items-center gap-1">
|
|
442
|
+
<i class="fas fa-arrow-down text-2xs"></i>
|
|
443
|
+
<span>${prefix}${s.outputTokens} output</span>
|
|
444
|
+
</span>
|
|
445
|
+
<span class="divider">|</span>
|
|
446
|
+
<span class="flex items-center gap-1">
|
|
447
|
+
<i class="fas fa-bolt text-2xs"></i>
|
|
448
|
+
<span>${prefix}${tps} tok/s</span>
|
|
449
|
+
</span>
|
|
450
|
+
`;
|
|
451
|
+
if (s.cancelled) {
|
|
452
|
+
const badge = document.createElement('span');
|
|
453
|
+
badge.className = 'badge badge-amber';
|
|
454
|
+
badge.textContent = 'Cancelled';
|
|
455
|
+
statsBar.appendChild(badge);
|
|
456
|
+
}
|
|
457
|
+
div.appendChild(statsBar);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
container.appendChild(div);
|
|
461
|
+
container.scrollTop = container.scrollHeight;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
showNewSessionModal() {
|
|
465
|
+
// Remove existing modal if any
|
|
466
|
+
const existing = document.querySelector('#newSessionModal');
|
|
467
|
+
if (existing) existing.remove();
|
|
468
|
+
|
|
469
|
+
const agents = store.get('agents') || [];
|
|
470
|
+
const workflows = store.get('workflows') || [];
|
|
471
|
+
const llms = store.get('llms') || [];
|
|
472
|
+
|
|
473
|
+
const overlay = document.createElement('div');
|
|
474
|
+
overlay.id = 'newSessionModal';
|
|
475
|
+
overlay.className = 'modal-backdrop';
|
|
476
|
+
|
|
477
|
+
let itemsHtml = '';
|
|
159
478
|
|
|
160
|
-
// Agents section
|
|
161
479
|
if (agents.length > 0) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
<div
|
|
167
|
-
<div class="
|
|
168
|
-
|
|
169
|
-
<div class="font-medium text-gray-200 mb-0.5">${agent.name}</div>
|
|
170
|
-
<div class="text-xs text-gray-500 line-clamp-2">${agent.description}</div>
|
|
171
|
-
</div>
|
|
172
|
-
${isSelected ? '<i class="fas fa-check text-blue-400 ml-2 mt-1"></i>' : ''}
|
|
173
|
-
</div>
|
|
480
|
+
itemsHtml += '<div class="modal-section-label">Agents</div>';
|
|
481
|
+
itemsHtml += agents.map(a => `
|
|
482
|
+
<button data-type="agent" data-name="${this.escapeHtml(a.name)}" class="modal-pick-item">
|
|
483
|
+
<i class="fas fa-robot text-blue text-sm"></i>
|
|
484
|
+
<div class="flex-1 min-w-0">
|
|
485
|
+
<div class="text-sm font-medium text-primary">${this.escapeHtml(a.name)}</div>
|
|
486
|
+
<div class="text-xs text-muted truncate">${this.escapeHtml(a.description || '')}</div>
|
|
174
487
|
</div>
|
|
175
|
-
|
|
176
|
-
|
|
488
|
+
</button>
|
|
489
|
+
`).join('');
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (workflows.length > 0) {
|
|
493
|
+
itemsHtml += '<div class="modal-section-label">Workflows</div>';
|
|
494
|
+
itemsHtml += workflows.map(w => `
|
|
495
|
+
<button data-type="workflow" data-name="${this.escapeHtml(w.name)}" class="modal-pick-item">
|
|
496
|
+
<i class="fas fa-project-diagram text-orange text-sm"></i>
|
|
497
|
+
<div class="flex-1 min-w-0">
|
|
498
|
+
<div class="text-sm font-medium text-primary">${this.escapeHtml(w.name)}</div>
|
|
499
|
+
<div class="text-xs text-muted truncate">${this.escapeHtml(w.description || '')}</div>
|
|
500
|
+
</div>
|
|
501
|
+
</button>
|
|
502
|
+
`).join('');
|
|
177
503
|
}
|
|
178
504
|
|
|
179
|
-
// LLMs section
|
|
180
505
|
if (llms.length > 0) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
506
|
+
const defaultLlmName = store.get('defaultLlmName');
|
|
507
|
+
itemsHtml += '<div class="modal-section-label">LLMs</div>';
|
|
508
|
+
itemsHtml += llms.map(l => {
|
|
509
|
+
const isDefault = l.name === defaultLlmName;
|
|
184
510
|
return `
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
</
|
|
191
|
-
${isSelected ? '<i class="fas fa-check text-blue-400 ml-2 mt-1"></i>' : ''}
|
|
511
|
+
<button data-type="llm" data-name="${this.escapeHtml(l.name)}" class="modal-pick-item">
|
|
512
|
+
<i class="fas fa-microchip text-purple text-sm"></i>
|
|
513
|
+
<div class="flex-1 min-w-0">
|
|
514
|
+
<div class="flex items-center gap-2">
|
|
515
|
+
<span class="text-sm font-medium text-primary">${this.escapeHtml(l.name)}</span>
|
|
516
|
+
${isDefault ? '<span class="badge badge-green text-2xs">default</span>' : ''}
|
|
192
517
|
</div>
|
|
518
|
+
<div class="text-xs text-muted truncate">${this.escapeHtml(l.model || '')}</div>
|
|
193
519
|
</div>
|
|
194
|
-
|
|
195
|
-
}).join('');
|
|
520
|
+
</button>
|
|
521
|
+
`}).join('');
|
|
196
522
|
}
|
|
197
523
|
|
|
198
|
-
|
|
524
|
+
if (!itemsHtml) {
|
|
525
|
+
itemsHtml = '<div class="text-muted text-sm text-center py-8">No agents, workflows or LLMs available</div>';
|
|
526
|
+
}
|
|
199
527
|
|
|
200
|
-
|
|
528
|
+
overlay.innerHTML = `
|
|
529
|
+
<div class="modal-content modal-content-sm">
|
|
530
|
+
<div class="modal-header">
|
|
531
|
+
<h3 class="text-lg font-semibold text-primary">New conversation</h3>
|
|
532
|
+
<button id="closeNewSessionModal" class="modal-close-btn">
|
|
533
|
+
<i class="fas fa-xmark"></i>
|
|
534
|
+
</button>
|
|
535
|
+
</div>
|
|
536
|
+
<div class="overflow-y-auto custom-scrollbar flex-1">
|
|
537
|
+
${itemsHtml}
|
|
538
|
+
</div>
|
|
539
|
+
</div>
|
|
540
|
+
`;
|
|
541
|
+
|
|
542
|
+
document.body.appendChild(overlay);
|
|
543
|
+
|
|
544
|
+
// Close on backdrop click
|
|
545
|
+
overlay.addEventListener('click', (e) => {
|
|
546
|
+
if (e.target === overlay) overlay.remove();
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
overlay.querySelector('#closeNewSessionModal').addEventListener('click', () => overlay.remove());
|
|
550
|
+
|
|
551
|
+
// Pick handler
|
|
552
|
+
overlay.querySelectorAll('.modal-pick-item').forEach(item => {
|
|
201
553
|
item.addEventListener('click', () => {
|
|
202
554
|
const type = item.dataset.type;
|
|
203
555
|
const name = item.dataset.name;
|
|
204
556
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
557
|
+
const session = sessionStore.create({
|
|
558
|
+
agentName: type === 'agent' ? name : null,
|
|
559
|
+
agentType: type,
|
|
560
|
+
llmName: type === 'llm' ? name : null,
|
|
561
|
+
workflowName: type === 'workflow' ? name : null
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
overlay.remove();
|
|
565
|
+
this.switchToSession(session.id);
|
|
566
|
+
});
|
|
567
|
+
});
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
showNewAgentModal() {
|
|
571
|
+
const existing = document.querySelector('#newAgentModal');
|
|
572
|
+
if (existing) existing.remove();
|
|
573
|
+
|
|
574
|
+
const agents = store.get('agents') || [];
|
|
575
|
+
const hasArchitect = agents.some(a => a.name === 'architect');
|
|
576
|
+
|
|
577
|
+
const overlay = document.createElement('div');
|
|
578
|
+
overlay.id = 'newAgentModal';
|
|
579
|
+
overlay.className = 'modal-backdrop';
|
|
580
|
+
|
|
581
|
+
overlay.innerHTML = `
|
|
582
|
+
<div class="modal-content modal-content-sm">
|
|
583
|
+
<div class="modal-header">
|
|
584
|
+
<h3 class="text-lg font-semibold text-primary">Create a new agent</h3>
|
|
585
|
+
<button id="closeNewAgentModal" class="modal-close-btn">
|
|
586
|
+
<i class="fas fa-xmark"></i>
|
|
587
|
+
</button>
|
|
588
|
+
</div>
|
|
589
|
+
<div class="p-4 flex flex-col gap-3">
|
|
590
|
+
${hasArchitect ? `
|
|
591
|
+
<button id="agentViaArchitect" class="new-agent-option">
|
|
592
|
+
<div class="new-agent-option-icon bg-blue">
|
|
593
|
+
<i class="fas fa-comments"></i>
|
|
594
|
+
</div>
|
|
595
|
+
<div class="flex-1 min-w-0">
|
|
596
|
+
<div class="text-sm font-medium text-primary">Chat with Architect</div>
|
|
597
|
+
<div class="text-xs text-muted">Describe what you need and the Architect agent will build it for you</div>
|
|
598
|
+
</div>
|
|
599
|
+
<i class="fas fa-chevron-right text-xs text-muted"></i>
|
|
600
|
+
</button>
|
|
601
|
+
` : ''}
|
|
602
|
+
<button id="agentViaIde" class="new-agent-option">
|
|
603
|
+
<div class="new-agent-option-icon bg-green">
|
|
604
|
+
<i class="fas fa-code"></i>
|
|
605
|
+
</div>
|
|
606
|
+
<div class="flex-1 min-w-0">
|
|
607
|
+
<div class="text-sm font-medium text-primary">Create in IDE</div>
|
|
608
|
+
<div class="text-xs text-muted">Open the IDE editor with a blank agent template</div>
|
|
609
|
+
</div>
|
|
610
|
+
<i class="fas fa-chevron-right text-xs text-muted"></i>
|
|
611
|
+
</button>
|
|
612
|
+
</div>
|
|
613
|
+
</div>
|
|
614
|
+
`;
|
|
615
|
+
|
|
616
|
+
document.body.appendChild(overlay);
|
|
617
|
+
|
|
618
|
+
overlay.addEventListener('click', (e) => {
|
|
619
|
+
if (e.target === overlay) overlay.remove();
|
|
620
|
+
});
|
|
621
|
+
|
|
622
|
+
overlay.querySelector('#closeNewAgentModal').addEventListener('click', () => overlay.remove());
|
|
623
|
+
|
|
624
|
+
if (hasArchitect) {
|
|
625
|
+
overlay.querySelector('#agentViaArchitect').addEventListener('click', () => {
|
|
626
|
+
overlay.remove();
|
|
627
|
+
const session = sessionStore.create({
|
|
628
|
+
agentName: 'architect',
|
|
629
|
+
agentType: 'agent',
|
|
630
|
+
llmName: null,
|
|
631
|
+
workflowName: null,
|
|
632
|
+
});
|
|
633
|
+
this.switchToSession(session.id);
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
overlay.querySelector('#agentViaIde').addEventListener('click', () => {
|
|
638
|
+
overlay.remove();
|
|
639
|
+
store.set('activeTab', 'ide');
|
|
640
|
+
window.location.hash = 'ide';
|
|
641
|
+
// Wait for IDE to mount, then trigger the new agent dialog
|
|
642
|
+
setTimeout(() => {
|
|
643
|
+
const ide = document.querySelector('ide-view');
|
|
644
|
+
if (ide && ide._selectResourceType) {
|
|
645
|
+
ide._selectResourceType('agent');
|
|
223
646
|
}
|
|
647
|
+
}, 200);
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
deleteSession(sessionId) {
|
|
652
|
+
sessionStore.delete(sessionId);
|
|
653
|
+
const activeId = sessionStore.getActiveId();
|
|
654
|
+
|
|
655
|
+
if (!activeId || activeId === sessionId) {
|
|
656
|
+
// Switch to most recent remaining session
|
|
657
|
+
const sessions = sessionStore.getAll();
|
|
658
|
+
if (sessions.length > 0) {
|
|
659
|
+
this.switchToSession(sessions[0].id);
|
|
660
|
+
} else {
|
|
661
|
+
this.showEmptyState();
|
|
662
|
+
this.renderSessionList();
|
|
663
|
+
}
|
|
664
|
+
} else {
|
|
665
|
+
this.renderSessionList();
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
showEmptyState() {
|
|
670
|
+
const container = this.querySelector('#chatMessages');
|
|
671
|
+
if (container) {
|
|
672
|
+
container.innerHTML = `
|
|
673
|
+
<div class="flex-1 flex items-center justify-center h-full">
|
|
674
|
+
<div class="text-center text-muted">
|
|
675
|
+
<i class="fas fa-comments text-4xl mb-4 text-muted"></i>
|
|
676
|
+
<p class="text-lg">Start a new conversation</p>
|
|
677
|
+
<p class="text-sm mt-1">Click "New chat" to begin</p>
|
|
678
|
+
</div>
|
|
679
|
+
</div>
|
|
680
|
+
`;
|
|
681
|
+
}
|
|
224
682
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
683
|
+
this.updateChatHeader(null);
|
|
684
|
+
|
|
685
|
+
const input = this.querySelector('#chatInput');
|
|
686
|
+
if (input) {
|
|
687
|
+
input.disabled = false;
|
|
688
|
+
input.readOnly = true;
|
|
689
|
+
input.classList.add('cursor-pointer');
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const btn = this.querySelector('#sendMessageBtn');
|
|
693
|
+
if (btn) btn.disabled = true;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
updateChatHeader(session) {
|
|
697
|
+
const header = this.querySelector('#chatHeader');
|
|
698
|
+
if (!header) return;
|
|
699
|
+
|
|
700
|
+
if (!session) {
|
|
701
|
+
header.innerHTML = '<span class="text-muted">No conversation selected</span>';
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
let name, badgeText, badgeVariant, icon;
|
|
706
|
+
if (session.agentType === 'workflow') {
|
|
707
|
+
name = session.workflowName || 'Workflow';
|
|
708
|
+
badgeText = 'Workflow';
|
|
709
|
+
badgeVariant = 'badge-orange';
|
|
710
|
+
icon = 'fa-project-diagram';
|
|
711
|
+
} else if (session.agentType === 'agent') {
|
|
712
|
+
name = session.agentName || 'Agent';
|
|
713
|
+
badgeText = 'Agent';
|
|
714
|
+
badgeVariant = 'badge-blue';
|
|
715
|
+
icon = 'fa-robot';
|
|
716
|
+
} else {
|
|
717
|
+
name = session.llmName || 'LLM';
|
|
718
|
+
badgeText = 'LLM';
|
|
719
|
+
badgeVariant = 'badge-purple';
|
|
720
|
+
icon = 'fa-microchip';
|
|
721
|
+
}
|
|
722
|
+
const isAgent = session.agentType === 'agent';
|
|
723
|
+
|
|
724
|
+
let extraBadges = '';
|
|
725
|
+
if (isAgent) {
|
|
726
|
+
const agents = store.get('agents') || [];
|
|
727
|
+
const agent = agents.find(a => a.name === session.agentName);
|
|
728
|
+
if (agent) {
|
|
729
|
+
if (agent.publish?.enabled) {
|
|
730
|
+
const chatUrl = `/chat/${encodeURIComponent(agent.name)}`;
|
|
731
|
+
extraBadges += `<a href="${chatUrl}" target="_blank" class="badge badge-pill badge-green no-underline" title="Open published chat"><i class="fas fa-globe text-2xs"></i> Published</a>`;
|
|
228
732
|
}
|
|
229
733
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
734
|
+
const hasMemory = agent.memory === true || (agent.memory && agent.memory.enabled);
|
|
735
|
+
if (hasMemory) {
|
|
736
|
+
extraBadges += `<span class="badge badge-pill badge-amber" title="Persistent memory enabled"><i class="fas fa-brain text-2xs"></i> Memory</span>`;
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
if (agent.tools?.length) {
|
|
740
|
+
const toolNames = agent.tools.map(t => typeof t === 'string' ? t : t.name);
|
|
741
|
+
const toolListHtml = toolNames.map(t => `<div class="tools-popover-item">${this.escapeHtml(t)}</div>`).join('');
|
|
742
|
+
extraBadges += `
|
|
743
|
+
<span class="tools-badge-wrapper">
|
|
744
|
+
<span class="badge badge-pill badge-gray"><i class="fas fa-wrench text-2xs"></i> ${toolNames.length} tool${toolNames.length !== 1 ? 's' : ''}</span>
|
|
745
|
+
<div class="tools-popover">${toolListHtml}</div>
|
|
746
|
+
</span>`;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
header.innerHTML = `
|
|
752
|
+
<div class="flex items-center gap-2 flex-wrap">
|
|
753
|
+
<i class="fas ${icon} text-sm text-secondary"></i>
|
|
754
|
+
<span class="font-medium text-primary">${this.escapeHtml(name)}</span>
|
|
755
|
+
<span class="badge badge-pill ${badgeVariant}">${badgeText}</span>
|
|
756
|
+
${extraBadges}
|
|
757
|
+
</div>
|
|
758
|
+
`;
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// --- File Attachments ---
|
|
762
|
+
|
|
763
|
+
handleFileSelect(e) {
|
|
764
|
+
const files = Array.from(e.target.files);
|
|
765
|
+
e.target.value = '';
|
|
766
|
+
|
|
767
|
+
const needsConversion = ['image/webp', 'image/bmp', 'image/tiff'];
|
|
768
|
+
|
|
769
|
+
for (const file of files) {
|
|
770
|
+
if (needsConversion.includes(file.type)) {
|
|
771
|
+
this.convertImageToJpeg(file);
|
|
772
|
+
} else {
|
|
773
|
+
const reader = new FileReader();
|
|
774
|
+
reader.onload = () => {
|
|
775
|
+
const dataUrl = reader.result;
|
|
776
|
+
const commaIdx = dataUrl.indexOf(',');
|
|
777
|
+
const base64 = dataUrl.slice(commaIdx + 1);
|
|
778
|
+
const mediaType = file.type || 'application/octet-stream';
|
|
779
|
+
|
|
780
|
+
this.pendingAttachments.push({ data: base64, mediaType, name: file.name });
|
|
781
|
+
this.renderAttachmentPreview();
|
|
782
|
+
};
|
|
783
|
+
reader.readAsDataURL(file);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
convertImageToJpeg(file) {
|
|
789
|
+
const img = new Image();
|
|
790
|
+
const url = URL.createObjectURL(file);
|
|
791
|
+
img.onload = () => {
|
|
792
|
+
const canvas = document.createElement('canvas');
|
|
793
|
+
canvas.width = img.naturalWidth;
|
|
794
|
+
canvas.height = img.naturalHeight;
|
|
795
|
+
canvas.getContext('2d').drawImage(img, 0, 0);
|
|
796
|
+
URL.revokeObjectURL(url);
|
|
797
|
+
|
|
798
|
+
const dataUrl = canvas.toDataURL('image/jpeg', 0.92);
|
|
799
|
+
const base64 = dataUrl.split(',')[1];
|
|
800
|
+
this.pendingAttachments.push({ data: base64, mediaType: 'image/jpeg', name: file.name });
|
|
801
|
+
this.renderAttachmentPreview();
|
|
802
|
+
};
|
|
803
|
+
img.src = url;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
renderAttachmentPreview() {
|
|
807
|
+
const preview = this.querySelector('#attachmentPreview');
|
|
808
|
+
if (!preview) return;
|
|
809
|
+
|
|
810
|
+
if (this.pendingAttachments.length === 0) {
|
|
811
|
+
preview.classList.remove('visible');
|
|
812
|
+
preview.innerHTML = '';
|
|
813
|
+
return;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
preview.classList.add('visible');
|
|
817
|
+
preview.innerHTML = this.pendingAttachments.map((att, i) => {
|
|
818
|
+
const isImage = att.mediaType.startsWith('image/');
|
|
819
|
+
const thumb = isImage
|
|
820
|
+
? `<img src="data:${att.mediaType};base64,${att.data}">`
|
|
821
|
+
: `<i class="fas fa-file text-secondary text-lg"></i>`;
|
|
822
|
+
return `
|
|
823
|
+
<div class="attachment-pill">
|
|
824
|
+
${thumb}
|
|
825
|
+
<span class="truncate attachment-name">${this.escapeHtml(att.name)}</span>
|
|
826
|
+
<button class="attachment-remove" data-index="${i}">
|
|
827
|
+
<i class="fas fa-xmark text-xs"></i>
|
|
828
|
+
</button>
|
|
829
|
+
</div>
|
|
830
|
+
`;
|
|
831
|
+
}).join('');
|
|
832
|
+
|
|
833
|
+
preview.querySelectorAll('.attachment-remove').forEach(btn => {
|
|
834
|
+
btn.addEventListener('click', (e) => {
|
|
835
|
+
const idx = parseInt(e.currentTarget.dataset.index, 10);
|
|
836
|
+
this.pendingAttachments.splice(idx, 1);
|
|
837
|
+
this.renderAttachmentPreview();
|
|
233
838
|
});
|
|
234
839
|
});
|
|
235
840
|
}
|
|
236
841
|
|
|
237
|
-
|
|
842
|
+
clearAttachments() {
|
|
843
|
+
this.pendingAttachments = [];
|
|
844
|
+
this.renderAttachmentPreview();
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// --- Messaging ---
|
|
848
|
+
|
|
849
|
+
async sendMessage() {
|
|
850
|
+
const input = this.querySelector('#chatInput');
|
|
851
|
+
const message = input.value.trim();
|
|
238
852
|
const selectionType = store.get('selectionType');
|
|
239
853
|
const agent = store.get('selectedAgent');
|
|
240
854
|
const llm = store.get('selectedLlm');
|
|
241
|
-
const
|
|
242
|
-
|
|
855
|
+
const activeId = sessionStore.getActiveId();
|
|
856
|
+
|
|
857
|
+
const hasAttachments = this.pendingAttachments.length > 0;
|
|
858
|
+
if ((!message && !hasAttachments) || this.isLoading || !activeId) return;
|
|
859
|
+
|
|
860
|
+
// Handle workflow messages (including interrupt responses)
|
|
861
|
+
if (selectionType === 'workflow') {
|
|
862
|
+
return this._sendWorkflowMessage(message);
|
|
863
|
+
}
|
|
243
864
|
|
|
244
865
|
const selected = selectionType === 'agent' ? agent : llm;
|
|
866
|
+
if (!selected) return;
|
|
867
|
+
|
|
868
|
+
const attachments = hasAttachments ? [...this.pendingAttachments] : null;
|
|
869
|
+
|
|
870
|
+
this.appendMessage('user', message || '(attached files)', { attachments });
|
|
871
|
+
sessionStore.addMessage(activeId, 'user', message || '(attached files)');
|
|
872
|
+
input.value = '';
|
|
873
|
+
input.style.height = 'auto';
|
|
874
|
+
this.clearAttachments();
|
|
875
|
+
|
|
876
|
+
this.isLoading = true;
|
|
877
|
+
this.updateUiState();
|
|
878
|
+
|
|
879
|
+
const responseId = 'response-' + Date.now();
|
|
880
|
+
this.createResponseBubble(responseId);
|
|
881
|
+
|
|
882
|
+
const abortController = new AbortController();
|
|
883
|
+
this.currentAbortController = abortController;
|
|
884
|
+
this.streamUsageData = null;
|
|
885
|
+
this.startStreamTimer(responseId);
|
|
886
|
+
|
|
887
|
+
try {
|
|
888
|
+
let response;
|
|
889
|
+
if (selectionType === 'agent') {
|
|
890
|
+
const inputVars = agent.inputVariables || ['message'];
|
|
891
|
+
const inputObj = {};
|
|
892
|
+
inputObj[inputVars[0] || 'message'] = message;
|
|
893
|
+
if (attachments) inputObj.attachments = attachments;
|
|
894
|
+
response = await api.streamAgent(agent.name, inputObj, activeId, { signal: abortController.signal });
|
|
895
|
+
} else {
|
|
896
|
+
response = await api.streamLLM(llm.name, message, activeId, attachments, { signal: abortController.signal });
|
|
897
|
+
}
|
|
898
|
+
|
|
899
|
+
streamManager.start(activeId, {
|
|
900
|
+
response,
|
|
901
|
+
abortController,
|
|
902
|
+
streamType: selectionType === 'agent' ? 'agent' : 'llm',
|
|
903
|
+
inputMessage: message,
|
|
904
|
+
responseId,
|
|
905
|
+
});
|
|
245
906
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
907
|
+
this._attachToStream(activeId, responseId);
|
|
908
|
+
} catch (e) {
|
|
909
|
+
const wasCancelled = e.name === 'AbortError';
|
|
910
|
+
if (!wasCancelled) {
|
|
911
|
+
this.updateResponseError(responseId, `Error: ${e.message}`);
|
|
912
|
+
}
|
|
913
|
+
this.stopStreamTimer(responseId, message, '', wasCancelled);
|
|
914
|
+
this.currentAbortController = null;
|
|
915
|
+
this.isLoading = false;
|
|
916
|
+
this.updateUiState();
|
|
250
917
|
}
|
|
251
918
|
|
|
252
|
-
|
|
919
|
+
this.renderSessionList();
|
|
253
920
|
}
|
|
254
921
|
|
|
255
|
-
|
|
256
|
-
const
|
|
257
|
-
if (
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
922
|
+
_attachToStream(sessionId, responseId, initialThinkingState) {
|
|
923
|
+
const state = streamManager.getState(sessionId);
|
|
924
|
+
if (!state) return;
|
|
925
|
+
|
|
926
|
+
const thinkingState = initialThinkingState || {
|
|
927
|
+
inThinking: false,
|
|
928
|
+
thinkingSections: [],
|
|
929
|
+
currentSection: null,
|
|
930
|
+
thinkingContent: '',
|
|
931
|
+
thinkingPill: null,
|
|
932
|
+
};
|
|
933
|
+
|
|
934
|
+
// For LLM streams, remove loading dots (skip if reconnecting with no content yet)
|
|
935
|
+
if (state.streamType === 'llm') {
|
|
936
|
+
const hasContent = state.content || state.events.length > 0;
|
|
937
|
+
if (hasContent || !initialThinkingState) {
|
|
938
|
+
const bubble = this.querySelector(`#${responseId}`);
|
|
939
|
+
if (bubble) {
|
|
940
|
+
const contentDiv = bubble.querySelector('.response-content');
|
|
941
|
+
const loadingDots = contentDiv?.querySelector('.loading-dots');
|
|
942
|
+
if (loadingDots) {
|
|
943
|
+
loadingDots.remove();
|
|
944
|
+
bubble.querySelector('.response-bubble-inner')?.classList.remove('loading');
|
|
945
|
+
contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
|
|
946
|
+
contentDiv.innerHTML = '';
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
let hasToolCalls = false;
|
|
953
|
+
|
|
954
|
+
this._streamUnsubscribe = streamManager.subscribe(sessionId, (event) => {
|
|
955
|
+
if (event.type === '_stream_end') {
|
|
956
|
+
this.streamUsageData = state.usageData;
|
|
957
|
+
const wasCancelled = event.status === 'cancelled';
|
|
958
|
+
|
|
959
|
+
// Finalize any in-progress thinking pill
|
|
960
|
+
const bubble = this.querySelector(`#${responseId}`);
|
|
961
|
+
if (bubble) {
|
|
962
|
+
const toolsDiv = bubble.querySelector('.tool-invocations');
|
|
963
|
+
this.finalizeThinkingPill(toolsDiv, thinkingState);
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
if (state.streamType === 'agent') {
|
|
967
|
+
if (hasToolCalls && !state.content.trim()) {
|
|
968
|
+
const bubble = this.querySelector(`#${responseId}`);
|
|
969
|
+
if (bubble) {
|
|
970
|
+
const contentDiv = bubble.querySelector('.response-content');
|
|
971
|
+
const loadingDots = contentDiv?.querySelector('.loading-dots');
|
|
972
|
+
if (loadingDots) {
|
|
973
|
+
loadingDots.remove();
|
|
974
|
+
bubble.querySelector('.response-bubble-inner')?.classList.remove('loading');
|
|
975
|
+
contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
|
|
976
|
+
contentDiv.innerHTML = '';
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
this.stopStreamTimer(responseId, state.inputMessage, state.content, wasCancelled);
|
|
983
|
+
this.currentAbortController = null;
|
|
984
|
+
this.isLoading = false;
|
|
985
|
+
this.updateUiState();
|
|
986
|
+
this.renderSessionList();
|
|
987
|
+
this._streamUnsubscribe = null;
|
|
988
|
+
|
|
989
|
+
const input = this.querySelector('#chatInput');
|
|
990
|
+
if (input) input.focus();
|
|
991
|
+
return;
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
if (state.streamType === 'agent') {
|
|
995
|
+
if (event.type === 'tool_start' || event.type === 'tool_end') hasToolCalls = true;
|
|
996
|
+
this.handleStreamEvent(event, responseId, state.content, thinkingState);
|
|
997
|
+
} else {
|
|
998
|
+
if (event.type === 'usage') {
|
|
999
|
+
this.streamUsageData = state.usageData;
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
if (event.error) {
|
|
1003
|
+
this.updateResponseError(responseId, `Error: ${event.error}`);
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
if (event.type === 'thinking') {
|
|
1007
|
+
const bubble = this.querySelector(`#${responseId}`);
|
|
1008
|
+
if (bubble) {
|
|
1009
|
+
const toolsDiv = bubble.querySelector('.tool-invocations');
|
|
1010
|
+
const container = this.querySelector('#chatMessages');
|
|
1011
|
+
const loadingDots = bubble.querySelector('.response-content .loading-dots');
|
|
1012
|
+
if (loadingDots) {
|
|
1013
|
+
loadingDots.remove();
|
|
1014
|
+
bubble.querySelector('.response-bubble-inner')?.classList.remove('loading');
|
|
1015
|
+
const contentDiv = bubble.querySelector('.response-content');
|
|
1016
|
+
contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
|
|
1017
|
+
contentDiv.innerHTML = '';
|
|
1018
|
+
}
|
|
1019
|
+
this.handleThinkingEvent(event, toolsDiv, thinkingState, container);
|
|
1020
|
+
}
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
if (event.content) {
|
|
1024
|
+
const bubble = this.querySelector(`#${responseId}`);
|
|
1025
|
+
if (bubble) {
|
|
1026
|
+
const contentDiv = bubble.querySelector('.response-content');
|
|
1027
|
+
const toolsDiv = bubble.querySelector('.tool-invocations');
|
|
1028
|
+
const loadingDots = contentDiv?.querySelector('.loading-dots');
|
|
1029
|
+
if (loadingDots) {
|
|
1030
|
+
loadingDots.remove();
|
|
1031
|
+
bubble.querySelector('.response-bubble-inner')?.classList.remove('loading');
|
|
1032
|
+
contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
|
|
1033
|
+
contentDiv.innerHTML = '';
|
|
1034
|
+
}
|
|
1035
|
+
this.finalizeThinkingPill(toolsDiv, thinkingState);
|
|
1036
|
+
const container = this.querySelector('#chatMessages');
|
|
1037
|
+
this.renderLlmContentStreaming(contentDiv, state.content, responseId, thinkingState);
|
|
1038
|
+
if (container) container.scrollTop = container.scrollHeight;
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
_reconnectToStream(sessionId) {
|
|
1046
|
+
const state = streamManager.getState(sessionId);
|
|
1047
|
+
if (!state || state.status !== 'streaming') return false;
|
|
1048
|
+
|
|
1049
|
+
this.createResponseBubble(state.responseId);
|
|
1050
|
+
const snapshotState = this._renderStreamSnapshot(state.responseId, state);
|
|
1051
|
+
|
|
1052
|
+
const thinkingState = {
|
|
1053
|
+
inThinking: false,
|
|
1054
|
+
thinkingSections: [],
|
|
1055
|
+
currentSection: null,
|
|
1056
|
+
thinkingContent: snapshotState.thinkingContent || '',
|
|
1057
|
+
thinkingPill: snapshotState.thinkingPill || null,
|
|
1058
|
+
};
|
|
1059
|
+
|
|
1060
|
+
this.streamUsageData = state.usageData;
|
|
1061
|
+
this.startStreamTimer(state.responseId, state.startTime);
|
|
1062
|
+
|
|
1063
|
+
this.isLoading = true;
|
|
1064
|
+
this.currentAbortController = state.abortController;
|
|
1065
|
+
this.updateUiState();
|
|
1066
|
+
|
|
1067
|
+
this._attachToStream(sessionId, state.responseId, thinkingState);
|
|
1068
|
+
return true;
|
|
1069
|
+
}
|
|
1070
|
+
|
|
1071
|
+
_renderStreamSnapshot(responseId, state) {
|
|
1072
|
+
const bubble = this.querySelector(`#${responseId}`);
|
|
1073
|
+
if (!bubble) return {};
|
|
1074
|
+
|
|
1075
|
+
const contentDiv = bubble.querySelector('.response-content');
|
|
1076
|
+
const toolsDiv = bubble.querySelector('.tool-invocations');
|
|
1077
|
+
|
|
1078
|
+
const hasVisualContent = state.content ||
|
|
1079
|
+
state.events.some(e => e.type === 'thinking' || e.type === 'tool_start' || e.type === 'content');
|
|
1080
|
+
const loadingDots = contentDiv.querySelector('.loading-dots');
|
|
1081
|
+
if (loadingDots && hasVisualContent) {
|
|
1082
|
+
loadingDots.remove();
|
|
1083
|
+
bubble.querySelector('.response-bubble-inner').classList.remove('loading');
|
|
1084
|
+
contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
|
|
1085
|
+
contentDiv.innerHTML = '';
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
let activeThinkingPill = null;
|
|
1089
|
+
let activeThinkingContent = '';
|
|
1090
|
+
|
|
1091
|
+
if (state.streamType === 'agent') {
|
|
1092
|
+
const tools = new Map();
|
|
1093
|
+
const completedThinking = [];
|
|
1094
|
+
let currentThinking = '';
|
|
1095
|
+
let lastWasThinking = false;
|
|
1096
|
+
let lastReactIteration = null;
|
|
1097
|
+
|
|
1098
|
+
for (const event of state.events) {
|
|
1099
|
+
if (event.type === 'thinking') {
|
|
1100
|
+
currentThinking += event.content;
|
|
1101
|
+
lastWasThinking = true;
|
|
1102
|
+
} else {
|
|
1103
|
+
if (lastWasThinking && currentThinking) {
|
|
1104
|
+
completedThinking.push(currentThinking);
|
|
1105
|
+
currentThinking = '';
|
|
1106
|
+
}
|
|
1107
|
+
lastWasThinking = false;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
if (event.type === 'tool_start') {
|
|
1111
|
+
tools.set(event.runId, { tool: event.tool, input: event.input, done: false });
|
|
1112
|
+
}
|
|
1113
|
+
if (event.type === 'tool_end') {
|
|
1114
|
+
const t = tools.get(event.runId);
|
|
1115
|
+
if (t) { t.output = event.output; t.done = true; }
|
|
1116
|
+
}
|
|
1117
|
+
if (event.type === 'react_iteration') {
|
|
1118
|
+
lastReactIteration = event;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1122
|
+
for (const content of completedThinking) {
|
|
1123
|
+
this._createThinkingPill(toolsDiv, content);
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
if (lastWasThinking && currentThinking) {
|
|
1127
|
+
const pill = document.createElement('div');
|
|
1128
|
+
pill.className = 'tool-pill thinking';
|
|
1129
|
+
pill.innerHTML = '<i class="fas fa-brain animate-pulse text-2xs"></i><span>Thinking...</span>';
|
|
1130
|
+
toolsDiv.appendChild(pill);
|
|
1131
|
+
activeThinkingPill = pill;
|
|
1132
|
+
activeThinkingContent = currentThinking;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
for (const [runId, t] of tools) {
|
|
1136
|
+
if (t.done) {
|
|
1137
|
+
this._createToolPill(toolsDiv, runId, t.tool, t.input, t.output);
|
|
1138
|
+
} else {
|
|
1139
|
+
const toolEl = document.createElement('div');
|
|
1140
|
+
toolEl.id = `tool-${runId}`;
|
|
1141
|
+
toolEl.className = 'tool-pill';
|
|
1142
|
+
toolEl.dataset.toolInput = typeof t.input === 'string' ? t.input : JSON.stringify(t.input, null, 2);
|
|
1143
|
+
toolEl.innerHTML = `<i class="fas fa-circle-notch animate-spin text-blue text-2xs"></i><span>${this.escapeHtml(t.tool)}</span>`;
|
|
1144
|
+
toolsDiv.appendChild(toolEl);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
if (lastReactIteration) {
|
|
1149
|
+
const wrapper = bubble.closest('.response-wrapper');
|
|
1150
|
+
const statusText = wrapper?.querySelector('.stream-status-text');
|
|
1151
|
+
if (statusText) {
|
|
1152
|
+
const contextKB = (lastReactIteration.contextChars / 1024).toFixed(1);
|
|
1153
|
+
statusText.textContent = `Iteration ${lastReactIteration.iteration} · ${contextKB} KB context`;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
if (state.content) {
|
|
1159
|
+
const div = document.createElement('div');
|
|
1160
|
+
div.className = 'content-text markdown-content';
|
|
1161
|
+
div.innerHTML = markdownRenderer.render(state.content);
|
|
1162
|
+
markdownRenderer.highlightCode(div);
|
|
1163
|
+
contentDiv.appendChild(div);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
const container = this.querySelector('#chatMessages');
|
|
1167
|
+
if (container) container.scrollTop = container.scrollHeight;
|
|
1168
|
+
|
|
1169
|
+
return { thinkingPill: activeThinkingPill, thinkingContent: activeThinkingContent };
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
// --- Workflow chat integration ---
|
|
1173
|
+
|
|
1174
|
+
async _sendWorkflowMessage(message) {
|
|
1175
|
+
const activeId = sessionStore.getActiveId();
|
|
1176
|
+
const wfState = workflowTasks.get(activeId);
|
|
1177
|
+
|
|
1178
|
+
if (wfState?.interruptState) {
|
|
1179
|
+
return this._respondToWorkflowInterrupt(activeId, message);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
const workflow = store.get('selectedWorkflow');
|
|
1183
|
+
if (!workflow) return;
|
|
1184
|
+
|
|
1185
|
+
const schema = workflow.inputSchema || {};
|
|
1186
|
+
const firstField = Object.keys(schema)[0] || 'input';
|
|
1187
|
+
const inputObj = { [firstField]: message };
|
|
1188
|
+
|
|
1189
|
+
const input = this.querySelector('#chatInput');
|
|
1190
|
+
this.appendMessage('user', message);
|
|
1191
|
+
sessionStore.addMessage(activeId, 'user', message);
|
|
1192
|
+
input.value = '';
|
|
1193
|
+
input.style.height = 'auto';
|
|
1194
|
+
this.clearAttachments();
|
|
1195
|
+
|
|
1196
|
+
const responseId = 'response-' + Date.now();
|
|
1197
|
+
this.createResponseBubble(responseId);
|
|
1198
|
+
this.isLoading = true;
|
|
1199
|
+
this.updateUiState();
|
|
1200
|
+
this.startStreamTimer(responseId);
|
|
1201
|
+
|
|
1202
|
+
const abortController = new AbortController();
|
|
1203
|
+
workflowTasks.set(activeId, {
|
|
1204
|
+
responseId,
|
|
1205
|
+
startTime: Date.now(),
|
|
1206
|
+
chatOutputFormat: workflow.chatOutputFormat || 'json',
|
|
1207
|
+
workflowName: workflow.name,
|
|
1208
|
+
abortController,
|
|
1209
|
+
interruptState: null,
|
|
1210
|
+
status: 'streaming',
|
|
1211
|
+
events: [],
|
|
1212
|
+
inputMessage: message,
|
|
1213
|
+
taskId: null,
|
|
1214
|
+
});
|
|
1215
|
+
|
|
1216
|
+
try {
|
|
1217
|
+
const response = await api.startWorkflowStream(workflow.name, inputObj, abortController.signal);
|
|
1218
|
+
await this._processWorkflowStream(response, activeId, responseId);
|
|
1219
|
+
} catch (e) {
|
|
1220
|
+
if (e.name === 'AbortError') {
|
|
1221
|
+
this._finishWorkflowStream(activeId, responseId, null, null, true);
|
|
262
1222
|
} else {
|
|
263
|
-
|
|
1223
|
+
this.updateResponseError(responseId, `Error: ${e.message}`);
|
|
1224
|
+
this.stopStreamTimer(responseId, message, '', false);
|
|
1225
|
+
this.isLoading = false;
|
|
1226
|
+
this.updateUiState();
|
|
264
1227
|
}
|
|
265
1228
|
}
|
|
266
|
-
}
|
|
267
1229
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
const message = input.value.trim();
|
|
271
|
-
const selectionType = store.get('selectionType');
|
|
272
|
-
const agent = store.get('selectedAgent');
|
|
273
|
-
const llm = store.get('selectedLlm');
|
|
1230
|
+
this.renderSessionList();
|
|
1231
|
+
}
|
|
274
1232
|
|
|
275
|
-
|
|
1233
|
+
async _respondToWorkflowInterrupt(sessionId, message) {
|
|
1234
|
+
const wfState = workflowTasks.get(sessionId);
|
|
1235
|
+
if (!wfState?.interruptState) return;
|
|
276
1236
|
|
|
277
|
-
|
|
1237
|
+
const { threadId, workflowName } = wfState.interruptState;
|
|
278
1238
|
|
|
279
|
-
|
|
1239
|
+
const input = this.querySelector('#chatInput');
|
|
280
1240
|
this.appendMessage('user', message);
|
|
1241
|
+
sessionStore.addMessage(sessionId, 'user', message);
|
|
281
1242
|
input.value = '';
|
|
282
1243
|
input.style.height = 'auto';
|
|
283
1244
|
|
|
284
|
-
this.isLoading = true;
|
|
285
|
-
this.updateUiState();
|
|
286
|
-
|
|
287
1245
|
const responseId = 'response-' + Date.now();
|
|
288
1246
|
this.createResponseBubble(responseId);
|
|
289
1247
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
1248
|
+
const abortController = new AbortController();
|
|
1249
|
+
wfState.responseId = responseId;
|
|
1250
|
+
wfState.interruptState = null;
|
|
1251
|
+
wfState.status = 'streaming';
|
|
1252
|
+
wfState.abortController = abortController;
|
|
293
1253
|
|
|
294
|
-
|
|
295
|
-
|
|
1254
|
+
this.isLoading = true;
|
|
1255
|
+
this.updateUiState();
|
|
1256
|
+
this.startStreamTimer(responseId);
|
|
296
1257
|
|
|
297
1258
|
try {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
} else if (selectionType === 'llm') {
|
|
301
|
-
finalContent = await this.sendLlmMessage(llm, message, responseId);
|
|
302
|
-
}
|
|
1259
|
+
const response = await api.resumeWorkflowStream(workflowName, threadId, message, abortController.signal);
|
|
1260
|
+
await this._processWorkflowStream(response, sessionId, responseId);
|
|
303
1261
|
} catch (e) {
|
|
304
1262
|
if (e.name === 'AbortError') {
|
|
305
|
-
|
|
1263
|
+
this._finishWorkflowStream(sessionId, responseId, null, null, true);
|
|
306
1264
|
} else {
|
|
307
1265
|
this.updateResponseError(responseId, `Error: ${e.message}`);
|
|
1266
|
+
this.stopStreamTimer(responseId, message, '', false);
|
|
1267
|
+
this.isLoading = false;
|
|
1268
|
+
this.updateUiState();
|
|
308
1269
|
}
|
|
309
|
-
} finally {
|
|
310
|
-
this.stopStreamTimer(responseId, message, finalContent, wasCancelled);
|
|
311
|
-
this.currentAbortController = null;
|
|
312
|
-
this.isLoading = false;
|
|
313
|
-
this.updateUiState();
|
|
314
|
-
input.focus();
|
|
315
1270
|
}
|
|
1271
|
+
|
|
1272
|
+
this.renderSessionList();
|
|
316
1273
|
}
|
|
317
1274
|
|
|
318
|
-
async
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
1275
|
+
async _processWorkflowStream(response, sessionId, responseId) {
|
|
1276
|
+
if (!response.ok) {
|
|
1277
|
+
const text = await response.text();
|
|
1278
|
+
let msg = `HTTP ${response.status}`;
|
|
1279
|
+
try { msg = JSON.parse(text).error || msg; } catch { /* use status */ }
|
|
1280
|
+
throw new Error(msg);
|
|
1281
|
+
}
|
|
322
1282
|
|
|
323
|
-
const
|
|
324
|
-
const reader = res.body.getReader();
|
|
1283
|
+
const reader = response.body.getReader();
|
|
325
1284
|
const decoder = new TextDecoder();
|
|
326
|
-
|
|
327
|
-
const bubble = this.querySelector(`#${responseId}`);
|
|
328
|
-
const contentDiv = bubble.querySelector('.response-content');
|
|
329
|
-
const container = this.querySelector('#chatMessages');
|
|
330
|
-
const thinkingState = {
|
|
331
|
-
inThinking: false,
|
|
332
|
-
thinkingSections: [],
|
|
333
|
-
currentSection: null
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
let currentContent = '';
|
|
337
1285
|
let buffer = '';
|
|
338
|
-
let hasToolCalls = false;
|
|
339
1286
|
|
|
340
1287
|
while (true) {
|
|
341
1288
|
const { done, value } = await reader.read();
|
|
342
1289
|
if (done) break;
|
|
343
1290
|
|
|
344
|
-
|
|
345
|
-
buffer += chunk;
|
|
346
|
-
|
|
1291
|
+
buffer += decoder.decode(value, { stream: true });
|
|
347
1292
|
const lines = buffer.split('\n');
|
|
348
1293
|
buffer = lines.pop() || '';
|
|
349
1294
|
|
|
350
1295
|
for (const line of lines) {
|
|
351
|
-
if (line.
|
|
352
|
-
|
|
353
|
-
if (
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
// Handle server-side errors
|
|
361
|
-
if (event.error) {
|
|
362
|
-
this.updateResponseError(responseId, `Error: ${event.error}`);
|
|
363
|
-
return currentContent;
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
if (event.type === 'content') {
|
|
367
|
-
currentContent += event.content;
|
|
368
|
-
}
|
|
369
|
-
if (event.type === 'tool_start' || event.type === 'tool_end') {
|
|
370
|
-
hasToolCalls = true;
|
|
371
|
-
}
|
|
372
|
-
this.handleStreamEvent(event, responseId, currentContent, thinkingState);
|
|
373
|
-
} catch (e) {
|
|
374
|
-
console.error('Error parsing stream event', e, data);
|
|
375
|
-
}
|
|
1296
|
+
if (!line.startsWith('data: ')) continue;
|
|
1297
|
+
const payload = line.slice(6).trim();
|
|
1298
|
+
if (payload === '[DONE]') continue;
|
|
1299
|
+
|
|
1300
|
+
try {
|
|
1301
|
+
const update = JSON.parse(payload);
|
|
1302
|
+
this._handleWorkflowStreamEvent(update, sessionId, responseId);
|
|
1303
|
+
} catch (e) {
|
|
1304
|
+
console.error('Workflow stream parse error:', e);
|
|
376
1305
|
}
|
|
377
1306
|
}
|
|
378
1307
|
}
|
|
379
1308
|
|
|
380
|
-
//
|
|
381
|
-
if (
|
|
382
|
-
const
|
|
383
|
-
if (
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
contentDiv.innerHTML = '';
|
|
1309
|
+
// Process any remaining buffer
|
|
1310
|
+
if (buffer.startsWith('data: ')) {
|
|
1311
|
+
const payload = buffer.slice(6).trim();
|
|
1312
|
+
if (payload && payload !== '[DONE]') {
|
|
1313
|
+
try {
|
|
1314
|
+
const update = JSON.parse(payload);
|
|
1315
|
+
this._handleWorkflowStreamEvent(update, sessionId, responseId);
|
|
1316
|
+
} catch (e) { /* ignore */ }
|
|
389
1317
|
}
|
|
390
1318
|
}
|
|
391
1319
|
|
|
392
|
-
|
|
1320
|
+
// If stream ended without a result/error event, treat as error
|
|
1321
|
+
const wfState = workflowTasks.get(sessionId);
|
|
1322
|
+
if (wfState && wfState.status === 'streaming') {
|
|
1323
|
+
this._finishWorkflowStream(sessionId, responseId, null, 'Stream ended unexpectedly', false);
|
|
1324
|
+
}
|
|
393
1325
|
}
|
|
394
1326
|
|
|
395
|
-
|
|
396
|
-
const
|
|
397
|
-
|
|
398
|
-
|
|
1327
|
+
_handleWorkflowStreamEvent(update, sessionId, responseId) {
|
|
1328
|
+
const wfState = workflowTasks.get(sessionId);
|
|
1329
|
+
if (!wfState) return;
|
|
1330
|
+
|
|
1331
|
+
// Capture task ID from server
|
|
1332
|
+
if (update.type === 'task_id') {
|
|
1333
|
+
wfState.taskId = update.taskId;
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
399
1336
|
|
|
400
1337
|
const bubble = this.querySelector(`#${responseId}`);
|
|
1338
|
+
|
|
1339
|
+
if (update.type === 'status' && bubble) {
|
|
1340
|
+
const event = update.data;
|
|
1341
|
+
wfState.events.push(event);
|
|
1342
|
+
|
|
1343
|
+
const toolsDiv = bubble.querySelector('.tool-invocations');
|
|
1344
|
+
this._renderWorkflowEvent(event, toolsDiv, bubble);
|
|
1345
|
+
|
|
1346
|
+
// Handle interrupt — the stream returns a result with interrupted output
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
if (update.type === 'result') {
|
|
1350
|
+
const result = update.data;
|
|
1351
|
+
// Check if it's an interrupt
|
|
1352
|
+
if (result?.output?.interrupted && result?.output?.threadId) {
|
|
1353
|
+
this._handleWorkflowInterrupt(sessionId, responseId, result.output);
|
|
1354
|
+
} else if (result?.error) {
|
|
1355
|
+
this._finishWorkflowStream(sessionId, responseId, null, result.error, false);
|
|
1356
|
+
} else {
|
|
1357
|
+
this._finishWorkflowStream(sessionId, responseId, result, null, false);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
if (update.type === 'error') {
|
|
1362
|
+
this._finishWorkflowStream(sessionId, responseId, null, update.error, false);
|
|
1363
|
+
}
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
_renderWorkflowEvent(event, toolsDiv, bubble) {
|
|
401
1367
|
const contentDiv = bubble.querySelector('.response-content');
|
|
402
|
-
const loadingDots = contentDiv.querySelector('.loading-dots');
|
|
403
|
-
const container = this.querySelector('#chatMessages');
|
|
404
1368
|
|
|
405
|
-
if (
|
|
1369
|
+
if (event.type === 'step_start') {
|
|
1370
|
+
const pill = document.createElement('div');
|
|
1371
|
+
pill.id = `wf-step-${event.stepId}`;
|
|
1372
|
+
pill.className = 'tool-pill';
|
|
1373
|
+
pill.innerHTML = `<i class="fas fa-circle-notch animate-spin text-blue text-2xs"></i><span>${this.escapeHtml(event.stepId)}</span>`;
|
|
1374
|
+
toolsDiv.appendChild(pill);
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
if (event.type === 'step_complete') {
|
|
1378
|
+
const pill = toolsDiv.querySelector(`#wf-step-${event.stepId}`);
|
|
1379
|
+
if (pill) {
|
|
1380
|
+
pill.className = 'tool-pill done';
|
|
1381
|
+
pill.innerHTML = `<i class="fas fa-check text-green text-2xs"></i><span>${this.escapeHtml(event.stepId)}</span>`;
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
if (event.type === 'step_error') {
|
|
1386
|
+
const pill = toolsDiv.querySelector(`#wf-step-${event.stepId}`);
|
|
1387
|
+
if (pill) {
|
|
1388
|
+
pill.className = 'tool-pill done';
|
|
1389
|
+
pill.innerHTML = `<i class="fas fa-times text-red text-2xs"></i><span>${this.escapeHtml(event.stepId)}</span>`;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
if (event.type === 'tool_call') {
|
|
1394
|
+
const toolName = event.message?.replace(/^Calling:?\s*/i, '').split(/\s/)[0] || 'tool';
|
|
1395
|
+
const pill = document.createElement('div');
|
|
1396
|
+
pill.className = 'tool-pill wf-tool-active';
|
|
1397
|
+
|
|
1398
|
+
const pillContent = document.createElement('span');
|
|
1399
|
+
pillContent.className = 'inline-flex items-center gap-1';
|
|
1400
|
+
pillContent.innerHTML = `<i class="fas fa-circle-notch animate-spin text-blue text-2xs"></i><span>${this.escapeHtml(toolName)}</span>`;
|
|
1401
|
+
pill.appendChild(pillContent);
|
|
1402
|
+
|
|
1403
|
+
// Create details panel (populated on tool_result)
|
|
1404
|
+
const details = document.createElement('div');
|
|
1405
|
+
details.className = 'tool-invocation-details';
|
|
1406
|
+
if (event.toolInput) {
|
|
1407
|
+
const inputSection = document.createElement('div');
|
|
1408
|
+
inputSection.className = 'tool-detail-section';
|
|
1409
|
+
inputSection.innerHTML = '<h4>Input</h4>';
|
|
1410
|
+
const inputPre = document.createElement('pre');
|
|
1411
|
+
inputPre.className = 'tool-detail-pre custom-scrollbar';
|
|
1412
|
+
inputPre.textContent = event.toolInput;
|
|
1413
|
+
inputSection.appendChild(inputPre);
|
|
1414
|
+
details.appendChild(inputSection);
|
|
1415
|
+
}
|
|
1416
|
+
pill.appendChild(details);
|
|
1417
|
+
pill.addEventListener('click', (e) => {
|
|
1418
|
+
if (details.contains(e.target)) return;
|
|
1419
|
+
e.preventDefault();
|
|
1420
|
+
e.stopPropagation();
|
|
1421
|
+
toolsDiv.querySelectorAll('.tool-invocation-details.visible').forEach(d => {
|
|
1422
|
+
if (d !== details) d.classList.remove('visible');
|
|
1423
|
+
});
|
|
1424
|
+
details.classList.toggle('visible');
|
|
1425
|
+
});
|
|
1426
|
+
toolsDiv.appendChild(pill);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
if (event.type === 'tool_result') {
|
|
1430
|
+
const activePill = toolsDiv.querySelector('.wf-tool-active');
|
|
1431
|
+
if (activePill) {
|
|
1432
|
+
activePill.classList.remove('wf-tool-active');
|
|
1433
|
+
activePill.classList.add('done');
|
|
1434
|
+
const icon = activePill.querySelector('i');
|
|
1435
|
+
if (icon) icon.className = 'fas fa-check text-green text-2xs';
|
|
1436
|
+
|
|
1437
|
+
// Append output to the details panel
|
|
1438
|
+
if (event.toolOutput) {
|
|
1439
|
+
const details = activePill.querySelector('.tool-invocation-details');
|
|
1440
|
+
if (details) {
|
|
1441
|
+
const outputSection = document.createElement('div');
|
|
1442
|
+
outputSection.className = 'tool-detail-section';
|
|
1443
|
+
outputSection.innerHTML = '<h4>Output</h4>';
|
|
1444
|
+
const outputPre = document.createElement('pre');
|
|
1445
|
+
outputPre.className = 'tool-detail-pre custom-scrollbar';
|
|
1446
|
+
outputPre.textContent = event.toolOutput;
|
|
1447
|
+
outputSection.appendChild(outputPre);
|
|
1448
|
+
details.appendChild(outputSection);
|
|
1449
|
+
}
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
if (event.type === 'tool_discovery') {
|
|
1455
|
+
// Only show the final summary pill (e.g. "35 total tools ready"), skip intermediate progress
|
|
1456
|
+
if (event.message?.includes('total tools')) {
|
|
1457
|
+
const pill = document.createElement('div');
|
|
1458
|
+
pill.className = 'tool-pill done';
|
|
1459
|
+
pill.innerHTML = `<i class="fas fa-plug text-purple text-2xs"></i><span>${this.escapeHtml(event.message)}</span>`;
|
|
1460
|
+
toolsDiv.appendChild(pill);
|
|
1461
|
+
}
|
|
1462
|
+
const wrapper = bubble.closest('.response-wrapper');
|
|
1463
|
+
const statusText = wrapper?.querySelector('.stream-status-text');
|
|
1464
|
+
if (statusText) statusText.textContent = event.message || 'Discovering tools...';
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
if (event.type === 'react_iteration' || event.type === 'workflow_start') {
|
|
1468
|
+
const wrapper = bubble.closest('.response-wrapper');
|
|
1469
|
+
const statusText = wrapper?.querySelector('.stream-status-text');
|
|
1470
|
+
if (statusText) statusText.textContent = event.message || 'Processing...';
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// Remove loading dots on first meaningful event
|
|
1474
|
+
const loadingDots = contentDiv?.querySelector('.loading-dots');
|
|
1475
|
+
if (loadingDots && (event.type === 'step_start' || event.type === 'workflow_start' || event.type === 'tool_call' || event.type === 'tool_discovery' || event.type === 'react_iteration')) {
|
|
406
1476
|
loadingDots.remove();
|
|
407
|
-
bubble.querySelector('.response-bubble-inner')
|
|
408
|
-
bubble.querySelector('.response-bubble-inner').classList.add('py-3');
|
|
1477
|
+
bubble.querySelector('.response-bubble-inner')?.classList.remove('loading');
|
|
409
1478
|
contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
|
|
410
1479
|
contentDiv.innerHTML = '';
|
|
411
1480
|
}
|
|
412
1481
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
inThinking: false,
|
|
417
|
-
thinkingSections: [],
|
|
418
|
-
currentSection: null
|
|
419
|
-
};
|
|
1482
|
+
const container = this.querySelector('#chatMessages');
|
|
1483
|
+
if (container) container.scrollTop = container.scrollHeight;
|
|
1484
|
+
}
|
|
420
1485
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
1486
|
+
_handleWorkflowInterrupt(sessionId, responseId, interruptData) {
|
|
1487
|
+
const wfState = workflowTasks.get(sessionId);
|
|
1488
|
+
if (!wfState) return;
|
|
424
1489
|
|
|
425
|
-
|
|
426
|
-
|
|
1490
|
+
wfState.status = 'interrupted';
|
|
1491
|
+
const question = interruptData?.question || 'Input required';
|
|
1492
|
+
wfState.interruptState = {
|
|
1493
|
+
question,
|
|
1494
|
+
threadId: interruptData?.threadId,
|
|
1495
|
+
workflowName: wfState.workflowName,
|
|
1496
|
+
};
|
|
427
1497
|
|
|
428
|
-
|
|
429
|
-
|
|
1498
|
+
const bubble = this.querySelector(`#${responseId}`);
|
|
1499
|
+
if (bubble) {
|
|
1500
|
+
const contentDiv = bubble.querySelector('.response-content');
|
|
1501
|
+
const loadingDots = contentDiv?.querySelector('.loading-dots');
|
|
1502
|
+
if (loadingDots) {
|
|
1503
|
+
loadingDots.remove();
|
|
1504
|
+
bubble.querySelector('.response-bubble-inner')?.classList.remove('loading');
|
|
1505
|
+
contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
|
|
1506
|
+
contentDiv.innerHTML = '';
|
|
1507
|
+
}
|
|
430
1508
|
|
|
431
|
-
|
|
432
|
-
|
|
1509
|
+
const div = document.createElement('div');
|
|
1510
|
+
div.className = 'content-text markdown-content';
|
|
1511
|
+
div.innerHTML = markdownRenderer.render(question);
|
|
1512
|
+
markdownRenderer.highlightCode(div);
|
|
1513
|
+
contentDiv.appendChild(div);
|
|
1514
|
+
|
|
1515
|
+
const wrapper = bubble.closest('.response-wrapper');
|
|
1516
|
+
const statusBar = wrapper?.querySelector('.stream-status-bar');
|
|
1517
|
+
if (statusBar) {
|
|
1518
|
+
const statusText = statusBar.querySelector('.stream-status-text');
|
|
1519
|
+
if (statusText) statusText.textContent = 'Waiting for input...';
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
433
1522
|
|
|
434
|
-
|
|
435
|
-
const data = line.slice(6);
|
|
1523
|
+
sessionStore.addMessage(sessionId, 'assistant', question);
|
|
436
1524
|
|
|
437
|
-
|
|
1525
|
+
if (this.streamTimerInterval) {
|
|
1526
|
+
clearInterval(this.streamTimerInterval);
|
|
1527
|
+
this.streamTimerInterval = null;
|
|
1528
|
+
}
|
|
1529
|
+
this.isLoading = false;
|
|
1530
|
+
this.updateUiState();
|
|
438
1531
|
|
|
439
|
-
|
|
440
|
-
|
|
1532
|
+
const input = this.querySelector('#chatInput');
|
|
1533
|
+
if (input) input.focus();
|
|
1534
|
+
}
|
|
441
1535
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
1536
|
+
_finishWorkflowStream(sessionId, responseId, result, error, wasCancelled) {
|
|
1537
|
+
const wfState = workflowTasks.get(sessionId);
|
|
1538
|
+
if (!wfState) return;
|
|
446
1539
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
1540
|
+
wfState.status = 'done';
|
|
1541
|
+
wfState.abortController = null;
|
|
1542
|
+
|
|
1543
|
+
const bubble = this.querySelector(`#${responseId}`);
|
|
1544
|
+
if (bubble) {
|
|
1545
|
+
const contentDiv = bubble.querySelector('.response-content');
|
|
1546
|
+
const loadingDots = contentDiv?.querySelector('.loading-dots');
|
|
1547
|
+
if (loadingDots) {
|
|
1548
|
+
loadingDots.remove();
|
|
1549
|
+
bubble.querySelector('.response-bubble-inner')?.classList.remove('loading');
|
|
1550
|
+
contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
|
|
1551
|
+
contentDiv.innerHTML = '';
|
|
1552
|
+
}
|
|
455
1553
|
|
|
456
|
-
|
|
1554
|
+
if (error) {
|
|
1555
|
+
const errorDiv = document.createElement('div');
|
|
1556
|
+
errorDiv.className = 'text-red text-sm';
|
|
1557
|
+
errorDiv.textContent = `Error: ${error}`;
|
|
1558
|
+
contentDiv.appendChild(errorDiv);
|
|
1559
|
+
} else if (wasCancelled) {
|
|
1560
|
+
// No output content for cancelled
|
|
1561
|
+
} else if (result?.output) {
|
|
1562
|
+
this._renderWorkflowOutput(contentDiv, result.output, wfState.chatOutputFormat);
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
457
1565
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
}
|
|
1566
|
+
let content = '';
|
|
1567
|
+
if (error) {
|
|
1568
|
+
content = `Error: ${error}`;
|
|
1569
|
+
} else if (result?.output) {
|
|
1570
|
+
if (wfState.chatOutputFormat === 'text') {
|
|
1571
|
+
content = Object.values(result.output).join('\n\n');
|
|
1572
|
+
} else {
|
|
1573
|
+
content = '```json\n' + JSON.stringify(result.output, null, 2) + '\n```';
|
|
467
1574
|
}
|
|
468
1575
|
}
|
|
469
1576
|
|
|
470
|
-
|
|
1577
|
+
const elapsed = wfState.startTime ? Date.now() - wfState.startTime : 0;
|
|
1578
|
+
const meta = {
|
|
1579
|
+
thinking: [],
|
|
1580
|
+
tools: wfState.events
|
|
1581
|
+
.filter(e => e.type === 'step_complete')
|
|
1582
|
+
.map(e => ({ runId: e.stepId, tool: e.stepId, input: e.agent || '', output: e.message || 'Completed' })),
|
|
1583
|
+
stats: {
|
|
1584
|
+
elapsed,
|
|
1585
|
+
inputTokens: Math.round((wfState.inputMessage || '').length / 4),
|
|
1586
|
+
outputTokens: Math.round(content.length / 4),
|
|
1587
|
+
cancelled: wasCancelled,
|
|
1588
|
+
estimated: true,
|
|
1589
|
+
},
|
|
1590
|
+
};
|
|
1591
|
+
sessionStore.addMessage(sessionId, 'assistant', content, meta);
|
|
1592
|
+
|
|
1593
|
+
this.stopStreamTimer(responseId, wfState.inputMessage || '', content, wasCancelled);
|
|
1594
|
+
this.isLoading = false;
|
|
1595
|
+
this.updateUiState();
|
|
1596
|
+
this.renderSessionList();
|
|
1597
|
+
|
|
1598
|
+
setTimeout(() => workflowTasks.delete(sessionId), 10000);
|
|
1599
|
+
|
|
1600
|
+
const input = this.querySelector('#chatInput');
|
|
1601
|
+
if (input) input.focus();
|
|
471
1602
|
}
|
|
472
1603
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
const text = fullContent.slice(pos).trim();
|
|
485
|
-
if (text) {
|
|
486
|
-
parts.push({ type: 'text', content: text });
|
|
487
|
-
}
|
|
488
|
-
break;
|
|
489
|
-
}
|
|
1604
|
+
_renderWorkflowOutput(contentDiv, output, format) {
|
|
1605
|
+
const div = document.createElement('div');
|
|
1606
|
+
div.className = 'content-text markdown-content';
|
|
1607
|
+
if (format === 'text') {
|
|
1608
|
+
div.innerHTML = markdownRenderer.render(Object.values(output).join('\n\n'));
|
|
1609
|
+
} else {
|
|
1610
|
+
div.innerHTML = markdownRenderer.render('```json\n' + JSON.stringify(output, null, 2) + '\n```');
|
|
1611
|
+
}
|
|
1612
|
+
markdownRenderer.highlightCode(div);
|
|
1613
|
+
contentDiv.appendChild(div);
|
|
1614
|
+
}
|
|
490
1615
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
if (text) {
|
|
495
|
-
parts.push({ type: 'text', content: text });
|
|
496
|
-
}
|
|
497
|
-
}
|
|
1616
|
+
_reconnectWorkflowStream(sessionId) {
|
|
1617
|
+
const wfState = workflowTasks.get(sessionId);
|
|
1618
|
+
if (!wfState || wfState.status === 'done') return false;
|
|
498
1619
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
1620
|
+
if (wfState.interruptState) {
|
|
1621
|
+
this.isLoading = false;
|
|
1622
|
+
this.updateUiState();
|
|
1623
|
+
return true;
|
|
1624
|
+
}
|
|
502
1625
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
// Complete think section
|
|
511
|
-
const thinkContent = fullContent.slice(thinkContentStart, thinkEnd).trim();
|
|
512
|
-
parts.push({ type: 'think', content: thinkContent, complete: true, index: thinkIndex });
|
|
513
|
-
thinkIndex++;
|
|
514
|
-
pos = thinkEnd + 8; // After [/THINK]
|
|
1626
|
+
// Re-create the response bubble and replay cached events
|
|
1627
|
+
this.createResponseBubble(wfState.responseId);
|
|
1628
|
+
const bubble = this.querySelector(`#${wfState.responseId}`);
|
|
1629
|
+
if (bubble && wfState.events.length > 0) {
|
|
1630
|
+
const toolsDiv = bubble.querySelector('.tool-invocations');
|
|
1631
|
+
for (const event of wfState.events) {
|
|
1632
|
+
this._renderWorkflowEvent(event, toolsDiv, bubble);
|
|
515
1633
|
}
|
|
516
1634
|
}
|
|
517
1635
|
|
|
518
|
-
|
|
519
|
-
|
|
1636
|
+
this.startStreamTimer(wfState.responseId, wfState.startTime);
|
|
1637
|
+
this.isLoading = true;
|
|
1638
|
+
this.currentAbortController = null;
|
|
1639
|
+
this.updateUiState();
|
|
520
1640
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
const existingChild = contentDiv.children[currentChildIndex];
|
|
1641
|
+
return true;
|
|
1642
|
+
}
|
|
524
1643
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
1644
|
+
_attachClickDetails(pillEl, detailsEl, toolsDiv, container) {
|
|
1645
|
+
pillEl.addEventListener('click', (e) => {
|
|
1646
|
+
if (detailsEl.contains(e.target)) return;
|
|
1647
|
+
e.preventDefault();
|
|
1648
|
+
e.stopPropagation();
|
|
1649
|
+
toolsDiv.querySelectorAll('.tool-invocation-details.visible').forEach(d => {
|
|
1650
|
+
if (d !== detailsEl) d.classList.remove('visible');
|
|
1651
|
+
});
|
|
1652
|
+
const wasHidden = !detailsEl.classList.contains('visible');
|
|
1653
|
+
detailsEl.classList.toggle('visible');
|
|
1654
|
+
if (wasHidden && container) {
|
|
1655
|
+
const pillRect = pillEl.getBoundingClientRect();
|
|
1656
|
+
const containerRect = container.getBoundingClientRect();
|
|
1657
|
+
const spaceRight = containerRect.right - pillRect.left;
|
|
1658
|
+
if (spaceRight < 420) {
|
|
1659
|
+
detailsEl.style.right = '0';
|
|
1660
|
+
detailsEl.style.left = 'auto';
|
|
531
1661
|
} else {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
div.className = 'content-text markdown-content';
|
|
535
|
-
const renderedHtml = markdownRenderer.render(part.content);
|
|
536
|
-
div.innerHTML = renderedHtml;
|
|
537
|
-
markdownRenderer.highlightCode(div);
|
|
538
|
-
if (existingChild) {
|
|
539
|
-
contentDiv.insertBefore(div, existingChild);
|
|
540
|
-
} else {
|
|
541
|
-
contentDiv.appendChild(div);
|
|
542
|
-
}
|
|
1662
|
+
detailsEl.style.left = '0';
|
|
1663
|
+
detailsEl.style.right = 'auto';
|
|
543
1664
|
}
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
const label = existingChild.querySelector('.think-label');
|
|
551
|
-
const content = existingChild.querySelector('.think-content');
|
|
552
|
-
if (label) {
|
|
553
|
-
label.textContent = part.complete ? 'Thinking' : 'Thinking...';
|
|
554
|
-
}
|
|
555
|
-
if (content) {
|
|
556
|
-
const renderedHtml = markdownRenderer.render(part.content);
|
|
557
|
-
content.innerHTML = renderedHtml;
|
|
558
|
-
content.classList.add('markdown-content');
|
|
559
|
-
markdownRenderer.highlightCode(content);
|
|
560
|
-
}
|
|
561
|
-
} else {
|
|
562
|
-
// Create new think section with event listener
|
|
563
|
-
const section = document.createElement('div');
|
|
564
|
-
section.className = 'think-section mb-3 border-l-2 border-blue-500/40 pl-3 py-1';
|
|
565
|
-
section.dataset.thinkIndex = part.index;
|
|
566
|
-
|
|
567
|
-
const toggle = document.createElement('button');
|
|
568
|
-
toggle.className = 'think-toggle flex items-center gap-1.5 text-xs text-blue-400 hover:text-blue-300 py-1 cursor-pointer';
|
|
569
|
-
toggle.dataset.thinkId = thinkId;
|
|
570
|
-
|
|
571
|
-
toggle.innerHTML = `
|
|
572
|
-
<i class="fas fa-brain text-xs"></i>
|
|
573
|
-
<span class="font-medium think-label">${part.complete ? 'Thinking' : 'Thinking...'}</span>
|
|
574
|
-
<i class="fas fa-chevron-right text-[10px] transition-transform think-chevron"></i>
|
|
575
|
-
`;
|
|
576
|
-
|
|
577
|
-
const thinkContent = document.createElement('div');
|
|
578
|
-
thinkContent.id = thinkId;
|
|
579
|
-
thinkContent.className = 'think-content hidden text-sm text-gray-400 markdown-content mt-1 leading-relaxed';
|
|
580
|
-
const renderedHtml = markdownRenderer.render(part.content);
|
|
581
|
-
thinkContent.innerHTML = renderedHtml;
|
|
582
|
-
markdownRenderer.highlightCode(thinkContent);
|
|
583
|
-
|
|
584
|
-
// Add click handler ONCE when creating
|
|
585
|
-
toggle.addEventListener('click', (e) => {
|
|
586
|
-
e.preventDefault();
|
|
587
|
-
e.stopPropagation();
|
|
588
|
-
const content = section.querySelector('.think-content');
|
|
589
|
-
const chevron = section.querySelector('.think-chevron');
|
|
590
|
-
|
|
591
|
-
if (content && chevron) {
|
|
592
|
-
if (content.classList.contains('hidden')) {
|
|
593
|
-
content.classList.remove('hidden');
|
|
594
|
-
chevron.classList.remove('fa-chevron-right');
|
|
595
|
-
chevron.classList.add('fa-chevron-down');
|
|
596
|
-
} else {
|
|
597
|
-
content.classList.add('hidden');
|
|
598
|
-
chevron.classList.remove('fa-chevron-down');
|
|
599
|
-
chevron.classList.add('fa-chevron-right');
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
});
|
|
1665
|
+
}
|
|
1666
|
+
});
|
|
1667
|
+
document.addEventListener('click', (e) => {
|
|
1668
|
+
if (!pillEl.contains(e.target)) detailsEl.classList.remove('visible');
|
|
1669
|
+
}, { capture: true });
|
|
1670
|
+
}
|
|
603
1671
|
|
|
604
|
-
|
|
605
|
-
|
|
1672
|
+
_createThinkingPill(toolsDiv, content) {
|
|
1673
|
+
const container = this.querySelector('#chatMessages');
|
|
1674
|
+
const pill = document.createElement('div');
|
|
1675
|
+
pill.className = 'tool-pill done thinking';
|
|
1676
|
+
|
|
1677
|
+
const pillContent = document.createElement('span');
|
|
1678
|
+
pillContent.className = 'inline-flex items-center gap-1';
|
|
1679
|
+
pillContent.innerHTML = '<i class="fas fa-brain text-purple text-2xs"></i><span>Thinking</span>';
|
|
1680
|
+
pill.appendChild(pillContent);
|
|
1681
|
+
|
|
1682
|
+
const details = document.createElement('div');
|
|
1683
|
+
details.className = 'tool-invocation-details';
|
|
1684
|
+
|
|
1685
|
+
const section = document.createElement('div');
|
|
1686
|
+
section.className = 'tool-detail-section';
|
|
1687
|
+
const pre = document.createElement('div');
|
|
1688
|
+
pre.className = 'tool-detail-pre markdown-content custom-scrollbar';
|
|
1689
|
+
pre.innerHTML = markdownRenderer.render(content);
|
|
1690
|
+
markdownRenderer.highlightCode(pre);
|
|
1691
|
+
section.appendChild(pre);
|
|
1692
|
+
details.appendChild(section);
|
|
1693
|
+
pill.appendChild(details);
|
|
1694
|
+
|
|
1695
|
+
this._attachClickDetails(pill, details, toolsDiv, container);
|
|
1696
|
+
toolsDiv.appendChild(pill);
|
|
1697
|
+
}
|
|
606
1698
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
1699
|
+
_createToolPill(toolsDiv, runId, toolName, input, output) {
|
|
1700
|
+
const container = this.querySelector('#chatMessages');
|
|
1701
|
+
const toolInput = typeof input === 'string' ? input : JSON.stringify(input, null, 2);
|
|
1702
|
+
const toolOutput = typeof output === 'string' ? output : JSON.stringify(output, null, 2);
|
|
1703
|
+
|
|
1704
|
+
const toolEl = document.createElement('div');
|
|
1705
|
+
toolEl.id = `tool-${runId}`;
|
|
1706
|
+
toolEl.className = 'tool-pill done';
|
|
1707
|
+
|
|
1708
|
+
const pillContent = document.createElement('span');
|
|
1709
|
+
pillContent.className = 'inline-flex items-center gap-1';
|
|
1710
|
+
pillContent.innerHTML = `<i class="fas fa-check text-green text-2xs"></i><span>${this.escapeHtml(toolName)}</span>`;
|
|
1711
|
+
toolEl.appendChild(pillContent);
|
|
1712
|
+
|
|
1713
|
+
const details = document.createElement('div');
|
|
1714
|
+
details.className = 'tool-invocation-details';
|
|
1715
|
+
|
|
1716
|
+
if (toolInput) {
|
|
1717
|
+
const inputSection = document.createElement('div');
|
|
1718
|
+
inputSection.className = 'tool-detail-section';
|
|
1719
|
+
inputSection.innerHTML = '<h4>Input</h4>';
|
|
1720
|
+
const inputPre = document.createElement('pre');
|
|
1721
|
+
inputPre.className = 'tool-detail-pre custom-scrollbar';
|
|
1722
|
+
inputPre.textContent = toolInput;
|
|
1723
|
+
inputSection.appendChild(inputPre);
|
|
1724
|
+
details.appendChild(inputSection);
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
const outputSection = document.createElement('div');
|
|
1728
|
+
outputSection.className = 'tool-detail-section';
|
|
1729
|
+
outputSection.innerHTML = '<h4>Output</h4>';
|
|
1730
|
+
const outputPre = document.createElement('pre');
|
|
1731
|
+
outputPre.className = 'tool-detail-pre custom-scrollbar';
|
|
1732
|
+
outputPre.textContent = toolOutput;
|
|
1733
|
+
outputSection.appendChild(outputPre);
|
|
1734
|
+
details.appendChild(outputSection);
|
|
1735
|
+
|
|
1736
|
+
toolEl.appendChild(details);
|
|
1737
|
+
|
|
1738
|
+
this._attachClickDetails(toolEl, details, toolsDiv, container);
|
|
1739
|
+
toolsDiv.appendChild(toolEl);
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
renderLlmContentStreaming(contentDiv, fullContent, responseId, state) {
|
|
1743
|
+
const existing = contentDiv.querySelector('.content-text');
|
|
1744
|
+
if (existing) {
|
|
1745
|
+
const renderedHtml = markdownRenderer.render(fullContent);
|
|
1746
|
+
existing.innerHTML = renderedHtml;
|
|
1747
|
+
markdownRenderer.highlightCode(existing);
|
|
1748
|
+
} else {
|
|
1749
|
+
const div = document.createElement('div');
|
|
1750
|
+
div.className = 'content-text markdown-content';
|
|
1751
|
+
div.innerHTML = markdownRenderer.render(fullContent);
|
|
1752
|
+
markdownRenderer.highlightCode(div);
|
|
1753
|
+
contentDiv.appendChild(div);
|
|
615
1754
|
}
|
|
1755
|
+
}
|
|
616
1756
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
1757
|
+
handleThinkingEvent(event, toolsDiv, thinkingState, container) {
|
|
1758
|
+
if (!thinkingState.thinkingContent) {
|
|
1759
|
+
thinkingState.thinkingContent = '';
|
|
1760
|
+
}
|
|
1761
|
+
thinkingState.thinkingContent += event.content;
|
|
1762
|
+
|
|
1763
|
+
// Create pill on first thinking chunk
|
|
1764
|
+
if (!thinkingState.thinkingPill) {
|
|
1765
|
+
const pill = document.createElement('div');
|
|
1766
|
+
pill.className = 'tool-pill thinking';
|
|
1767
|
+
pill.innerHTML = `
|
|
1768
|
+
<i class="fas fa-brain animate-pulse text-2xs"></i>
|
|
1769
|
+
<span>Thinking...</span>
|
|
1770
|
+
`;
|
|
1771
|
+
toolsDiv.appendChild(pill);
|
|
1772
|
+
thinkingState.thinkingPill = pill;
|
|
1773
|
+
container.scrollTop = container.scrollHeight;
|
|
620
1774
|
}
|
|
621
1775
|
}
|
|
622
1776
|
|
|
1777
|
+
finalizeThinkingPill(toolsDiv, thinkingState) {
|
|
1778
|
+
const container = this.querySelector('#chatMessages');
|
|
1779
|
+
const pill = thinkingState.thinkingPill;
|
|
1780
|
+
if (!pill) return;
|
|
1781
|
+
|
|
1782
|
+
const content = thinkingState.thinkingContent || '';
|
|
1783
|
+
thinkingState.thinkingPill = null;
|
|
1784
|
+
thinkingState.thinkingContent = '';
|
|
1785
|
+
|
|
1786
|
+
pill.className = 'tool-pill done thinking';
|
|
1787
|
+
pill.innerHTML = '';
|
|
1788
|
+
|
|
1789
|
+
const pillContent = document.createElement('span');
|
|
1790
|
+
pillContent.className = 'inline-flex items-center gap-1';
|
|
1791
|
+
pillContent.innerHTML = '<i class="fas fa-brain text-purple text-2xs"></i><span>Thinking</span>';
|
|
1792
|
+
pill.appendChild(pillContent);
|
|
1793
|
+
|
|
1794
|
+
const details = document.createElement('div');
|
|
1795
|
+
details.className = 'tool-invocation-details';
|
|
1796
|
+
|
|
1797
|
+
const section = document.createElement('div');
|
|
1798
|
+
section.className = 'tool-detail-section';
|
|
1799
|
+
const pre = document.createElement('div');
|
|
1800
|
+
pre.className = 'tool-detail-pre markdown-content custom-scrollbar';
|
|
1801
|
+
pre.innerHTML = markdownRenderer.render(content);
|
|
1802
|
+
markdownRenderer.highlightCode(pre);
|
|
1803
|
+
section.appendChild(pre);
|
|
1804
|
+
details.appendChild(section);
|
|
1805
|
+
pill.appendChild(details);
|
|
1806
|
+
|
|
1807
|
+
this._attachClickDetails(pill, details, toolsDiv, container);
|
|
1808
|
+
}
|
|
1809
|
+
|
|
623
1810
|
createResponseBubble(id) {
|
|
624
1811
|
const container = this.querySelector('#chatMessages');
|
|
625
1812
|
const wrapper = document.createElement('div');
|
|
@@ -629,57 +1816,52 @@ export class AgentsView extends Component {
|
|
|
629
1816
|
div.id = id;
|
|
630
1817
|
div.className = 'flex justify-start';
|
|
631
1818
|
div.innerHTML = `
|
|
632
|
-
<div class="response-bubble-inner
|
|
1819
|
+
<div class="response-bubble-inner loading group">
|
|
633
1820
|
<div class="response-content whitespace-pre-wrap flex items-center">
|
|
634
|
-
<div class="loading-dots
|
|
635
|
-
<div
|
|
636
|
-
<div
|
|
637
|
-
<div
|
|
1821
|
+
<div class="loading-dots">
|
|
1822
|
+
<div></div>
|
|
1823
|
+
<div></div>
|
|
1824
|
+
<div></div>
|
|
638
1825
|
</div>
|
|
639
1826
|
</div>
|
|
640
|
-
<div class="tool-invocations
|
|
1827
|
+
<div class="tool-invocations"></div>
|
|
641
1828
|
</div>
|
|
642
1829
|
`;
|
|
643
1830
|
|
|
644
1831
|
wrapper.appendChild(div);
|
|
645
1832
|
|
|
646
|
-
// Stream status bar (visible during streaming)
|
|
647
1833
|
const statusBar = document.createElement('div');
|
|
648
|
-
statusBar.className = 'stream-status-bar
|
|
1834
|
+
statusBar.className = 'stream-status-bar';
|
|
649
1835
|
statusBar.innerHTML = `
|
|
650
|
-
<div class="
|
|
1836
|
+
<div class="status-dot-pulse"></div>
|
|
651
1837
|
<span class="stream-status-text">Generating...</span>
|
|
652
|
-
<span class="stream-elapsed text-
|
|
653
|
-
<button class="stream-cancel-btn
|
|
654
|
-
Stop
|
|
655
|
-
</button>
|
|
1838
|
+
<span class="stream-elapsed text-muted">0.0s</span>
|
|
1839
|
+
<button class="stream-cancel-btn">Stop</button>
|
|
656
1840
|
`;
|
|
657
1841
|
wrapper.appendChild(statusBar);
|
|
658
1842
|
|
|
659
|
-
// Wire up cancel button
|
|
660
1843
|
statusBar.querySelector('.stream-cancel-btn').addEventListener('click', () => this.cancelCurrentStream());
|
|
661
1844
|
|
|
662
|
-
// Stats bar (visible after completion)
|
|
663
1845
|
const statsBar = document.createElement('div');
|
|
664
|
-
statsBar.className = 'stream-stats-bar
|
|
1846
|
+
statsBar.className = 'stream-stats-bar';
|
|
665
1847
|
statsBar.innerHTML = `
|
|
666
1848
|
<span class="flex items-center gap-1">
|
|
667
1849
|
<i class="far fa-clock"></i>
|
|
668
1850
|
<span class="stats-elapsed"></span>
|
|
669
1851
|
</span>
|
|
670
|
-
<span class="
|
|
1852
|
+
<span class="divider">|</span>
|
|
671
1853
|
<span class="flex items-center gap-1">
|
|
672
|
-
<i class="fas fa-arrow-up text-
|
|
1854
|
+
<i class="fas fa-arrow-up text-2xs"></i>
|
|
673
1855
|
<span class="stats-input-tokens"></span>
|
|
674
1856
|
</span>
|
|
675
|
-
<span class="
|
|
1857
|
+
<span class="divider">|</span>
|
|
676
1858
|
<span class="flex items-center gap-1">
|
|
677
|
-
<i class="fas fa-arrow-down text-
|
|
1859
|
+
<i class="fas fa-arrow-down text-2xs"></i>
|
|
678
1860
|
<span class="stats-output-tokens"></span>
|
|
679
1861
|
</span>
|
|
680
|
-
<span class="
|
|
1862
|
+
<span class="divider">|</span>
|
|
681
1863
|
<span class="flex items-center gap-1">
|
|
682
|
-
<i class="fas fa-bolt text-
|
|
1864
|
+
<i class="fas fa-bolt text-2xs"></i>
|
|
683
1865
|
<span class="stats-tps"></span>
|
|
684
1866
|
</span>
|
|
685
1867
|
`;
|
|
@@ -698,24 +1880,28 @@ export class AgentsView extends Component {
|
|
|
698
1880
|
const loadingDots = contentDiv.querySelector('.loading-dots');
|
|
699
1881
|
const container = this.querySelector('#chatMessages');
|
|
700
1882
|
|
|
701
|
-
if (event.type === '
|
|
1883
|
+
if (event.type === 'thinking') {
|
|
1884
|
+
this.handleThinkingEvent(event, toolsDiv, thinkingState, container);
|
|
1885
|
+
} else if (event.type === 'content') {
|
|
1886
|
+
// Finalize any in-progress thinking pill
|
|
1887
|
+
this.finalizeThinkingPill(toolsDiv, thinkingState);
|
|
702
1888
|
if (loadingDots) {
|
|
703
1889
|
loadingDots.remove();
|
|
704
|
-
bubble.querySelector('.response-bubble-inner').classList.remove('
|
|
705
|
-
bubble.querySelector('.response-bubble-inner').classList.add('py-3');
|
|
1890
|
+
bubble.querySelector('.response-bubble-inner').classList.remove('loading');
|
|
706
1891
|
contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
|
|
707
1892
|
contentDiv.innerHTML = '';
|
|
708
1893
|
}
|
|
709
1894
|
this.renderLlmContentStreaming(contentDiv, currentContent, responseId, thinkingState);
|
|
710
1895
|
container.scrollTop = container.scrollHeight;
|
|
711
1896
|
} else if (event.type === 'tool_start') {
|
|
1897
|
+
this.finalizeThinkingPill(toolsDiv, thinkingState);
|
|
712
1898
|
const toolId = `tool-${event.runId}`;
|
|
713
1899
|
const toolEl = document.createElement('div');
|
|
714
1900
|
toolEl.id = toolId;
|
|
715
|
-
toolEl.className = 'tool-pill
|
|
1901
|
+
toolEl.className = 'tool-pill';
|
|
716
1902
|
toolEl.dataset.toolInput = typeof event.input === 'string' ? event.input : JSON.stringify(event.input, null, 2);
|
|
717
1903
|
toolEl.innerHTML = `
|
|
718
|
-
<i class="fas fa-circle-notch animate-spin text-blue
|
|
1904
|
+
<i class="fas fa-circle-notch animate-spin text-blue text-2xs"></i>
|
|
719
1905
|
<span>${this.escapeHtml(event.tool)}</span>
|
|
720
1906
|
`;
|
|
721
1907
|
toolsDiv.appendChild(toolEl);
|
|
@@ -728,106 +1914,101 @@ export class AgentsView extends Component {
|
|
|
728
1914
|
const toolInput = toolEl.dataset.toolInput || '';
|
|
729
1915
|
const toolOutput = typeof event.output === 'string' ? event.output : JSON.stringify(event.output, null, 2);
|
|
730
1916
|
|
|
731
|
-
toolEl.className = 'tool-pill
|
|
1917
|
+
toolEl.className = 'tool-pill done';
|
|
732
1918
|
toolEl.innerHTML = '';
|
|
733
1919
|
|
|
734
|
-
// Pill content (icon + name)
|
|
735
1920
|
const pillContent = document.createElement('span');
|
|
736
|
-
pillContent.className = 'inline-flex items-center gap-1
|
|
1921
|
+
pillContent.className = 'inline-flex items-center gap-1';
|
|
737
1922
|
pillContent.innerHTML = `
|
|
738
|
-
<i class="fas fa-check text-green
|
|
1923
|
+
<i class="fas fa-check text-green text-2xs"></i>
|
|
739
1924
|
<span>${this.escapeHtml(event.tool)}</span>
|
|
740
1925
|
`;
|
|
741
1926
|
toolEl.appendChild(pillContent);
|
|
742
1927
|
|
|
743
|
-
// Popover details panel (positioned below the pill)
|
|
744
1928
|
const details = document.createElement('div');
|
|
745
|
-
details.className = 'tool-invocation-details
|
|
1929
|
+
details.className = 'tool-invocation-details';
|
|
746
1930
|
|
|
747
|
-
// Input section
|
|
748
1931
|
if (toolInput) {
|
|
749
1932
|
const inputSection = document.createElement('div');
|
|
750
|
-
inputSection.className = '
|
|
751
|
-
inputSection.innerHTML =
|
|
1933
|
+
inputSection.className = 'tool-detail-section';
|
|
1934
|
+
inputSection.innerHTML = '<h4>Input</h4>';
|
|
752
1935
|
const inputPre = document.createElement('pre');
|
|
753
|
-
inputPre.className = '
|
|
1936
|
+
inputPre.className = 'tool-detail-pre custom-scrollbar';
|
|
754
1937
|
inputPre.textContent = toolInput;
|
|
755
1938
|
inputSection.appendChild(inputPre);
|
|
756
1939
|
details.appendChild(inputSection);
|
|
757
1940
|
}
|
|
758
1941
|
|
|
759
|
-
// Output section
|
|
760
1942
|
const outputSection = document.createElement('div');
|
|
761
|
-
outputSection.className = '
|
|
762
|
-
outputSection.innerHTML =
|
|
1943
|
+
outputSection.className = 'tool-detail-section';
|
|
1944
|
+
outputSection.innerHTML = '<h4>Output</h4>';
|
|
763
1945
|
const outputPre = document.createElement('pre');
|
|
764
|
-
outputPre.className = '
|
|
1946
|
+
outputPre.className = 'tool-detail-pre custom-scrollbar';
|
|
765
1947
|
outputPre.textContent = toolOutput;
|
|
766
1948
|
outputSection.appendChild(outputPre);
|
|
767
1949
|
details.appendChild(outputSection);
|
|
768
1950
|
|
|
769
1951
|
toolEl.appendChild(details);
|
|
770
1952
|
|
|
771
|
-
|
|
772
|
-
toolEl.addEventListener('click', (e) => {
|
|
773
|
-
e.preventDefault();
|
|
774
|
-
e.stopPropagation();
|
|
775
|
-
// Close any other open popovers
|
|
776
|
-
toolsDiv.querySelectorAll('.tool-invocation-details:not(.hidden)').forEach(d => {
|
|
777
|
-
if (d !== details) d.classList.add('hidden');
|
|
778
|
-
});
|
|
779
|
-
details.classList.toggle('hidden');
|
|
780
|
-
});
|
|
781
|
-
|
|
782
|
-
// Close popover when clicking outside
|
|
783
|
-
const closeHandler = (e) => {
|
|
784
|
-
if (!toolEl.contains(e.target)) {
|
|
785
|
-
details.classList.add('hidden');
|
|
786
|
-
}
|
|
787
|
-
};
|
|
788
|
-
document.addEventListener('click', closeHandler, { capture: true });
|
|
1953
|
+
this._attachClickDetails(toolEl, details, toolsDiv, container);
|
|
789
1954
|
container.scrollTop = container.scrollHeight;
|
|
1955
|
+
|
|
1956
|
+
if (event.tool === 'workspace_write' || event.tool === 'workspace_delete') {
|
|
1957
|
+
try {
|
|
1958
|
+
const result = JSON.parse(typeof event.output === 'string' ? event.output : JSON.stringify(event.output));
|
|
1959
|
+
if (result.success && (result.reloaded === 'agent' || result.unloaded === 'agent')) this.loadAgents();
|
|
1960
|
+
} catch { /* ignore parse errors */ }
|
|
1961
|
+
}
|
|
790
1962
|
}
|
|
791
1963
|
} else if (event.type === 'result') {
|
|
792
1964
|
if (loadingDots) {
|
|
793
1965
|
loadingDots.remove();
|
|
794
|
-
bubble.querySelector('.response-bubble-inner').classList.remove('
|
|
795
|
-
bubble.querySelector('.response-bubble-inner').classList.add('py-3');
|
|
1966
|
+
bubble.querySelector('.response-bubble-inner').classList.remove('loading');
|
|
796
1967
|
contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
|
|
797
1968
|
contentDiv.innerHTML = '';
|
|
798
1969
|
}
|
|
799
1970
|
|
|
800
|
-
// Display structured output as formatted JSON
|
|
801
1971
|
const resultContainer = document.createElement('div');
|
|
802
|
-
resultContainer.className = '
|
|
1972
|
+
resultContainer.className = 'panel';
|
|
803
1973
|
|
|
804
1974
|
const resultPre = document.createElement('pre');
|
|
805
|
-
resultPre.className = 'text-sm text-
|
|
1975
|
+
resultPre.className = 'text-sm text-primary font-mono whitespace-pre-wrap overflow-x-auto';
|
|
806
1976
|
resultPre.textContent = JSON.stringify(event.output, null, 2);
|
|
807
1977
|
|
|
808
1978
|
resultContainer.appendChild(resultPre);
|
|
809
1979
|
contentDiv.appendChild(resultContainer);
|
|
810
1980
|
|
|
811
|
-
// Scroll to bottom
|
|
812
1981
|
container.scrollTop = container.scrollHeight;
|
|
813
1982
|
} else if (event.type === 'error') {
|
|
814
1983
|
if (loadingDots) {
|
|
815
1984
|
loadingDots.remove();
|
|
816
|
-
bubble.querySelector('.response-bubble-inner').classList.remove('
|
|
817
|
-
bubble.querySelector('.response-bubble-inner').classList.add('py-3');
|
|
1985
|
+
bubble.querySelector('.response-bubble-inner').classList.remove('loading');
|
|
818
1986
|
contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
|
|
819
1987
|
}
|
|
820
1988
|
const errorDiv = document.createElement('div');
|
|
821
|
-
errorDiv.className = 'text-red
|
|
1989
|
+
errorDiv.className = 'text-red text-sm';
|
|
822
1990
|
errorDiv.textContent = `Error: ${event.error}`;
|
|
823
1991
|
contentDiv.appendChild(errorDiv);
|
|
824
1992
|
container.scrollTop = container.scrollHeight;
|
|
1993
|
+
} else if (event.type === 'warning') {
|
|
1994
|
+
const warningDiv = document.createElement('div');
|
|
1995
|
+
warningDiv.className = 'text-yellow text-sm';
|
|
1996
|
+
warningDiv.textContent = event.message;
|
|
1997
|
+
contentDiv.appendChild(warningDiv);
|
|
1998
|
+
container.scrollTop = container.scrollHeight;
|
|
825
1999
|
} else if (event.type === 'usage') {
|
|
826
2000
|
this.streamUsageData = {
|
|
827
2001
|
input_tokens: event.input_tokens || 0,
|
|
828
2002
|
output_tokens: event.output_tokens || 0,
|
|
829
2003
|
total_tokens: event.total_tokens || 0,
|
|
830
2004
|
};
|
|
2005
|
+
} else if (event.type === 'react_iteration') {
|
|
2006
|
+
const wrapper = bubble.closest('.response-wrapper');
|
|
2007
|
+
const statusText = wrapper?.querySelector('.stream-status-text');
|
|
2008
|
+
if (statusText) {
|
|
2009
|
+
const contextKB = (event.contextChars / 1024).toFixed(1);
|
|
2010
|
+
statusText.textContent = `Iteration ${event.iteration} · ${contextKB} KB context`;
|
|
2011
|
+
}
|
|
831
2012
|
}
|
|
832
2013
|
}
|
|
833
2014
|
|
|
@@ -835,15 +2016,20 @@ export class AgentsView extends Component {
|
|
|
835
2016
|
const bubble = this.querySelector(`#${id}`);
|
|
836
2017
|
if (bubble) {
|
|
837
2018
|
const content = bubble.querySelector('.response-content');
|
|
838
|
-
content.innerHTML = `<span class="text-red
|
|
2019
|
+
content.innerHTML = `<span class="text-red">${errorMsg}</span>`;
|
|
839
2020
|
}
|
|
840
2021
|
}
|
|
841
2022
|
|
|
842
2023
|
updateUiState() {
|
|
843
2024
|
const btn = this.querySelector('#sendMessageBtn');
|
|
844
2025
|
const input = this.querySelector('#chatInput');
|
|
845
|
-
|
|
846
|
-
if (
|
|
2026
|
+
const hasActiveSession = !!sessionStore.getActiveId();
|
|
2027
|
+
if (btn) btn.disabled = this.isLoading || !hasActiveSession;
|
|
2028
|
+
if (input) {
|
|
2029
|
+
input.disabled = this.isLoading;
|
|
2030
|
+
input.readOnly = !hasActiveSession;
|
|
2031
|
+
input.classList.toggle('cursor-pointer', !hasActiveSession);
|
|
2032
|
+
}
|
|
847
2033
|
}
|
|
848
2034
|
|
|
849
2035
|
appendMessage(role, content, metadata = {}) {
|
|
@@ -854,14 +2040,29 @@ export class AgentsView extends Component {
|
|
|
854
2040
|
const div = document.createElement('div');
|
|
855
2041
|
div.className = isUser ? 'flex justify-end' : 'flex justify-start';
|
|
856
2042
|
|
|
857
|
-
const
|
|
858
|
-
|
|
2043
|
+
const bubbleClass = isUser ? 'user-bubble' : (hasError ? 'response-bubble-inner error' : 'response-bubble-inner');
|
|
2044
|
+
|
|
2045
|
+
// Build attachment thumbnails for user messages
|
|
2046
|
+
let attachmentHtml = '';
|
|
2047
|
+
if (isUser && metadata.attachments && metadata.attachments.length > 0) {
|
|
2048
|
+
const thumbs = metadata.attachments.map(att => {
|
|
2049
|
+
if (att.mediaType.startsWith('image/')) {
|
|
2050
|
+
return `<img src="data:${att.mediaType};base64,${att.data}" class="attachment-thumb">`;
|
|
2051
|
+
}
|
|
2052
|
+
return `<div class="attachment-pill">
|
|
2053
|
+
<i class="fas fa-file"></i>
|
|
2054
|
+
<span class="truncate attachment-name">${this.escapeHtml(att.name)}</span>
|
|
2055
|
+
</div>`;
|
|
2056
|
+
}).join('');
|
|
2057
|
+
attachmentHtml = `<div class="flex flex-wrap gap-2 mb-2">${thumbs}</div>`;
|
|
2058
|
+
}
|
|
859
2059
|
|
|
860
2060
|
div.innerHTML = `
|
|
861
|
-
<div class="
|
|
2061
|
+
<div class="${bubbleClass} group">
|
|
2062
|
+
${attachmentHtml}
|
|
862
2063
|
<div class="whitespace-pre-wrap">${this.escapeHtml(content)}</div>
|
|
863
2064
|
${!isUser && !hasError ? `
|
|
864
|
-
<button class="copy-btn
|
|
2065
|
+
<button class="copy-btn" title="Copy">
|
|
865
2066
|
<i class="far fa-copy"></i>
|
|
866
2067
|
</button>
|
|
867
2068
|
` : ''}
|
|
@@ -888,11 +2089,11 @@ export class AgentsView extends Component {
|
|
|
888
2089
|
div.id = id;
|
|
889
2090
|
div.className = 'flex justify-start';
|
|
890
2091
|
div.innerHTML = `
|
|
891
|
-
<div class="
|
|
892
|
-
<div class="
|
|
893
|
-
<div
|
|
894
|
-
<div
|
|
895
|
-
<div
|
|
2092
|
+
<div class="response-bubble-inner loading">
|
|
2093
|
+
<div class="loading-dots">
|
|
2094
|
+
<div></div>
|
|
2095
|
+
<div></div>
|
|
2096
|
+
<div></div>
|
|
896
2097
|
</div>
|
|
897
2098
|
</div>
|
|
898
2099
|
`;
|
|
@@ -907,35 +2108,120 @@ export class AgentsView extends Component {
|
|
|
907
2108
|
}
|
|
908
2109
|
|
|
909
2110
|
escapeHtml(text) {
|
|
2111
|
+
return sharedEscapeHtml(text);
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
_getRandomWelcomeMessage() {
|
|
2115
|
+
const messages = [
|
|
2116
|
+
'Awaiting your command, master.',
|
|
2117
|
+
'The agents are restless. Give them purpose.',
|
|
2118
|
+
'Ready when you are. No pressure... okay, maybe a little.',
|
|
2119
|
+
'Spinning up neurons... just kidding, I was already ready.',
|
|
2120
|
+
'All systems nominal. Your move, human.',
|
|
2121
|
+
'The orchestrator awaits. What shall we build today?',
|
|
2122
|
+
'Standing by. The agents are stretching their digital legs.',
|
|
2123
|
+
'Another day, another chance to orchestrate greatness.',
|
|
2124
|
+
'Agents assembled. Awaiting mission briefing.',
|
|
2125
|
+
'The stage is set. You are the conductor.',
|
|
2126
|
+
];
|
|
2127
|
+
return messages[Math.floor(Math.random() * messages.length)];
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
_appendWelcomeMessage(container) {
|
|
910
2131
|
const div = document.createElement('div');
|
|
911
|
-
div.
|
|
912
|
-
|
|
2132
|
+
div.className = 'welcome-container';
|
|
2133
|
+
div.innerHTML = `
|
|
2134
|
+
<svg class="welcome-orca" viewBox="0 0 220 140" xmlns="http://www.w3.org/2000/svg">
|
|
2135
|
+
<!-- Main body -->
|
|
2136
|
+
<path class="orca-body" d="
|
|
2137
|
+
M 30,68
|
|
2138
|
+
C 28,58 38,42 58,38
|
|
2139
|
+
C 68,35 74,30 78,18
|
|
2140
|
+
C 80,12 84,12 85,18
|
|
2141
|
+
C 87,28 86,35 92,38
|
|
2142
|
+
C 112,34 148,40 172,54
|
|
2143
|
+
C 176,50 182,44 188,40
|
|
2144
|
+
C 192,38 194,42 190,46
|
|
2145
|
+
C 186,50 184,54 182,56
|
|
2146
|
+
C 186,60 188,66 184,68
|
|
2147
|
+
C 180,70 178,66 176,62
|
|
2148
|
+
C 168,72 142,78 112,76
|
|
2149
|
+
C 82,74 52,70 38,66
|
|
2150
|
+
C 34,64 30,68 30,68 Z
|
|
2151
|
+
"/>
|
|
2152
|
+
<!-- Dorsal fin accent line -->
|
|
2153
|
+
<path class="orca-detail" d="M 72,38 C 74,28 78,18 80,14"/>
|
|
2154
|
+
<!-- Belly line -->
|
|
2155
|
+
<path class="orca-detail" d="M 42,64 C 62,70 100,74 140,72 C 158,70 170,66 176,62"/>
|
|
2156
|
+
<!-- Saddle patch -->
|
|
2157
|
+
<path class="orca-patch" d="M 92,40 C 102,38 112,40 108,48 C 104,54 90,50 92,40 Z"/>
|
|
2158
|
+
<!-- Eye patch -->
|
|
2159
|
+
<path class="orca-patch" d="M 44,52 C 50,48 60,50 56,58 C 52,62 42,58 44,52 Z"/>
|
|
2160
|
+
<!-- Eye -->
|
|
2161
|
+
<circle class="orca-eye" cx="42" cy="54" r="2.5"/>
|
|
2162
|
+
<!-- Pectoral fin -->
|
|
2163
|
+
<path class="orca-body" d="M 65,66 C 70,74 64,82 58,76 C 54,72 60,66 65,66 Z"/>
|
|
2164
|
+
<!-- Tail detail -->
|
|
2165
|
+
<path class="orca-detail" d="M 172,54 C 176,52 180,48 184,44"/>
|
|
2166
|
+
<path class="orca-detail" d="M 176,62 C 180,64 184,66 186,64"/>
|
|
2167
|
+
</svg>
|
|
2168
|
+
<div class="welcome-text">${this._getRandomWelcomeMessage()}</div>
|
|
2169
|
+
${this._renderSampleQuestionChips()}
|
|
2170
|
+
`;
|
|
2171
|
+
container.appendChild(div);
|
|
2172
|
+
|
|
2173
|
+
div.querySelectorAll('.sample-question-chip').forEach(chip => {
|
|
2174
|
+
chip.addEventListener('click', () => {
|
|
2175
|
+
const input = this.querySelector('#chatInput');
|
|
2176
|
+
if (input) {
|
|
2177
|
+
input.value = chip.textContent;
|
|
2178
|
+
input.focus();
|
|
2179
|
+
}
|
|
2180
|
+
});
|
|
2181
|
+
});
|
|
913
2182
|
}
|
|
914
2183
|
|
|
915
|
-
|
|
916
|
-
const
|
|
917
|
-
|
|
2184
|
+
_renderSampleQuestionChips() {
|
|
2185
|
+
const agent = store.get('selectedAgent');
|
|
2186
|
+
const workflow = store.get('selectedWorkflow');
|
|
2187
|
+
const questions = agent?.sampleQuestions || workflow?.sampleQuestions;
|
|
2188
|
+
if (!questions || questions.length === 0) return '';
|
|
918
2189
|
|
|
919
|
-
|
|
920
|
-
|
|
2190
|
+
const chips = questions.map(q =>
|
|
2191
|
+
`<button class="sample-question-chip">${this.escapeHtml(q)}</button>`
|
|
2192
|
+
).join('');
|
|
2193
|
+
|
|
2194
|
+
return `
|
|
2195
|
+
<div class="sample-questions-wrap">${chips}</div>
|
|
2196
|
+
`;
|
|
2197
|
+
}
|
|
921
2198
|
|
|
922
|
-
|
|
2199
|
+
_appendSessionResetBanner(container) {
|
|
923
2200
|
const div = document.createElement('div');
|
|
924
|
-
div.className = '
|
|
2201
|
+
div.className = 'session-reset-banner';
|
|
925
2202
|
div.innerHTML = `
|
|
926
|
-
<div class="
|
|
927
|
-
|
|
928
|
-
|
|
2203
|
+
<div class="session-reset-line"></div>
|
|
2204
|
+
<span class="session-reset-label">
|
|
2205
|
+
<i class="fas fa-rotate-right"></i>
|
|
2206
|
+
Server restarted — new session
|
|
2207
|
+
</span>
|
|
2208
|
+
<div class="session-reset-line"></div>
|
|
929
2209
|
`;
|
|
930
2210
|
container.appendChild(div);
|
|
931
|
-
|
|
932
|
-
// Generate a new session ID for fresh conversation
|
|
933
|
-
store.set('sessionId', 'session-' + Date.now() + '-' + Math.random().toString(36).substring(2, 9));
|
|
2211
|
+
container.scrollTop = container.scrollHeight;
|
|
934
2212
|
}
|
|
935
2213
|
|
|
936
2214
|
postRender() {
|
|
937
2215
|
const input = this.querySelector('#chatInput');
|
|
938
2216
|
|
|
2217
|
+
// Open new conversation modal when clicking input with no active session
|
|
2218
|
+
input.addEventListener('mousedown', (e) => {
|
|
2219
|
+
if (!sessionStore.getActiveId()) {
|
|
2220
|
+
e.preventDefault();
|
|
2221
|
+
this.showNewSessionModal();
|
|
2222
|
+
}
|
|
2223
|
+
});
|
|
2224
|
+
|
|
939
2225
|
// Auto-resize
|
|
940
2226
|
input.addEventListener('input', () => {
|
|
941
2227
|
input.style.height = 'auto';
|
|
@@ -952,60 +2238,78 @@ export class AgentsView extends Component {
|
|
|
952
2238
|
|
|
953
2239
|
this.querySelector('#sendMessageBtn').addEventListener('click', () => this.sendMessage());
|
|
954
2240
|
|
|
955
|
-
//
|
|
956
|
-
|
|
957
|
-
|
|
2241
|
+
// Attach button + file input
|
|
2242
|
+
this.querySelector('#attachBtn').addEventListener('click', () => this.querySelector('#fileInput').click());
|
|
2243
|
+
this.querySelector('#fileInput').addEventListener('change', (e) => this.handleFileSelect(e));
|
|
958
2244
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
this.toggleDropdown();
|
|
962
|
-
});
|
|
2245
|
+
// New chat button
|
|
2246
|
+
this.querySelector('#newChatBtn').addEventListener('click', () => this.showNewSessionModal());
|
|
963
2247
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
2248
|
+
// New agent button
|
|
2249
|
+
this.querySelector('#newAgentBtn').addEventListener('click', () => this.showNewAgentModal());
|
|
2250
|
+
|
|
2251
|
+
// Mobile sidebar toggle
|
|
2252
|
+
this.querySelector('#sidebarToggleBtn').addEventListener('click', () => this.toggleSidebar(true));
|
|
2253
|
+
this.querySelector('#sidebarBackdrop').addEventListener('click', () => this.toggleSidebar(false));
|
|
969
2254
|
}
|
|
970
2255
|
|
|
971
2256
|
template() {
|
|
972
2257
|
return `
|
|
973
|
-
<div class="
|
|
974
|
-
<!--
|
|
975
|
-
<div id="
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
2258
|
+
<div class="agent-shell">
|
|
2259
|
+
<!-- Mobile sidebar backdrop -->
|
|
2260
|
+
<div id="sidebarBackdrop" class="sidebar-backdrop"></div>
|
|
2261
|
+
|
|
2262
|
+
<!-- Sidebar -->
|
|
2263
|
+
<div id="sidebar" class="agent-sidebar">
|
|
2264
|
+
<div class="p-3">
|
|
2265
|
+
<button id="newChatBtn" class="new-chat-btn">
|
|
2266
|
+
<i class="fas fa-plus text-xs text-accent"></i>
|
|
2267
|
+
<span>New chat</span>
|
|
2268
|
+
</button>
|
|
2269
|
+
</div>
|
|
2270
|
+
<div id="sessionList" class="flex-1 overflow-y-auto custom-scrollbar px-2 pb-2"></div>
|
|
2271
|
+
<div class="px-3 sidebar-bottom-action">
|
|
2272
|
+
<button id="newAgentBtn" class="sidebar-secondary-btn">
|
|
2273
|
+
<i class="fas fa-robot text-xs text-blue"></i>
|
|
2274
|
+
<span>New agent</span>
|
|
2275
|
+
</button>
|
|
980
2276
|
</div>
|
|
981
2277
|
</div>
|
|
982
2278
|
|
|
983
|
-
<!--
|
|
984
|
-
<div class="
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
<div class="
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
2279
|
+
<!-- Chat Area -->
|
|
2280
|
+
<div class="chat-area">
|
|
2281
|
+
<!-- Chat Header -->
|
|
2282
|
+
<div class="chat-header">
|
|
2283
|
+
<button id="sidebarToggleBtn" class="sidebar-toggle-btn">
|
|
2284
|
+
<i class="fas fa-bars"></i>
|
|
2285
|
+
</button>
|
|
2286
|
+
<div id="chatHeader" class="flex-1 min-w-0">
|
|
2287
|
+
<span class="text-muted">No conversation selected</span>
|
|
2288
|
+
</div>
|
|
2289
|
+
</div>
|
|
2290
|
+
|
|
2291
|
+
<!-- Chat Messages -->
|
|
2292
|
+
<div id="chatMessages" class="chat-messages custom-scrollbar"></div>
|
|
997
2293
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
2294
|
+
<!-- Input Area -->
|
|
2295
|
+
<div class="chat-input-area">
|
|
2296
|
+
<div id="attachmentPreview" class="attachment-preview"></div>
|
|
2297
|
+
<div class="chat-input-wrap">
|
|
2298
|
+
<input type="file" id="fileInput" multiple accept="image/*,.pdf,.doc,.docx,.xls,.xlsx,.pptx,.txt,.md,.csv,.json,.yaml,.yml,.xml,.html,.css,.js,.ts,.py,.java,.c,.cpp,.go,.rs,.rb,.php,.sql,.sh,.log,.ini,.toml,.env" class="hidden">
|
|
2299
|
+
<textarea id="chatInput" rows="1" readonly
|
|
2300
|
+
placeholder="Ask anything"></textarea>
|
|
2301
|
+
|
|
2302
|
+
<div class="chat-input-actions left">
|
|
2303
|
+
<button id="attachBtn" type="button" class="attach-btn" title="Attach files">
|
|
2304
|
+
<i class="fas fa-plus text-sm"></i>
|
|
2305
|
+
</button>
|
|
1003
2306
|
</div>
|
|
1004
2307
|
|
|
1005
|
-
<
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
2308
|
+
<div class="chat-input-actions right">
|
|
2309
|
+
<button id="sendMessageBtn" disabled class="send-btn">
|
|
2310
|
+
<i class="fas fa-paper-plane text-sm"></i>
|
|
2311
|
+
</button>
|
|
2312
|
+
</div>
|
|
1009
2313
|
</div>
|
|
1010
2314
|
</div>
|
|
1011
2315
|
</div>
|