agent-orcha 0.0.7 → 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 +86 -28
- package/dist/lib/agents/agent-executor.d.ts.map +1 -1
- package/dist/lib/agents/agent-executor.js +23 -7
- package/dist/lib/agents/agent-executor.js.map +1 -1
- package/dist/lib/agents/react-loop.d.ts.map +1 -1
- package/dist/lib/agents/react-loop.js +27 -0
- package/dist/lib/agents/react-loop.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/knowledge/knowledge-store.d.ts +1 -1
- package/dist/lib/knowledge/knowledge-store.d.ts.map +1 -1
- package/dist/lib/knowledge/knowledge-store.js +25 -4
- package/dist/lib/knowledge/knowledge-store.js.map +1 -1
- package/dist/lib/knowledge/loaders/file-loaders.d.ts +0 -1
- package/dist/lib/knowledge/loaders/file-loaders.d.ts.map +1 -1
- package/dist/lib/knowledge/loaders/file-loaders.js +7 -15
- package/dist/lib/knowledge/loaders/file-loaders.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 +13 -13
- 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-config.d.ts +51 -8
- package/dist/lib/llm/llm-config.d.ts.map +1 -1
- package/dist/lib/llm/llm-config.js +161 -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 +41 -8
- package/dist/lib/llm/llm-factory.js.map +1 -1
- package/dist/lib/llm/providers/openai-chat-model.d.ts +10 -0
- package/dist/lib/llm/providers/openai-chat-model.d.ts.map +1 -1
- package/dist/lib/llm/providers/openai-chat-model.js +37 -5
- 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 -5
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/orchestrator.d.ts +9 -0
- package/dist/lib/orchestrator.d.ts.map +1 -1
- package/dist/lib/orchestrator.js +151 -3
- package/dist/lib/orchestrator.js.map +1 -1
- package/dist/lib/sandbox/cdp-client.d.ts +2 -1
- package/dist/lib/sandbox/cdp-client.d.ts.map +1 -1
- package/dist/lib/sandbox/cdp-client.js +33 -7
- package/dist/lib/sandbox/cdp-client.js.map +1 -1
- package/dist/lib/sandbox/index.d.ts +1 -0
- package/dist/lib/sandbox/index.d.ts.map +1 -1
- package/dist/lib/sandbox/index.js +1 -0
- package/dist/lib/sandbox/index.js.map +1 -1
- package/dist/lib/sandbox/page-readiness.d.ts.map +1 -1
- package/dist/lib/sandbox/page-readiness.js +33 -0
- package/dist/lib/sandbox/page-readiness.js.map +1 -1
- package/dist/lib/sandbox/sandbox-browser.d.ts.map +1 -1
- package/dist/lib/sandbox/sandbox-browser.js +14 -1
- package/dist/lib/sandbox/sandbox-browser.js.map +1 -1
- 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.map +1 -1
- package/dist/lib/sandbox/sandbox-file.js +5 -4
- package/dist/lib/sandbox/sandbox-file.js.map +1 -1
- package/dist/lib/sandbox/sandbox-shell.d.ts +2 -1
- package/dist/lib/sandbox/sandbox-shell.d.ts.map +1 -1
- package/dist/lib/sandbox/sandbox-shell.js +42 -24
- package/dist/lib/sandbox/sandbox-shell.js.map +1 -1
- package/dist/lib/sandbox/sandbox-web.d.ts.map +1 -1
- package/dist/lib/sandbox/sandbox-web.js +27 -2
- package/dist/lib/sandbox/sandbox-web.js.map +1 -1
- package/dist/lib/sandbox/vision-browser.d.ts.map +1 -1
- package/dist/lib/sandbox/vision-browser.js +9 -0
- package/dist/lib/sandbox/vision-browser.js.map +1 -1
- 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/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 +7 -13
- 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 +70 -37
- 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/workspace/workspace-tools.d.ts.map +1 -1
- package/dist/lib/tools/workspace/workspace-tools.js +5 -4
- package/dist/lib/tools/workspace/workspace-tools.js.map +1 -1
- 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/workflows/react-workflow-executor.d.ts.map +1 -1
- package/dist/lib/workflows/react-workflow-executor.js +20 -14
- package/dist/lib/workflows/react-workflow-executor.js.map +1 -1
- package/dist/lib/workflows/types.d.ts +71 -45
- 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/public/assets/logo.png +0 -0
- package/dist/public/chat.html +3 -78
- package/dist/public/index.html +3 -330
- package/dist/public/src/components/AgentComposer.js +132 -132
- package/dist/public/src/components/AgentsView.js +1231 -350
- package/dist/public/src/components/AppRoot.js +101 -39
- package/dist/public/src/components/GraphView.js +11 -13
- package/dist/public/src/components/IdeView.js +133 -98
- 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 +79 -126
- package/dist/public/src/components/NavBar.js +16 -26
- package/dist/public/src/components/StandaloneChat.js +136 -150
- package/dist/public/src/services/ApiService.js +196 -2
- package/dist/public/src/services/SessionStore.js +6 -3
- package/dist/public/src/services/StreamManager.js +183 -0
- package/dist/public/src/store.js +1 -1
- package/dist/public/src/utils/card.js +21 -0
- package/dist/public/src/utils/markdown.js +1 -7
- 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 +13 -2
- 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/routes/agents.route.d.ts.map +1 -1
- package/dist/src/routes/agents.route.js +2 -0
- package/dist/src/routes/agents.route.js.map +1 -1
- package/dist/src/routes/chat.route.d.ts.map +1 -1
- package/dist/src/routes/chat.route.js +3 -2
- package/dist/src/routes/chat.route.js.map +1 -1
- package/dist/src/routes/llm.route.d.ts.map +1 -1
- package/dist/src/routes/llm.route.js +227 -7
- 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/vnc.route.d.ts +10 -1
- package/dist/src/routes/vnc.route.d.ts.map +1 -1
- package/dist/src/routes/vnc.route.js +37 -12
- package/dist/src/routes/vnc.route.js.map +1 -1
- 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 +24 -2
- package/dist/src/server.js.map +1 -1
- package/dist/templates/agents/actor.agent.yaml +34 -0
- package/dist/templates/agents/architect.agent.yaml +0 -1
- package/dist/templates/agents/chatbot.agent.yaml +0 -1
- package/dist/templates/agents/corporate.agent.yaml +0 -1
- package/dist/templates/agents/functions.agent.yaml +29 -0
- package/dist/templates/agents/investment-analyst.agent.yaml +0 -1
- package/dist/templates/agents/music-librarian.agent.yaml +3 -27
- package/dist/templates/agents/network-security.agent.yaml +0 -1
- package/dist/templates/agents/transport-security.agent.yaml +0 -1
- package/dist/templates/agents/web-engineer.agent.yaml +3 -4
- package/dist/templates/agents/web-pilot.agent.yaml +0 -1
- 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/web-docs.knowledge.yaml +1 -1
- package/dist/templates/llm.json +73 -10
- package/dist/templates/skills/orcha-builder/SKILL.md +56 -3
- 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 +14 -6
- package/dist/public/src/components/SkillsView.js +0 -137
- package/dist/public/src/components/WorkflowsView.js +0 -568
|
@@ -2,9 +2,14 @@
|
|
|
2
2
|
import { Component } from '../utils/Component.js';
|
|
3
3
|
import { api } from '../services/ApiService.js';
|
|
4
4
|
import { sessionStore } from '../services/SessionStore.js';
|
|
5
|
+
import { streamManager } from '../services/StreamManager.js';
|
|
6
|
+
import { escapeHtml as sharedEscapeHtml } from '../utils/card.js';
|
|
5
7
|
import { store } from '../store.js';
|
|
6
8
|
import { markdownRenderer } from '../utils/markdown.js';
|
|
7
9
|
|
|
10
|
+
// Survives component remount (tab navigation) but not page refresh
|
|
11
|
+
const workflowTasks = new Map();
|
|
12
|
+
|
|
8
13
|
export class AgentsView extends Component {
|
|
9
14
|
constructor() {
|
|
10
15
|
super();
|
|
@@ -14,20 +19,27 @@ export class AgentsView extends Component {
|
|
|
14
19
|
this.streamTimerInterval = null;
|
|
15
20
|
this.streamUsageData = null;
|
|
16
21
|
this.pendingAttachments = [];
|
|
22
|
+
this._streamUnsubscribe = null;
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
async connectedCallback() {
|
|
20
26
|
super.connectedCallback();
|
|
21
|
-
await Promise.all([this.loadAgents(), this.loadLLMs()]);
|
|
27
|
+
await Promise.all([this.loadAgents(), this.loadLLMs(), this.loadWorkflows()]);
|
|
22
28
|
this.restoreActiveSession();
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
disconnectedCallback() {
|
|
26
|
-
this.
|
|
27
|
-
|
|
28
|
-
this.
|
|
29
|
-
|
|
32
|
+
if (this._streamUnsubscribe) {
|
|
33
|
+
this._streamUnsubscribe();
|
|
34
|
+
this._streamUnsubscribe = null;
|
|
35
|
+
}
|
|
36
|
+
if (this.streamTimerInterval) {
|
|
37
|
+
clearInterval(this.streamTimerInterval);
|
|
38
|
+
this.streamTimerInterval = null;
|
|
30
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)
|
|
31
43
|
}
|
|
32
44
|
|
|
33
45
|
formatElapsedTime(ms) {
|
|
@@ -44,13 +56,33 @@ export class AgentsView extends Component {
|
|
|
44
56
|
}
|
|
45
57
|
|
|
46
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
|
+
|
|
47
71
|
if (this.currentAbortController) {
|
|
48
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;
|
|
49
80
|
}
|
|
81
|
+
streamManager.cancel(activeId);
|
|
50
82
|
}
|
|
51
83
|
|
|
52
|
-
startStreamTimer(responseId) {
|
|
53
|
-
this.streamStartTime = Date.now();
|
|
84
|
+
startStreamTimer(responseId, startTime) {
|
|
85
|
+
this.streamStartTime = startTime || Date.now();
|
|
54
86
|
this.streamTimerInterval = setInterval(() => {
|
|
55
87
|
const elapsed = Date.now() - this.streamStartTime;
|
|
56
88
|
const bubble = this.querySelector(`#${responseId}`);
|
|
@@ -78,7 +110,7 @@ export class AgentsView extends Component {
|
|
|
78
110
|
const statusBar = wrapper.querySelector('.stream-status-bar');
|
|
79
111
|
const statsBar = wrapper.querySelector('.stream-stats-bar');
|
|
80
112
|
|
|
81
|
-
if (statusBar) statusBar.
|
|
113
|
+
if (statusBar) statusBar.remove();
|
|
82
114
|
|
|
83
115
|
if (statsBar) {
|
|
84
116
|
const elapsedEl = statsBar.querySelector('.stats-elapsed');
|
|
@@ -107,12 +139,12 @@ export class AgentsView extends Component {
|
|
|
107
139
|
|
|
108
140
|
if (wasCancelled) {
|
|
109
141
|
const cancelBadge = document.createElement('span');
|
|
110
|
-
cancelBadge.className = '
|
|
142
|
+
cancelBadge.className = 'badge badge-amber';
|
|
111
143
|
cancelBadge.textContent = 'Cancelled';
|
|
112
144
|
statsBar.appendChild(cancelBadge);
|
|
113
145
|
}
|
|
114
146
|
|
|
115
|
-
statsBar.classList.
|
|
147
|
+
statsBar.classList.add('visible');
|
|
116
148
|
}
|
|
117
149
|
}
|
|
118
150
|
|
|
@@ -128,13 +160,27 @@ export class AgentsView extends Component {
|
|
|
128
160
|
|
|
129
161
|
async loadLLMs() {
|
|
130
162
|
try {
|
|
131
|
-
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;
|
|
132
167
|
store.set('llms', llms);
|
|
168
|
+
store.set('defaultLlmName', defaultLlmName);
|
|
133
169
|
} catch (e) {
|
|
134
170
|
console.error('Failed to load LLMs', e);
|
|
135
171
|
}
|
|
136
172
|
}
|
|
137
173
|
|
|
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
|
+
|
|
138
184
|
// --- Sidebar toggle (mobile) ---
|
|
139
185
|
|
|
140
186
|
_isMobile() {
|
|
@@ -147,13 +193,11 @@ export class AgentsView extends Component {
|
|
|
147
193
|
if (!sidebar || !backdrop) return;
|
|
148
194
|
|
|
149
195
|
if (show) {
|
|
150
|
-
sidebar.classList.
|
|
151
|
-
|
|
152
|
-
backdrop.classList.remove('hidden');
|
|
196
|
+
sidebar.classList.add('open');
|
|
197
|
+
backdrop.classList.add('visible');
|
|
153
198
|
} else {
|
|
154
|
-
sidebar.classList.
|
|
155
|
-
|
|
156
|
-
backdrop.classList.add('hidden');
|
|
199
|
+
sidebar.classList.remove('open');
|
|
200
|
+
backdrop.classList.remove('visible');
|
|
157
201
|
}
|
|
158
202
|
}
|
|
159
203
|
|
|
@@ -177,30 +221,34 @@ export class AgentsView extends Component {
|
|
|
177
221
|
const activeId = sessionStore.getActiveId();
|
|
178
222
|
|
|
179
223
|
if (sessions.length === 0) {
|
|
180
|
-
list.innerHTML = '<div class="text-
|
|
224
|
+
list.innerHTML = '<div class="text-muted text-sm text-center py-8">No conversations yet</div>';
|
|
181
225
|
return;
|
|
182
226
|
}
|
|
183
227
|
|
|
184
228
|
list.innerHTML = sessions.map(s => {
|
|
185
229
|
const isActive = s.id === activeId;
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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
|
+
}
|
|
193
241
|
|
|
194
242
|
return `
|
|
195
|
-
<div data-session-id="${s.id}" class="session-item
|
|
243
|
+
<div data-session-id="${s.id}" class="session-item${isActive ? ' active' : ''}">
|
|
196
244
|
<div class="flex-1 min-w-0">
|
|
197
|
-
<div class="text-sm
|
|
198
|
-
<div class="flex items-center gap-1
|
|
199
|
-
<i class="fas ${icon} text-
|
|
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>
|
|
200
248
|
<span class="truncate">${this.escapeHtml(displayName)}</span>
|
|
201
249
|
</div>
|
|
202
250
|
</div>
|
|
203
|
-
<button data-delete-id="${s.id}" class="session-delete-btn
|
|
251
|
+
<button data-delete-id="${s.id}" class="session-delete-btn" title="Delete">
|
|
204
252
|
<i class="fas fa-xmark text-xs"></i>
|
|
205
253
|
</button>
|
|
206
254
|
</div>
|
|
@@ -224,11 +272,17 @@ export class AgentsView extends Component {
|
|
|
224
272
|
}
|
|
225
273
|
|
|
226
274
|
switchToSession(sessionId) {
|
|
227
|
-
//
|
|
228
|
-
if (this.
|
|
229
|
-
this.
|
|
230
|
-
this.
|
|
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;
|
|
231
279
|
}
|
|
280
|
+
if (this.streamTimerInterval) {
|
|
281
|
+
clearInterval(this.streamTimerInterval);
|
|
282
|
+
this.streamTimerInterval = null;
|
|
283
|
+
}
|
|
284
|
+
this.streamStartTime = null;
|
|
285
|
+
this.currentAbortController = null;
|
|
232
286
|
this.isLoading = false;
|
|
233
287
|
|
|
234
288
|
const session = sessionStore.get(sessionId);
|
|
@@ -240,21 +294,39 @@ export class AgentsView extends Component {
|
|
|
240
294
|
const agents = store.get('agents') || [];
|
|
241
295
|
const llms = store.get('llms') || [];
|
|
242
296
|
|
|
243
|
-
if (session.agentType === '
|
|
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') {
|
|
244
305
|
const agent = agents.find(a => a.name === session.agentName);
|
|
245
306
|
store.set('selectedAgent', agent || null);
|
|
246
307
|
store.set('selectedLlm', null);
|
|
308
|
+
store.set('selectedWorkflow', null);
|
|
247
309
|
store.set('selectionType', 'agent');
|
|
248
310
|
} else {
|
|
249
311
|
const llm = llms.find(l => l.name === session.llmName);
|
|
250
312
|
store.set('selectedLlm', llm || null);
|
|
251
313
|
store.set('selectedAgent', null);
|
|
314
|
+
store.set('selectedWorkflow', null);
|
|
252
315
|
store.set('selectionType', 'llm');
|
|
253
316
|
}
|
|
254
317
|
|
|
255
318
|
this.restoreMessages(session);
|
|
256
319
|
this.updateChatHeader(session);
|
|
257
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
|
+
}
|
|
329
|
+
|
|
258
330
|
this.updateUiState();
|
|
259
331
|
|
|
260
332
|
// Close sidebar on mobile after selecting
|
|
@@ -285,7 +357,7 @@ export class AgentsView extends Component {
|
|
|
285
357
|
if (msg.role === 'user') {
|
|
286
358
|
this.appendMessage('user', msg.content);
|
|
287
359
|
} else {
|
|
288
|
-
this.appendRestoredAssistantMessage(msg.content);
|
|
360
|
+
this.appendRestoredAssistantMessage(msg.content, msg.meta);
|
|
289
361
|
}
|
|
290
362
|
}
|
|
291
363
|
|
|
@@ -300,7 +372,7 @@ export class AgentsView extends Component {
|
|
|
300
372
|
}
|
|
301
373
|
}
|
|
302
374
|
|
|
303
|
-
appendRestoredAssistantMessage(content) {
|
|
375
|
+
appendRestoredAssistantMessage(content, meta) {
|
|
304
376
|
const container = this.querySelector('#chatMessages');
|
|
305
377
|
const div = document.createElement('div');
|
|
306
378
|
div.className = 'response-wrapper';
|
|
@@ -308,17 +380,83 @@ export class AgentsView extends Component {
|
|
|
308
380
|
const bubble = document.createElement('div');
|
|
309
381
|
bubble.className = 'flex justify-start';
|
|
310
382
|
bubble.innerHTML = `
|
|
311
|
-
<div class="response-bubble-inner
|
|
383
|
+
<div class="response-bubble-inner group">
|
|
312
384
|
<div class="response-content markdown-content"></div>
|
|
385
|
+
<div class="tool-invocations"></div>
|
|
313
386
|
</div>
|
|
314
387
|
`;
|
|
315
388
|
|
|
316
389
|
const contentDiv = bubble.querySelector('.response-content');
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
+
}
|
|
320
421
|
|
|
321
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
|
+
|
|
322
460
|
container.appendChild(div);
|
|
323
461
|
container.scrollTop = container.scrollHeight;
|
|
324
462
|
}
|
|
@@ -329,49 +467,69 @@ export class AgentsView extends Component {
|
|
|
329
467
|
if (existing) existing.remove();
|
|
330
468
|
|
|
331
469
|
const agents = store.get('agents') || [];
|
|
470
|
+
const workflows = store.get('workflows') || [];
|
|
332
471
|
const llms = store.get('llms') || [];
|
|
333
472
|
|
|
334
473
|
const overlay = document.createElement('div');
|
|
335
474
|
overlay.id = 'newSessionModal';
|
|
336
|
-
overlay.className = 'modal-backdrop
|
|
475
|
+
overlay.className = 'modal-backdrop';
|
|
337
476
|
|
|
338
477
|
let itemsHtml = '';
|
|
339
478
|
|
|
340
479
|
if (agents.length > 0) {
|
|
341
|
-
itemsHtml += '<div class="
|
|
480
|
+
itemsHtml += '<div class="modal-section-label">Agents</div>';
|
|
342
481
|
itemsHtml += agents.map(a => `
|
|
343
|
-
<button data-type="agent" data-name="${this.escapeHtml(a.name)}" class="modal-pick-item
|
|
344
|
-
<i class="fas fa-robot text-blue
|
|
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>
|
|
345
484
|
<div class="flex-1 min-w-0">
|
|
346
|
-
<div class="text-sm font-medium text-
|
|
347
|
-
<div class="text-xs text-
|
|
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>
|
|
348
487
|
</div>
|
|
349
488
|
</button>
|
|
350
489
|
`).join('');
|
|
351
490
|
}
|
|
352
491
|
|
|
353
|
-
if (
|
|
354
|
-
itemsHtml += '<div class="
|
|
355
|
-
itemsHtml +=
|
|
356
|
-
<button data-type="
|
|
357
|
-
<i class="fas fa-
|
|
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>
|
|
358
497
|
<div class="flex-1 min-w-0">
|
|
359
|
-
<div class="text-sm font-medium text-
|
|
360
|
-
<div class="text-xs text-
|
|
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>
|
|
361
500
|
</div>
|
|
362
501
|
</button>
|
|
363
502
|
`).join('');
|
|
364
503
|
}
|
|
365
504
|
|
|
505
|
+
if (llms.length > 0) {
|
|
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;
|
|
510
|
+
return `
|
|
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>' : ''}
|
|
517
|
+
</div>
|
|
518
|
+
<div class="text-xs text-muted truncate">${this.escapeHtml(l.model || '')}</div>
|
|
519
|
+
</div>
|
|
520
|
+
</button>
|
|
521
|
+
`}).join('');
|
|
522
|
+
}
|
|
523
|
+
|
|
366
524
|
if (!itemsHtml) {
|
|
367
|
-
itemsHtml = '<div class="text-
|
|
525
|
+
itemsHtml = '<div class="text-muted text-sm text-center py-8">No agents, workflows or LLMs available</div>';
|
|
368
526
|
}
|
|
369
527
|
|
|
370
528
|
overlay.innerHTML = `
|
|
371
|
-
<div class="modal-content
|
|
372
|
-
<div class="
|
|
373
|
-
<h3 class="text-lg font-semibold text-
|
|
374
|
-
<button id="closeNewSessionModal" class="
|
|
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">
|
|
375
533
|
<i class="fas fa-xmark"></i>
|
|
376
534
|
</button>
|
|
377
535
|
</div>
|
|
@@ -399,7 +557,8 @@ export class AgentsView extends Component {
|
|
|
399
557
|
const session = sessionStore.create({
|
|
400
558
|
agentName: type === 'agent' ? name : null,
|
|
401
559
|
agentType: type,
|
|
402
|
-
llmName: type === 'llm' ? name : null
|
|
560
|
+
llmName: type === 'llm' ? name : null,
|
|
561
|
+
workflowName: type === 'workflow' ? name : null
|
|
403
562
|
});
|
|
404
563
|
|
|
405
564
|
overlay.remove();
|
|
@@ -408,6 +567,87 @@ export class AgentsView extends Component {
|
|
|
408
567
|
});
|
|
409
568
|
}
|
|
410
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');
|
|
646
|
+
}
|
|
647
|
+
}, 200);
|
|
648
|
+
});
|
|
649
|
+
}
|
|
650
|
+
|
|
411
651
|
deleteSession(sessionId) {
|
|
412
652
|
sessionStore.delete(sessionId);
|
|
413
653
|
const activeId = sessionStore.getActiveId();
|
|
@@ -431,8 +671,8 @@ export class AgentsView extends Component {
|
|
|
431
671
|
if (container) {
|
|
432
672
|
container.innerHTML = `
|
|
433
673
|
<div class="flex-1 flex items-center justify-center h-full">
|
|
434
|
-
<div class="text-center text-
|
|
435
|
-
<i class="fas fa-comments text-4xl mb-4 text-
|
|
674
|
+
<div class="text-center text-muted">
|
|
675
|
+
<i class="fas fa-comments text-4xl mb-4 text-muted"></i>
|
|
436
676
|
<p class="text-lg">Start a new conversation</p>
|
|
437
677
|
<p class="text-sm mt-1">Click "New chat" to begin</p>
|
|
438
678
|
</div>
|
|
@@ -458,15 +698,28 @@ export class AgentsView extends Component {
|
|
|
458
698
|
if (!header) return;
|
|
459
699
|
|
|
460
700
|
if (!session) {
|
|
461
|
-
header.innerHTML = '<span class="text-
|
|
701
|
+
header.innerHTML = '<span class="text-muted">No conversation selected</span>';
|
|
462
702
|
return;
|
|
463
703
|
}
|
|
464
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
|
+
}
|
|
465
722
|
const isAgent = session.agentType === 'agent';
|
|
466
|
-
const name = isAgent ? (session.agentName || 'Agent') : (session.llmName || 'LLM');
|
|
467
|
-
const badgeText = isAgent ? 'Agent' : 'LLM';
|
|
468
|
-
const badgeColor = isAgent ? 'bg-blue-500/20 text-blue-400' : 'bg-purple-500/20 text-purple-400';
|
|
469
|
-
const icon = isAgent ? 'fa-robot' : 'fa-microchip';
|
|
470
723
|
|
|
471
724
|
let extraBadges = '';
|
|
472
725
|
if (isAgent) {
|
|
@@ -475,12 +728,12 @@ export class AgentsView extends Component {
|
|
|
475
728
|
if (agent) {
|
|
476
729
|
if (agent.publish?.enabled) {
|
|
477
730
|
const chatUrl = `/chat/${encodeURIComponent(agent.name)}`;
|
|
478
|
-
extraBadges += `<a href="${chatUrl}" target="_blank" class="
|
|
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>`;
|
|
479
732
|
}
|
|
480
733
|
|
|
481
734
|
const hasMemory = agent.memory === true || (agent.memory && agent.memory.enabled);
|
|
482
735
|
if (hasMemory) {
|
|
483
|
-
extraBadges += `<span class="
|
|
736
|
+
extraBadges += `<span class="badge badge-pill badge-amber" title="Persistent memory enabled"><i class="fas fa-brain text-2xs"></i> Memory</span>`;
|
|
484
737
|
}
|
|
485
738
|
|
|
486
739
|
if (agent.tools?.length) {
|
|
@@ -488,7 +741,7 @@ export class AgentsView extends Component {
|
|
|
488
741
|
const toolListHtml = toolNames.map(t => `<div class="tools-popover-item">${this.escapeHtml(t)}</div>`).join('');
|
|
489
742
|
extraBadges += `
|
|
490
743
|
<span class="tools-badge-wrapper">
|
|
491
|
-
<span class="
|
|
744
|
+
<span class="badge badge-pill badge-gray"><i class="fas fa-wrench text-2xs"></i> ${toolNames.length} tool${toolNames.length !== 1 ? 's' : ''}</span>
|
|
492
745
|
<div class="tools-popover">${toolListHtml}</div>
|
|
493
746
|
</span>`;
|
|
494
747
|
}
|
|
@@ -497,9 +750,9 @@ export class AgentsView extends Component {
|
|
|
497
750
|
|
|
498
751
|
header.innerHTML = `
|
|
499
752
|
<div class="flex items-center gap-2 flex-wrap">
|
|
500
|
-
<i class="fas ${icon} text-sm text-
|
|
501
|
-
<span class="font-medium text-
|
|
502
|
-
<span class="
|
|
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>
|
|
503
756
|
${extraBadges}
|
|
504
757
|
</div>
|
|
505
758
|
`;
|
|
@@ -555,22 +808,22 @@ export class AgentsView extends Component {
|
|
|
555
808
|
if (!preview) return;
|
|
556
809
|
|
|
557
810
|
if (this.pendingAttachments.length === 0) {
|
|
558
|
-
preview.classList.
|
|
811
|
+
preview.classList.remove('visible');
|
|
559
812
|
preview.innerHTML = '';
|
|
560
813
|
return;
|
|
561
814
|
}
|
|
562
815
|
|
|
563
|
-
preview.classList.
|
|
816
|
+
preview.classList.add('visible');
|
|
564
817
|
preview.innerHTML = this.pendingAttachments.map((att, i) => {
|
|
565
818
|
const isImage = att.mediaType.startsWith('image/');
|
|
566
819
|
const thumb = isImage
|
|
567
|
-
? `<img src="data:${att.mediaType};base64,${att.data}"
|
|
568
|
-
: `<i class="fas fa-file text-
|
|
820
|
+
? `<img src="data:${att.mediaType};base64,${att.data}">`
|
|
821
|
+
: `<i class="fas fa-file text-secondary text-lg"></i>`;
|
|
569
822
|
return `
|
|
570
|
-
<div class="attachment-pill
|
|
823
|
+
<div class="attachment-pill">
|
|
571
824
|
${thumb}
|
|
572
|
-
<span class="
|
|
573
|
-
<button class="attachment-remove
|
|
825
|
+
<span class="truncate attachment-name">${this.escapeHtml(att.name)}</span>
|
|
826
|
+
<button class="attachment-remove" data-index="${i}">
|
|
574
827
|
<i class="fas fa-xmark text-xs"></i>
|
|
575
828
|
</button>
|
|
576
829
|
</div>
|
|
@@ -601,15 +854,19 @@ export class AgentsView extends Component {
|
|
|
601
854
|
const llm = store.get('selectedLlm');
|
|
602
855
|
const activeId = sessionStore.getActiveId();
|
|
603
856
|
|
|
604
|
-
const selected = selectionType === 'agent' ? agent : llm;
|
|
605
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
|
+
}
|
|
606
864
|
|
|
607
|
-
|
|
865
|
+
const selected = selectionType === 'agent' ? agent : llm;
|
|
866
|
+
if (!selected) return;
|
|
608
867
|
|
|
609
|
-
// Capture attachments before clearing
|
|
610
868
|
const attachments = hasAttachments ? [...this.pendingAttachments] : null;
|
|
611
869
|
|
|
612
|
-
// Add user message (with optional attachment thumbnails)
|
|
613
870
|
this.appendMessage('user', message || '(attached files)', { attachments });
|
|
614
871
|
sessionStore.addMessage(activeId, 'user', message || '(attached files)');
|
|
615
872
|
input.value = '';
|
|
@@ -622,204 +879,864 @@ export class AgentsView extends Component {
|
|
|
622
879
|
const responseId = 'response-' + Date.now();
|
|
623
880
|
this.createResponseBubble(responseId);
|
|
624
881
|
|
|
625
|
-
|
|
882
|
+
const abortController = new AbortController();
|
|
883
|
+
this.currentAbortController = abortController;
|
|
626
884
|
this.streamUsageData = null;
|
|
627
885
|
this.startStreamTimer(responseId);
|
|
628
886
|
|
|
629
|
-
let finalContent = '';
|
|
630
|
-
let wasCancelled = false;
|
|
631
|
-
|
|
632
887
|
try {
|
|
888
|
+
let response;
|
|
633
889
|
if (selectionType === 'agent') {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
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 });
|
|
637
897
|
}
|
|
898
|
+
|
|
899
|
+
streamManager.start(activeId, {
|
|
900
|
+
response,
|
|
901
|
+
abortController,
|
|
902
|
+
streamType: selectionType === 'agent' ? 'agent' : 'llm',
|
|
903
|
+
inputMessage: message,
|
|
904
|
+
responseId,
|
|
905
|
+
});
|
|
906
|
+
|
|
907
|
+
this._attachToStream(activeId, responseId);
|
|
638
908
|
} catch (e) {
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
} else {
|
|
909
|
+
const wasCancelled = e.name === 'AbortError';
|
|
910
|
+
if (!wasCancelled) {
|
|
642
911
|
this.updateResponseError(responseId, `Error: ${e.message}`);
|
|
643
912
|
}
|
|
644
|
-
|
|
645
|
-
this.stopStreamTimer(responseId, message, finalContent, wasCancelled);
|
|
913
|
+
this.stopStreamTimer(responseId, message, '', wasCancelled);
|
|
646
914
|
this.currentAbortController = null;
|
|
647
915
|
this.isLoading = false;
|
|
648
916
|
this.updateUiState();
|
|
649
|
-
input.focus();
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// Persist assistant response
|
|
653
|
-
if (finalContent) {
|
|
654
|
-
sessionStore.addMessage(activeId, 'assistant', finalContent);
|
|
655
917
|
}
|
|
656
918
|
|
|
657
|
-
// Re-render sidebar (title/ordering may have changed)
|
|
658
919
|
this.renderSessionList();
|
|
659
920
|
}
|
|
660
921
|
|
|
661
|
-
|
|
662
|
-
const
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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
|
+
}
|
|
667
950
|
}
|
|
668
951
|
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
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);
|
|
673
1051
|
|
|
674
|
-
const bubble = this.querySelector(`#${responseId}`);
|
|
675
|
-
const contentDiv = bubble.querySelector('.response-content');
|
|
676
|
-
const container = this.querySelector('#chatMessages');
|
|
677
1052
|
const thinkingState = {
|
|
678
1053
|
inThinking: false,
|
|
679
1054
|
thinkingSections: [],
|
|
680
|
-
currentSection: null
|
|
1055
|
+
currentSection: null,
|
|
1056
|
+
thinkingContent: snapshotState.thinkingContent || '',
|
|
1057
|
+
thinkingPill: snapshotState.thinkingPill || null,
|
|
681
1058
|
};
|
|
682
1059
|
|
|
683
|
-
|
|
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);
|
|
1222
|
+
} else {
|
|
1223
|
+
this.updateResponseError(responseId, `Error: ${e.message}`);
|
|
1224
|
+
this.stopStreamTimer(responseId, message, '', false);
|
|
1225
|
+
this.isLoading = false;
|
|
1226
|
+
this.updateUiState();
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
this.renderSessionList();
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
async _respondToWorkflowInterrupt(sessionId, message) {
|
|
1234
|
+
const wfState = workflowTasks.get(sessionId);
|
|
1235
|
+
if (!wfState?.interruptState) return;
|
|
1236
|
+
|
|
1237
|
+
const { threadId, workflowName } = wfState.interruptState;
|
|
1238
|
+
|
|
1239
|
+
const input = this.querySelector('#chatInput');
|
|
1240
|
+
this.appendMessage('user', message);
|
|
1241
|
+
sessionStore.addMessage(sessionId, 'user', message);
|
|
1242
|
+
input.value = '';
|
|
1243
|
+
input.style.height = 'auto';
|
|
1244
|
+
|
|
1245
|
+
const responseId = 'response-' + Date.now();
|
|
1246
|
+
this.createResponseBubble(responseId);
|
|
1247
|
+
|
|
1248
|
+
const abortController = new AbortController();
|
|
1249
|
+
wfState.responseId = responseId;
|
|
1250
|
+
wfState.interruptState = null;
|
|
1251
|
+
wfState.status = 'streaming';
|
|
1252
|
+
wfState.abortController = abortController;
|
|
1253
|
+
|
|
1254
|
+
this.isLoading = true;
|
|
1255
|
+
this.updateUiState();
|
|
1256
|
+
this.startStreamTimer(responseId);
|
|
1257
|
+
|
|
1258
|
+
try {
|
|
1259
|
+
const response = await api.resumeWorkflowStream(workflowName, threadId, message, abortController.signal);
|
|
1260
|
+
await this._processWorkflowStream(response, sessionId, responseId);
|
|
1261
|
+
} catch (e) {
|
|
1262
|
+
if (e.name === 'AbortError') {
|
|
1263
|
+
this._finishWorkflowStream(sessionId, responseId, null, null, true);
|
|
1264
|
+
} else {
|
|
1265
|
+
this.updateResponseError(responseId, `Error: ${e.message}`);
|
|
1266
|
+
this.stopStreamTimer(responseId, message, '', false);
|
|
1267
|
+
this.isLoading = false;
|
|
1268
|
+
this.updateUiState();
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
this.renderSessionList();
|
|
1273
|
+
}
|
|
1274
|
+
|
|
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
|
+
}
|
|
1282
|
+
|
|
1283
|
+
const reader = response.body.getReader();
|
|
1284
|
+
const decoder = new TextDecoder();
|
|
684
1285
|
let buffer = '';
|
|
685
|
-
let hasToolCalls = false;
|
|
686
1286
|
|
|
687
1287
|
while (true) {
|
|
688
1288
|
const { done, value } = await reader.read();
|
|
689
1289
|
if (done) break;
|
|
690
1290
|
|
|
691
|
-
|
|
692
|
-
buffer += chunk;
|
|
693
|
-
|
|
1291
|
+
buffer += decoder.decode(value, { stream: true });
|
|
694
1292
|
const lines = buffer.split('\n');
|
|
695
1293
|
buffer = lines.pop() || '';
|
|
696
1294
|
|
|
697
1295
|
for (const line of lines) {
|
|
698
|
-
if (line.
|
|
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);
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
699
1308
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
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 */ }
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
703
1319
|
|
|
704
|
-
|
|
705
|
-
|
|
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
|
+
}
|
|
1325
|
+
}
|
|
706
1326
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
return currentContent;
|
|
711
|
-
}
|
|
1327
|
+
_handleWorkflowStreamEvent(update, sessionId, responseId) {
|
|
1328
|
+
const wfState = workflowTasks.get(sessionId);
|
|
1329
|
+
if (!wfState) return;
|
|
712
1330
|
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
1331
|
+
// Capture task ID from server
|
|
1332
|
+
if (update.type === 'task_id') {
|
|
1333
|
+
wfState.taskId = update.taskId;
|
|
1334
|
+
return;
|
|
1335
|
+
}
|
|
1336
|
+
|
|
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) {
|
|
1367
|
+
const contentDiv = bubble.querySelector('.response-content');
|
|
1368
|
+
|
|
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);
|
|
722
1449
|
}
|
|
723
1450
|
}
|
|
724
1451
|
}
|
|
725
1452
|
}
|
|
726
1453
|
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
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')) {
|
|
1476
|
+
loadingDots.remove();
|
|
1477
|
+
bubble.querySelector('.response-bubble-inner')?.classList.remove('loading');
|
|
1478
|
+
contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
|
|
1479
|
+
contentDiv.innerHTML = '';
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
const container = this.querySelector('#chatMessages');
|
|
1483
|
+
if (container) container.scrollTop = container.scrollHeight;
|
|
1484
|
+
}
|
|
1485
|
+
|
|
1486
|
+
_handleWorkflowInterrupt(sessionId, responseId, interruptData) {
|
|
1487
|
+
const wfState = workflowTasks.get(sessionId);
|
|
1488
|
+
if (!wfState) return;
|
|
730
1489
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
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
|
+
};
|
|
1497
|
+
|
|
1498
|
+
const bubble = this.querySelector(`#${responseId}`);
|
|
1499
|
+
if (bubble) {
|
|
1500
|
+
const contentDiv = bubble.querySelector('.response-content');
|
|
1501
|
+
const loadingDots = contentDiv?.querySelector('.loading-dots');
|
|
734
1502
|
if (loadingDots) {
|
|
735
1503
|
loadingDots.remove();
|
|
736
|
-
bubble.querySelector('.response-bubble-inner')
|
|
737
|
-
bubble.querySelector('.response-bubble-inner').classList.add('py-3');
|
|
1504
|
+
bubble.querySelector('.response-bubble-inner')?.classList.remove('loading');
|
|
738
1505
|
contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
|
|
739
1506
|
contentDiv.innerHTML = '';
|
|
740
1507
|
}
|
|
1508
|
+
|
|
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
|
+
}
|
|
1522
|
+
|
|
1523
|
+
sessionStore.addMessage(sessionId, 'assistant', question);
|
|
1524
|
+
|
|
1525
|
+
if (this.streamTimerInterval) {
|
|
1526
|
+
clearInterval(this.streamTimerInterval);
|
|
1527
|
+
this.streamTimerInterval = null;
|
|
741
1528
|
}
|
|
1529
|
+
this.isLoading = false;
|
|
1530
|
+
this.updateUiState();
|
|
742
1531
|
|
|
743
|
-
|
|
1532
|
+
const input = this.querySelector('#chatInput');
|
|
1533
|
+
if (input) input.focus();
|
|
744
1534
|
}
|
|
745
1535
|
|
|
746
|
-
|
|
747
|
-
const
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
1536
|
+
_finishWorkflowStream(sessionId, responseId, result, error, wasCancelled) {
|
|
1537
|
+
const wfState = workflowTasks.get(sessionId);
|
|
1538
|
+
if (!wfState) return;
|
|
1539
|
+
|
|
1540
|
+
wfState.status = 'done';
|
|
1541
|
+
wfState.abortController = null;
|
|
751
1542
|
|
|
752
1543
|
const bubble = this.querySelector(`#${responseId}`);
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
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
|
+
}
|
|
756
1553
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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
|
+
}
|
|
763
1564
|
}
|
|
764
1565
|
|
|
765
|
-
let
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
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```';
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
772
1576
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
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);
|
|
776
1592
|
|
|
777
|
-
|
|
778
|
-
|
|
1593
|
+
this.stopStreamTimer(responseId, wfState.inputMessage || '', content, wasCancelled);
|
|
1594
|
+
this.isLoading = false;
|
|
1595
|
+
this.updateUiState();
|
|
1596
|
+
this.renderSessionList();
|
|
779
1597
|
|
|
780
|
-
|
|
781
|
-
buffer = lines.pop() || '';
|
|
1598
|
+
setTimeout(() => workflowTasks.delete(sessionId), 10000);
|
|
782
1599
|
|
|
783
|
-
|
|
784
|
-
|
|
1600
|
+
const input = this.querySelector('#chatInput');
|
|
1601
|
+
if (input) input.focus();
|
|
1602
|
+
}
|
|
785
1603
|
|
|
786
|
-
|
|
787
|
-
|
|
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
|
+
}
|
|
788
1615
|
|
|
789
|
-
|
|
1616
|
+
_reconnectWorkflowStream(sessionId) {
|
|
1617
|
+
const wfState = workflowTasks.get(sessionId);
|
|
1618
|
+
if (!wfState || wfState.status === 'done') return false;
|
|
790
1619
|
|
|
791
|
-
|
|
792
|
-
|
|
1620
|
+
if (wfState.interruptState) {
|
|
1621
|
+
this.isLoading = false;
|
|
1622
|
+
this.updateUiState();
|
|
1623
|
+
return true;
|
|
1624
|
+
}
|
|
793
1625
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
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);
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
798
1635
|
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
total_tokens: parsed.total_tokens || 0,
|
|
804
|
-
};
|
|
805
|
-
continue;
|
|
806
|
-
}
|
|
1636
|
+
this.startStreamTimer(wfState.responseId, wfState.startTime);
|
|
1637
|
+
this.isLoading = true;
|
|
1638
|
+
this.currentAbortController = null;
|
|
1639
|
+
this.updateUiState();
|
|
807
1640
|
|
|
808
|
-
|
|
1641
|
+
return true;
|
|
1642
|
+
}
|
|
809
1643
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
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';
|
|
1661
|
+
} else {
|
|
1662
|
+
detailsEl.style.left = '0';
|
|
1663
|
+
detailsEl.style.right = 'auto';
|
|
818
1664
|
}
|
|
819
1665
|
}
|
|
1666
|
+
});
|
|
1667
|
+
document.addEventListener('click', (e) => {
|
|
1668
|
+
if (!pillEl.contains(e.target)) detailsEl.classList.remove('visible');
|
|
1669
|
+
}, { capture: true });
|
|
1670
|
+
}
|
|
1671
|
+
|
|
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
|
+
}
|
|
1698
|
+
|
|
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);
|
|
820
1725
|
}
|
|
821
1726
|
|
|
822
|
-
|
|
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);
|
|
823
1740
|
}
|
|
824
1741
|
|
|
825
1742
|
renderLlmContentStreaming(contentDiv, fullContent, responseId, state) {
|
|
@@ -846,9 +1763,9 @@ export class AgentsView extends Component {
|
|
|
846
1763
|
// Create pill on first thinking chunk
|
|
847
1764
|
if (!thinkingState.thinkingPill) {
|
|
848
1765
|
const pill = document.createElement('div');
|
|
849
|
-
pill.className = '
|
|
1766
|
+
pill.className = 'tool-pill thinking';
|
|
850
1767
|
pill.innerHTML = `
|
|
851
|
-
<i class="fas fa-brain animate-pulse text-
|
|
1768
|
+
<i class="fas fa-brain animate-pulse text-2xs"></i>
|
|
852
1769
|
<span>Thinking...</span>
|
|
853
1770
|
`;
|
|
854
1771
|
toolsDiv.appendChild(pill);
|
|
@@ -858,6 +1775,7 @@ export class AgentsView extends Component {
|
|
|
858
1775
|
}
|
|
859
1776
|
|
|
860
1777
|
finalizeThinkingPill(toolsDiv, thinkingState) {
|
|
1778
|
+
const container = this.querySelector('#chatMessages');
|
|
861
1779
|
const pill = thinkingState.thinkingPill;
|
|
862
1780
|
if (!pill) return;
|
|
863
1781
|
|
|
@@ -865,40 +1783,28 @@ export class AgentsView extends Component {
|
|
|
865
1783
|
thinkingState.thinkingPill = null;
|
|
866
1784
|
thinkingState.thinkingContent = '';
|
|
867
1785
|
|
|
868
|
-
pill.className = '
|
|
1786
|
+
pill.className = 'tool-pill done thinking';
|
|
869
1787
|
pill.innerHTML = '';
|
|
870
1788
|
|
|
871
1789
|
const pillContent = document.createElement('span');
|
|
872
|
-
pillContent.className = 'inline-flex items-center gap-1
|
|
873
|
-
pillContent.innerHTML =
|
|
874
|
-
<i class="fas fa-brain text-purple-400 text-[10px]"></i>
|
|
875
|
-
<span>Thinking</span>
|
|
876
|
-
`;
|
|
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>';
|
|
877
1792
|
pill.appendChild(pillContent);
|
|
878
1793
|
|
|
879
|
-
const
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
const
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
popover.style.top = 'auto';
|
|
894
|
-
if (pillRect.left + 400 > window.innerWidth - 16) {
|
|
895
|
-
popover.style.left = Math.max(8, pillRect.right - 400) + 'px';
|
|
896
|
-
} else {
|
|
897
|
-
popover.style.left = pillRect.left + 'px';
|
|
898
|
-
}
|
|
899
|
-
popover.style.right = 'auto';
|
|
900
|
-
});
|
|
901
|
-
pill.addEventListener('mouseleave', () => popover.classList.add('hidden'));
|
|
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);
|
|
902
1808
|
}
|
|
903
1809
|
|
|
904
1810
|
createResponseBubble(id) {
|
|
@@ -910,54 +1816,52 @@ export class AgentsView extends Component {
|
|
|
910
1816
|
div.id = id;
|
|
911
1817
|
div.className = 'flex justify-start';
|
|
912
1818
|
div.innerHTML = `
|
|
913
|
-
<div class="response-bubble-inner
|
|
1819
|
+
<div class="response-bubble-inner loading group">
|
|
914
1820
|
<div class="response-content whitespace-pre-wrap flex items-center">
|
|
915
|
-
<div class="loading-dots
|
|
916
|
-
<div
|
|
917
|
-
<div
|
|
918
|
-
<div
|
|
1821
|
+
<div class="loading-dots">
|
|
1822
|
+
<div></div>
|
|
1823
|
+
<div></div>
|
|
1824
|
+
<div></div>
|
|
919
1825
|
</div>
|
|
920
1826
|
</div>
|
|
921
|
-
<div class="tool-invocations
|
|
1827
|
+
<div class="tool-invocations"></div>
|
|
922
1828
|
</div>
|
|
923
1829
|
`;
|
|
924
1830
|
|
|
925
1831
|
wrapper.appendChild(div);
|
|
926
1832
|
|
|
927
1833
|
const statusBar = document.createElement('div');
|
|
928
|
-
statusBar.className = 'stream-status-bar
|
|
1834
|
+
statusBar.className = 'stream-status-bar';
|
|
929
1835
|
statusBar.innerHTML = `
|
|
930
|
-
<div class="
|
|
1836
|
+
<div class="status-dot-pulse"></div>
|
|
931
1837
|
<span class="stream-status-text">Generating...</span>
|
|
932
|
-
<span class="stream-elapsed text-
|
|
933
|
-
<button class="stream-cancel-btn
|
|
934
|
-
Stop
|
|
935
|
-
</button>
|
|
1838
|
+
<span class="stream-elapsed text-muted">0.0s</span>
|
|
1839
|
+
<button class="stream-cancel-btn">Stop</button>
|
|
936
1840
|
`;
|
|
937
1841
|
wrapper.appendChild(statusBar);
|
|
938
1842
|
|
|
939
1843
|
statusBar.querySelector('.stream-cancel-btn').addEventListener('click', () => this.cancelCurrentStream());
|
|
940
1844
|
|
|
941
1845
|
const statsBar = document.createElement('div');
|
|
942
|
-
statsBar.className = 'stream-stats-bar
|
|
1846
|
+
statsBar.className = 'stream-stats-bar';
|
|
943
1847
|
statsBar.innerHTML = `
|
|
944
1848
|
<span class="flex items-center gap-1">
|
|
945
1849
|
<i class="far fa-clock"></i>
|
|
946
1850
|
<span class="stats-elapsed"></span>
|
|
947
1851
|
</span>
|
|
948
|
-
<span class="
|
|
1852
|
+
<span class="divider">|</span>
|
|
949
1853
|
<span class="flex items-center gap-1">
|
|
950
|
-
<i class="fas fa-arrow-up text-
|
|
1854
|
+
<i class="fas fa-arrow-up text-2xs"></i>
|
|
951
1855
|
<span class="stats-input-tokens"></span>
|
|
952
1856
|
</span>
|
|
953
|
-
<span class="
|
|
1857
|
+
<span class="divider">|</span>
|
|
954
1858
|
<span class="flex items-center gap-1">
|
|
955
|
-
<i class="fas fa-arrow-down text-
|
|
1859
|
+
<i class="fas fa-arrow-down text-2xs"></i>
|
|
956
1860
|
<span class="stats-output-tokens"></span>
|
|
957
1861
|
</span>
|
|
958
|
-
<span class="
|
|
1862
|
+
<span class="divider">|</span>
|
|
959
1863
|
<span class="flex items-center gap-1">
|
|
960
|
-
<i class="fas fa-bolt text-
|
|
1864
|
+
<i class="fas fa-bolt text-2xs"></i>
|
|
961
1865
|
<span class="stats-tps"></span>
|
|
962
1866
|
</span>
|
|
963
1867
|
`;
|
|
@@ -983,8 +1887,7 @@ export class AgentsView extends Component {
|
|
|
983
1887
|
this.finalizeThinkingPill(toolsDiv, thinkingState);
|
|
984
1888
|
if (loadingDots) {
|
|
985
1889
|
loadingDots.remove();
|
|
986
|
-
bubble.querySelector('.response-bubble-inner').classList.remove('
|
|
987
|
-
bubble.querySelector('.response-bubble-inner').classList.add('py-3');
|
|
1890
|
+
bubble.querySelector('.response-bubble-inner').classList.remove('loading');
|
|
988
1891
|
contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
|
|
989
1892
|
contentDiv.innerHTML = '';
|
|
990
1893
|
}
|
|
@@ -995,10 +1898,10 @@ export class AgentsView extends Component {
|
|
|
995
1898
|
const toolId = `tool-${event.runId}`;
|
|
996
1899
|
const toolEl = document.createElement('div');
|
|
997
1900
|
toolEl.id = toolId;
|
|
998
|
-
toolEl.className = 'tool-pill
|
|
1901
|
+
toolEl.className = 'tool-pill';
|
|
999
1902
|
toolEl.dataset.toolInput = typeof event.input === 'string' ? event.input : JSON.stringify(event.input, null, 2);
|
|
1000
1903
|
toolEl.innerHTML = `
|
|
1001
|
-
<i class="fas fa-circle-notch animate-spin text-blue
|
|
1904
|
+
<i class="fas fa-circle-notch animate-spin text-blue text-2xs"></i>
|
|
1002
1905
|
<span>${this.escapeHtml(event.tool)}</span>
|
|
1003
1906
|
`;
|
|
1004
1907
|
toolsDiv.appendChild(toolEl);
|
|
@@ -1011,71 +1914,43 @@ export class AgentsView extends Component {
|
|
|
1011
1914
|
const toolInput = toolEl.dataset.toolInput || '';
|
|
1012
1915
|
const toolOutput = typeof event.output === 'string' ? event.output : JSON.stringify(event.output, null, 2);
|
|
1013
1916
|
|
|
1014
|
-
toolEl.className = 'tool-pill
|
|
1917
|
+
toolEl.className = 'tool-pill done';
|
|
1015
1918
|
toolEl.innerHTML = '';
|
|
1016
1919
|
|
|
1017
1920
|
const pillContent = document.createElement('span');
|
|
1018
|
-
pillContent.className = 'inline-flex items-center gap-1
|
|
1921
|
+
pillContent.className = 'inline-flex items-center gap-1';
|
|
1019
1922
|
pillContent.innerHTML = `
|
|
1020
|
-
<i class="fas fa-check text-green
|
|
1923
|
+
<i class="fas fa-check text-green text-2xs"></i>
|
|
1021
1924
|
<span>${this.escapeHtml(event.tool)}</span>
|
|
1022
1925
|
`;
|
|
1023
1926
|
toolEl.appendChild(pillContent);
|
|
1024
1927
|
|
|
1025
1928
|
const details = document.createElement('div');
|
|
1026
|
-
details.className = 'tool-invocation-details
|
|
1929
|
+
details.className = 'tool-invocation-details';
|
|
1027
1930
|
|
|
1028
1931
|
if (toolInput) {
|
|
1029
1932
|
const inputSection = document.createElement('div');
|
|
1030
|
-
inputSection.className = '
|
|
1031
|
-
inputSection.innerHTML =
|
|
1933
|
+
inputSection.className = 'tool-detail-section';
|
|
1934
|
+
inputSection.innerHTML = '<h4>Input</h4>';
|
|
1032
1935
|
const inputPre = document.createElement('pre');
|
|
1033
|
-
inputPre.className = '
|
|
1936
|
+
inputPre.className = 'tool-detail-pre custom-scrollbar';
|
|
1034
1937
|
inputPre.textContent = toolInput;
|
|
1035
1938
|
inputSection.appendChild(inputPre);
|
|
1036
1939
|
details.appendChild(inputSection);
|
|
1037
1940
|
}
|
|
1038
1941
|
|
|
1039
1942
|
const outputSection = document.createElement('div');
|
|
1040
|
-
outputSection.className = '
|
|
1041
|
-
outputSection.innerHTML =
|
|
1943
|
+
outputSection.className = 'tool-detail-section';
|
|
1944
|
+
outputSection.innerHTML = '<h4>Output</h4>';
|
|
1042
1945
|
const outputPre = document.createElement('pre');
|
|
1043
|
-
outputPre.className = '
|
|
1946
|
+
outputPre.className = 'tool-detail-pre custom-scrollbar';
|
|
1044
1947
|
outputPre.textContent = toolOutput;
|
|
1045
1948
|
outputSection.appendChild(outputPre);
|
|
1046
1949
|
details.appendChild(outputSection);
|
|
1047
1950
|
|
|
1048
1951
|
toolEl.appendChild(details);
|
|
1049
1952
|
|
|
1050
|
-
|
|
1051
|
-
if (details.contains(e.target)) return;
|
|
1052
|
-
e.preventDefault();
|
|
1053
|
-
e.stopPropagation();
|
|
1054
|
-
toolsDiv.querySelectorAll('.tool-invocation-details:not(.hidden)').forEach(d => {
|
|
1055
|
-
if (d !== details) d.classList.add('hidden');
|
|
1056
|
-
});
|
|
1057
|
-
const wasHidden = details.classList.contains('hidden');
|
|
1058
|
-
details.classList.toggle('hidden');
|
|
1059
|
-
if (wasHidden) {
|
|
1060
|
-
const pillRect = toolEl.getBoundingClientRect();
|
|
1061
|
-
const containerRect = container.getBoundingClientRect();
|
|
1062
|
-
const spaceRight = containerRect.right - pillRect.left;
|
|
1063
|
-
if (spaceRight < 420) {
|
|
1064
|
-
details.style.right = '0';
|
|
1065
|
-
details.style.left = 'auto';
|
|
1066
|
-
} else {
|
|
1067
|
-
details.style.left = '0';
|
|
1068
|
-
details.style.right = 'auto';
|
|
1069
|
-
}
|
|
1070
|
-
}
|
|
1071
|
-
});
|
|
1072
|
-
|
|
1073
|
-
const closeHandler = (e) => {
|
|
1074
|
-
if (!toolEl.contains(e.target)) {
|
|
1075
|
-
details.classList.add('hidden');
|
|
1076
|
-
}
|
|
1077
|
-
};
|
|
1078
|
-
document.addEventListener('click', closeHandler, { capture: true });
|
|
1953
|
+
this._attachClickDetails(toolEl, details, toolsDiv, container);
|
|
1079
1954
|
container.scrollTop = container.scrollHeight;
|
|
1080
1955
|
|
|
1081
1956
|
if (event.tool === 'workspace_write' || event.tool === 'workspace_delete') {
|
|
@@ -1088,17 +1963,16 @@ export class AgentsView extends Component {
|
|
|
1088
1963
|
} else if (event.type === 'result') {
|
|
1089
1964
|
if (loadingDots) {
|
|
1090
1965
|
loadingDots.remove();
|
|
1091
|
-
bubble.querySelector('.response-bubble-inner').classList.remove('
|
|
1092
|
-
bubble.querySelector('.response-bubble-inner').classList.add('py-3');
|
|
1966
|
+
bubble.querySelector('.response-bubble-inner').classList.remove('loading');
|
|
1093
1967
|
contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
|
|
1094
1968
|
contentDiv.innerHTML = '';
|
|
1095
1969
|
}
|
|
1096
1970
|
|
|
1097
1971
|
const resultContainer = document.createElement('div');
|
|
1098
|
-
resultContainer.className = '
|
|
1972
|
+
resultContainer.className = 'panel';
|
|
1099
1973
|
|
|
1100
1974
|
const resultPre = document.createElement('pre');
|
|
1101
|
-
resultPre.className = 'text-sm text-
|
|
1975
|
+
resultPre.className = 'text-sm text-primary font-mono whitespace-pre-wrap overflow-x-auto';
|
|
1102
1976
|
resultPre.textContent = JSON.stringify(event.output, null, 2);
|
|
1103
1977
|
|
|
1104
1978
|
resultContainer.appendChild(resultPre);
|
|
@@ -1108,15 +1982,20 @@ export class AgentsView extends Component {
|
|
|
1108
1982
|
} else if (event.type === 'error') {
|
|
1109
1983
|
if (loadingDots) {
|
|
1110
1984
|
loadingDots.remove();
|
|
1111
|
-
bubble.querySelector('.response-bubble-inner').classList.remove('
|
|
1112
|
-
bubble.querySelector('.response-bubble-inner').classList.add('py-3');
|
|
1985
|
+
bubble.querySelector('.response-bubble-inner').classList.remove('loading');
|
|
1113
1986
|
contentDiv.classList.remove('flex', 'items-center', 'whitespace-pre-wrap');
|
|
1114
1987
|
}
|
|
1115
1988
|
const errorDiv = document.createElement('div');
|
|
1116
|
-
errorDiv.className = 'text-red
|
|
1989
|
+
errorDiv.className = 'text-red text-sm';
|
|
1117
1990
|
errorDiv.textContent = `Error: ${event.error}`;
|
|
1118
1991
|
contentDiv.appendChild(errorDiv);
|
|
1119
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;
|
|
1120
1999
|
} else if (event.type === 'usage') {
|
|
1121
2000
|
this.streamUsageData = {
|
|
1122
2001
|
input_tokens: event.input_tokens || 0,
|
|
@@ -1137,7 +2016,7 @@ export class AgentsView extends Component {
|
|
|
1137
2016
|
const bubble = this.querySelector(`#${id}`);
|
|
1138
2017
|
if (bubble) {
|
|
1139
2018
|
const content = bubble.querySelector('.response-content');
|
|
1140
|
-
content.innerHTML = `<span class="text-red
|
|
2019
|
+
content.innerHTML = `<span class="text-red">${errorMsg}</span>`;
|
|
1141
2020
|
}
|
|
1142
2021
|
}
|
|
1143
2022
|
|
|
@@ -1161,30 +2040,29 @@ export class AgentsView extends Component {
|
|
|
1161
2040
|
const div = document.createElement('div');
|
|
1162
2041
|
div.className = isUser ? 'flex justify-end' : 'flex justify-start';
|
|
1163
2042
|
|
|
1164
|
-
const
|
|
1165
|
-
const textColor = hasError ? 'text-red-300' : 'text-gray-100';
|
|
2043
|
+
const bubbleClass = isUser ? 'user-bubble' : (hasError ? 'response-bubble-inner error' : 'response-bubble-inner');
|
|
1166
2044
|
|
|
1167
2045
|
// Build attachment thumbnails for user messages
|
|
1168
2046
|
let attachmentHtml = '';
|
|
1169
2047
|
if (isUser && metadata.attachments && metadata.attachments.length > 0) {
|
|
1170
2048
|
const thumbs = metadata.attachments.map(att => {
|
|
1171
2049
|
if (att.mediaType.startsWith('image/')) {
|
|
1172
|
-
return `<img src="data:${att.mediaType};base64,${att.data}" class="
|
|
2050
|
+
return `<img src="data:${att.mediaType};base64,${att.data}" class="attachment-thumb">`;
|
|
1173
2051
|
}
|
|
1174
|
-
return `<div class="
|
|
2052
|
+
return `<div class="attachment-pill">
|
|
1175
2053
|
<i class="fas fa-file"></i>
|
|
1176
|
-
<span class="
|
|
2054
|
+
<span class="truncate attachment-name">${this.escapeHtml(att.name)}</span>
|
|
1177
2055
|
</div>`;
|
|
1178
2056
|
}).join('');
|
|
1179
2057
|
attachmentHtml = `<div class="flex flex-wrap gap-2 mb-2">${thumbs}</div>`;
|
|
1180
2058
|
}
|
|
1181
2059
|
|
|
1182
2060
|
div.innerHTML = `
|
|
1183
|
-
<div class="
|
|
2061
|
+
<div class="${bubbleClass} group">
|
|
1184
2062
|
${attachmentHtml}
|
|
1185
2063
|
<div class="whitespace-pre-wrap">${this.escapeHtml(content)}</div>
|
|
1186
2064
|
${!isUser && !hasError ? `
|
|
1187
|
-
<button class="copy-btn
|
|
2065
|
+
<button class="copy-btn" title="Copy">
|
|
1188
2066
|
<i class="far fa-copy"></i>
|
|
1189
2067
|
</button>
|
|
1190
2068
|
` : ''}
|
|
@@ -1211,11 +2089,11 @@ export class AgentsView extends Component {
|
|
|
1211
2089
|
div.id = id;
|
|
1212
2090
|
div.className = 'flex justify-start';
|
|
1213
2091
|
div.innerHTML = `
|
|
1214
|
-
<div class="
|
|
1215
|
-
<div class="
|
|
1216
|
-
<div
|
|
1217
|
-
<div
|
|
1218
|
-
<div
|
|
2092
|
+
<div class="response-bubble-inner loading">
|
|
2093
|
+
<div class="loading-dots">
|
|
2094
|
+
<div></div>
|
|
2095
|
+
<div></div>
|
|
2096
|
+
<div></div>
|
|
1219
2097
|
</div>
|
|
1220
2098
|
</div>
|
|
1221
2099
|
`;
|
|
@@ -1230,9 +2108,7 @@ export class AgentsView extends Component {
|
|
|
1230
2108
|
}
|
|
1231
2109
|
|
|
1232
2110
|
escapeHtml(text) {
|
|
1233
|
-
|
|
1234
|
-
div.textContent = text;
|
|
1235
|
-
return div.innerHTML;
|
|
2111
|
+
return sharedEscapeHtml(text);
|
|
1236
2112
|
}
|
|
1237
2113
|
|
|
1238
2114
|
_getRandomWelcomeMessage() {
|
|
@@ -1307,15 +2183,16 @@ export class AgentsView extends Component {
|
|
|
1307
2183
|
|
|
1308
2184
|
_renderSampleQuestionChips() {
|
|
1309
2185
|
const agent = store.get('selectedAgent');
|
|
1310
|
-
const
|
|
2186
|
+
const workflow = store.get('selectedWorkflow');
|
|
2187
|
+
const questions = agent?.sampleQuestions || workflow?.sampleQuestions;
|
|
1311
2188
|
if (!questions || questions.length === 0) return '';
|
|
1312
2189
|
|
|
1313
2190
|
const chips = questions.map(q =>
|
|
1314
|
-
`<button class="sample-question-chip
|
|
2191
|
+
`<button class="sample-question-chip">${this.escapeHtml(q)}</button>`
|
|
1315
2192
|
).join('');
|
|
1316
2193
|
|
|
1317
2194
|
return `
|
|
1318
|
-
<div class="
|
|
2195
|
+
<div class="sample-questions-wrap">${chips}</div>
|
|
1319
2196
|
`;
|
|
1320
2197
|
}
|
|
1321
2198
|
|
|
@@ -1368,6 +2245,9 @@ export class AgentsView extends Component {
|
|
|
1368
2245
|
// New chat button
|
|
1369
2246
|
this.querySelector('#newChatBtn').addEventListener('click', () => this.showNewSessionModal());
|
|
1370
2247
|
|
|
2248
|
+
// New agent button
|
|
2249
|
+
this.querySelector('#newAgentBtn').addEventListener('click', () => this.showNewAgentModal());
|
|
2250
|
+
|
|
1371
2251
|
// Mobile sidebar toggle
|
|
1372
2252
|
this.querySelector('#sidebarToggleBtn').addEventListener('click', () => this.toggleSidebar(true));
|
|
1373
2253
|
this.querySelector('#sidebarBackdrop').addEventListener('click', () => this.toggleSidebar(false));
|
|
@@ -1375,57 +2255,58 @@ export class AgentsView extends Component {
|
|
|
1375
2255
|
|
|
1376
2256
|
template() {
|
|
1377
2257
|
return `
|
|
1378
|
-
<div class="
|
|
2258
|
+
<div class="agent-shell">
|
|
1379
2259
|
<!-- Mobile sidebar backdrop -->
|
|
1380
|
-
<div id="sidebarBackdrop" class="
|
|
2260
|
+
<div id="sidebarBackdrop" class="sidebar-backdrop"></div>
|
|
1381
2261
|
|
|
1382
2262
|
<!-- Sidebar -->
|
|
1383
|
-
<div id="sidebar" class="
|
|
1384
|
-
fixed md:relative inset-y-0 left-0 z-40 md:z-auto">
|
|
2263
|
+
<div id="sidebar" class="agent-sidebar">
|
|
1385
2264
|
<div class="p-3">
|
|
1386
|
-
<button id="newChatBtn" class="
|
|
1387
|
-
<i class="fas fa-plus text-xs text-
|
|
2265
|
+
<button id="newChatBtn" class="new-chat-btn">
|
|
2266
|
+
<i class="fas fa-plus text-xs text-accent"></i>
|
|
1388
2267
|
<span>New chat</span>
|
|
1389
2268
|
</button>
|
|
1390
2269
|
</div>
|
|
1391
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>
|
|
2276
|
+
</div>
|
|
1392
2277
|
</div>
|
|
1393
2278
|
|
|
1394
2279
|
<!-- Chat Area -->
|
|
1395
|
-
<div class="
|
|
2280
|
+
<div class="chat-area">
|
|
1396
2281
|
<!-- Chat Header -->
|
|
1397
|
-
<div class="
|
|
1398
|
-
<button id="sidebarToggleBtn" class="
|
|
2282
|
+
<div class="chat-header">
|
|
2283
|
+
<button id="sidebarToggleBtn" class="sidebar-toggle-btn">
|
|
1399
2284
|
<i class="fas fa-bars"></i>
|
|
1400
2285
|
</button>
|
|
1401
2286
|
<div id="chatHeader" class="flex-1 min-w-0">
|
|
1402
|
-
<span class="text-
|
|
2287
|
+
<span class="text-muted">No conversation selected</span>
|
|
1403
2288
|
</div>
|
|
1404
2289
|
</div>
|
|
1405
2290
|
|
|
1406
2291
|
<!-- Chat Messages -->
|
|
1407
|
-
<div id="chatMessages" class="
|
|
2292
|
+
<div id="chatMessages" class="chat-messages custom-scrollbar"></div>
|
|
1408
2293
|
|
|
1409
2294
|
<!-- Input Area -->
|
|
1410
|
-
<div class="
|
|
1411
|
-
<div id="attachmentPreview" class="
|
|
1412
|
-
<div class="
|
|
1413
|
-
<input type="file" id="fileInput" multiple accept="image/*,.pdf" class="hidden">
|
|
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">
|
|
1414
2299
|
<textarea id="chatInput" rows="1" readonly
|
|
1415
|
-
class="w-full bg-transparent pl-11 pr-14 py-3 text-gray-100 placeholder-gray-500 resize-none focus:outline-none max-h-[200px] cursor-pointer"
|
|
1416
2300
|
placeholder="Ask anything"></textarea>
|
|
1417
2301
|
|
|
1418
|
-
<div class="
|
|
1419
|
-
<button id="attachBtn" type="button"
|
|
1420
|
-
class="text-gray-500 hover:text-gray-300 p-1.5 rounded-lg hover:bg-dark-hover transition-colors"
|
|
1421
|
-
title="Attach files">
|
|
2302
|
+
<div class="chat-input-actions left">
|
|
2303
|
+
<button id="attachBtn" type="button" class="attach-btn" title="Attach files">
|
|
1422
2304
|
<i class="fas fa-plus text-sm"></i>
|
|
1423
2305
|
</button>
|
|
1424
2306
|
</div>
|
|
1425
2307
|
|
|
1426
|
-
<div class="
|
|
1427
|
-
<button id="sendMessageBtn" disabled
|
|
1428
|
-
class="bg-gradient-to-r from-blue-600 to-blue-700 hover:from-blue-500 hover:to-blue-600 disabled:opacity-50 disabled:cursor-not-allowed text-white p-2 rounded-lg transition-all shadow-lg shadow-blue-900/20">
|
|
2308
|
+
<div class="chat-input-actions right">
|
|
2309
|
+
<button id="sendMessageBtn" disabled class="send-btn">
|
|
1429
2310
|
<i class="fas fa-paper-plane text-sm"></i>
|
|
1430
2311
|
</button>
|
|
1431
2312
|
</div>
|