agent-orcha 0.0.5 → 0.0.7
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 +194 -1277
- package/dist/lib/agents/agent-executor.d.ts +4 -2
- package/dist/lib/agents/agent-executor.d.ts.map +1 -1
- package/dist/lib/agents/agent-executor.js +68 -52
- package/dist/lib/agents/agent-executor.js.map +1 -1
- package/dist/lib/agents/agent-loader.d.ts +3 -0
- package/dist/lib/agents/agent-loader.d.ts.map +1 -1
- package/dist/lib/agents/agent-loader.js +10 -1
- package/dist/lib/agents/agent-loader.js.map +1 -1
- package/dist/lib/agents/react-loop.d.ts.map +1 -1
- package/dist/lib/agents/react-loop.js +180 -142
- package/dist/lib/agents/react-loop.js.map +1 -1
- package/dist/lib/agents/types.d.ts +181 -18
- package/dist/lib/agents/types.d.ts.map +1 -1
- package/dist/lib/agents/types.js +18 -2
- package/dist/lib/agents/types.js.map +1 -1
- package/dist/lib/functions/function-loader.d.ts +2 -0
- package/dist/lib/functions/function-loader.d.ts.map +1 -1
- package/dist/lib/functions/function-loader.js +10 -0
- package/dist/lib/functions/function-loader.js.map +1 -1
- package/dist/lib/integrations/email.d.ts +38 -0
- package/dist/lib/integrations/email.d.ts.map +1 -0
- package/dist/lib/integrations/email.js +249 -0
- package/dist/lib/integrations/email.js.map +1 -0
- package/dist/lib/integrations/integration-manager.d.ts +5 -0
- package/dist/lib/integrations/integration-manager.d.ts.map +1 -1
- package/dist/lib/integrations/integration-manager.js +53 -3
- package/dist/lib/integrations/integration-manager.js.map +1 -1
- package/dist/lib/integrations/types.d.ts +187 -4
- package/dist/lib/integrations/types.d.ts.map +1 -1
- package/dist/lib/integrations/types.js +24 -1
- package/dist/lib/integrations/types.js.map +1 -1
- package/dist/lib/knowledge/knowledge-store.d.ts +6 -0
- package/dist/lib/knowledge/knowledge-store.d.ts.map +1 -1
- package/dist/lib/knowledge/knowledge-store.js +71 -4
- package/dist/lib/knowledge/knowledge-store.js.map +1 -1
- package/dist/lib/knowledge/loaders/file-loaders.d.ts +8 -2
- package/dist/lib/knowledge/loaders/file-loaders.d.ts.map +1 -1
- package/dist/lib/knowledge/loaders/file-loaders.js +89 -60
- package/dist/lib/knowledge/loaders/file-loaders.js.map +1 -1
- package/dist/lib/knowledge/loaders/web-loader.d.ts +12 -3
- package/dist/lib/knowledge/loaders/web-loader.d.ts.map +1 -1
- package/dist/lib/knowledge/loaders/web-loader.js +56 -22
- package/dist/lib/knowledge/loaders/web-loader.js.map +1 -1
- package/dist/lib/knowledge/types.d.ts +56 -20
- package/dist/lib/knowledge/types.d.ts.map +1 -1
- package/dist/lib/knowledge/types.js +18 -3
- package/dist/lib/knowledge/types.js.map +1 -1
- package/dist/lib/llm/llm-call-logger.d.ts +3 -1
- package/dist/lib/llm/llm-call-logger.d.ts.map +1 -1
- package/dist/lib/llm/llm-call-logger.js +31 -26
- package/dist/lib/llm/llm-call-logger.js.map +1 -1
- package/dist/lib/llm/llm-config.d.ts +8 -0
- package/dist/lib/llm/llm-config.d.ts.map +1 -1
- package/dist/lib/llm/llm-config.js +3 -1
- package/dist/lib/llm/llm-config.js.map +1 -1
- package/dist/lib/llm/llm-factory.d.ts.map +1 -1
- package/dist/lib/llm/llm-factory.js +3 -0
- package/dist/lib/llm/llm-factory.js.map +1 -1
- package/dist/lib/llm/providers/anthropic-chat-model.d.ts +5 -1
- package/dist/lib/llm/providers/anthropic-chat-model.d.ts.map +1 -1
- package/dist/lib/llm/providers/anthropic-chat-model.js +118 -42
- package/dist/lib/llm/providers/anthropic-chat-model.js.map +1 -1
- package/dist/lib/llm/providers/gemini-chat-model.d.ts +3 -2
- package/dist/lib/llm/providers/gemini-chat-model.d.ts.map +1 -1
- package/dist/lib/llm/providers/gemini-chat-model.js +83 -24
- package/dist/lib/llm/providers/gemini-chat-model.js.map +1 -1
- package/dist/lib/llm/providers/openai-chat-model.d.ts +10 -1
- package/dist/lib/llm/providers/openai-chat-model.d.ts.map +1 -1
- package/dist/lib/llm/providers/openai-chat-model.js +233 -32
- package/dist/lib/llm/providers/openai-chat-model.js.map +1 -1
- package/dist/lib/logger.d.ts.map +1 -1
- package/dist/lib/logger.js +0 -1
- package/dist/lib/logger.js.map +1 -1
- package/dist/lib/mcp/mcp-client.d.ts.map +1 -1
- package/dist/lib/mcp/mcp-client.js +5 -3
- package/dist/lib/mcp/mcp-client.js.map +1 -1
- package/dist/lib/mcp/types.d.ts +0 -9
- package/dist/lib/mcp/types.d.ts.map +1 -1
- package/dist/lib/mcp/types.js +1 -2
- package/dist/lib/mcp/types.js.map +1 -1
- package/dist/lib/memory/memory-manager.d.ts +1 -0
- package/dist/lib/memory/memory-manager.d.ts.map +1 -1
- package/dist/lib/memory/memory-manager.js +9 -0
- package/dist/lib/memory/memory-manager.js.map +1 -1
- package/dist/lib/orchestrator.d.ts +2 -8
- package/dist/lib/orchestrator.d.ts.map +1 -1
- package/dist/lib/orchestrator.js +96 -3
- package/dist/lib/orchestrator.js.map +1 -1
- package/dist/lib/sandbox/cdp-client.d.ts +14 -0
- package/dist/lib/sandbox/cdp-client.d.ts.map +1 -0
- package/dist/lib/sandbox/cdp-client.js +113 -0
- package/dist/lib/sandbox/cdp-client.js.map +1 -0
- package/dist/lib/sandbox/html-to-markdown.d.ts +9 -1
- package/dist/lib/sandbox/html-to-markdown.d.ts.map +1 -1
- package/dist/lib/sandbox/html-to-markdown.js +67 -10
- package/dist/lib/sandbox/html-to-markdown.js.map +1 -1
- package/dist/lib/sandbox/index.d.ts +5 -0
- package/dist/lib/sandbox/index.d.ts.map +1 -1
- package/dist/lib/sandbox/index.js +4 -0
- package/dist/lib/sandbox/index.js.map +1 -1
- package/dist/lib/sandbox/page-readiness.d.ts +37 -0
- package/dist/lib/sandbox/page-readiness.d.ts.map +1 -0
- package/dist/lib/sandbox/page-readiness.js +235 -0
- package/dist/lib/sandbox/page-readiness.js.map +1 -0
- package/dist/lib/sandbox/sandbox-browser.d.ts +4 -0
- package/dist/lib/sandbox/sandbox-browser.d.ts.map +1 -0
- package/dist/lib/sandbox/sandbox-browser.js +303 -0
- package/dist/lib/sandbox/sandbox-browser.js.map +1 -0
- package/dist/lib/sandbox/sandbox-file.d.ts +4 -0
- package/dist/lib/sandbox/sandbox-file.d.ts.map +1 -0
- package/dist/lib/sandbox/sandbox-file.js +168 -0
- package/dist/lib/sandbox/sandbox-file.js.map +1 -0
- package/dist/lib/sandbox/sandbox-shell.d.ts +4 -0
- package/dist/lib/sandbox/sandbox-shell.d.ts.map +1 -0
- package/dist/lib/sandbox/sandbox-shell.js +93 -0
- package/dist/lib/sandbox/sandbox-shell.js.map +1 -0
- package/dist/lib/sandbox/sandbox-web.d.ts.map +1 -1
- package/dist/lib/sandbox/sandbox-web.js +37 -22
- package/dist/lib/sandbox/sandbox-web.js.map +1 -1
- package/dist/lib/sandbox/types.d.ts +9 -0
- package/dist/lib/sandbox/types.d.ts.map +1 -1
- package/dist/lib/sandbox/types.js +1 -0
- package/dist/lib/sandbox/types.js.map +1 -1
- package/dist/lib/sandbox/vision-browser.d.ts +4 -0
- package/dist/lib/sandbox/vision-browser.d.ts.map +1 -0
- package/dist/lib/sandbox/vision-browser.js +289 -0
- package/dist/lib/sandbox/vision-browser.js.map +1 -0
- package/dist/lib/skills/skill-loader.d.ts +2 -0
- package/dist/lib/skills/skill-loader.d.ts.map +1 -1
- package/dist/lib/skills/skill-loader.js +12 -1
- package/dist/lib/skills/skill-loader.js.map +1 -1
- package/dist/lib/tasks/task-manager.d.ts +3 -1
- package/dist/lib/tasks/task-manager.d.ts.map +1 -1
- package/dist/lib/tasks/task-manager.js +11 -0
- package/dist/lib/tasks/task-manager.js.map +1 -1
- package/dist/lib/tasks/task-store.d.ts +1 -1
- package/dist/lib/tasks/task-store.d.ts.map +1 -1
- package/dist/lib/tasks/task-store.js.map +1 -1
- package/dist/lib/tasks/types.d.ts +18 -0
- package/dist/lib/tasks/types.d.ts.map +1 -1
- package/dist/lib/tools/built-in/integration-tools.d.ts +4 -0
- package/dist/lib/tools/built-in/integration-tools.d.ts.map +1 -0
- package/dist/lib/tools/built-in/integration-tools.js +47 -0
- package/dist/lib/tools/built-in/integration-tools.js.map +1 -0
- package/dist/lib/tools/built-in/knowledge-entity-lookup.tool.d.ts.map +1 -1
- package/dist/lib/tools/built-in/knowledge-entity-lookup.tool.js +12 -6
- package/dist/lib/tools/built-in/knowledge-entity-lookup.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 +4 -3
- package/dist/lib/tools/built-in/knowledge-sql.tool.js.map +1 -1
- package/dist/lib/tools/built-in/query-validators.d.ts.map +1 -1
- package/dist/lib/tools/built-in/query-validators.js +4 -0
- package/dist/lib/tools/built-in/query-validators.js.map +1 -1
- package/dist/lib/tools/workspace/workspace-tools.d.ts +1 -0
- package/dist/lib/tools/workspace/workspace-tools.d.ts.map +1 -1
- package/dist/lib/tools/workspace/workspace-tools.js +39 -0
- package/dist/lib/tools/workspace/workspace-tools.js.map +1 -1
- package/dist/lib/triggers/cron-trigger.d.ts +1 -1
- package/dist/lib/triggers/cron-trigger.d.ts.map +1 -1
- package/dist/lib/triggers/cron-trigger.js.map +1 -1
- package/dist/lib/triggers/trigger-manager.d.ts +1 -0
- package/dist/lib/triggers/trigger-manager.d.ts.map +1 -1
- package/dist/lib/triggers/trigger-manager.js +26 -0
- package/dist/lib/triggers/trigger-manager.js.map +1 -1
- package/dist/lib/triggers/webhook-trigger.d.ts +1 -1
- package/dist/lib/triggers/webhook-trigger.d.ts.map +1 -1
- package/dist/lib/triggers/webhook-trigger.js.map +1 -1
- package/dist/lib/types/llm-types.d.ts +22 -4
- package/dist/lib/types/llm-types.d.ts.map +1 -1
- package/dist/lib/types/llm-types.js +50 -0
- package/dist/lib/types/llm-types.js.map +1 -1
- package/dist/lib/types/tool-factory.d.ts +2 -2
- package/dist/lib/types/tool-factory.d.ts.map +1 -1
- package/dist/lib/types/tool-factory.js.map +1 -1
- package/dist/lib/utils/env-substitution.d.ts +6 -0
- package/dist/lib/utils/env-substitution.d.ts.map +1 -0
- package/dist/lib/utils/env-substitution.js +15 -0
- package/dist/lib/utils/env-substitution.js.map +1 -0
- package/dist/lib/workflows/react-workflow-executor.js +3 -3
- package/dist/lib/workflows/react-workflow-executor.js.map +1 -1
- package/dist/lib/workflows/types.d.ts +10 -10
- package/dist/lib/workflows/workflow-loader.d.ts +3 -0
- package/dist/lib/workflows/workflow-loader.d.ts.map +1 -1
- package/dist/lib/workflows/workflow-loader.js +10 -1
- package/dist/lib/workflows/workflow-loader.js.map +1 -1
- package/dist/public/chat.html +114 -0
- package/dist/public/index.html +157 -0
- package/dist/public/src/components/AgentComposer.js +807 -0
- package/dist/public/src/components/AgentsView.js +740 -317
- package/dist/public/src/components/AppRoot.js +30 -5
- package/dist/public/src/components/GraphView.js +372 -288
- package/dist/public/src/components/IdeView.js +163 -7
- package/dist/public/src/components/MonitorView.js +139 -1
- package/dist/public/src/components/StandaloneChat.js +889 -0
- package/dist/public/src/components/WorkflowsView.js +180 -28
- package/dist/public/src/services/ApiService.js +7 -2
- package/dist/public/src/services/SessionStore.js +83 -0
- package/dist/public/src/store.js +0 -2
- package/dist/public/src/utils/markdown.js +13 -0
- package/dist/src/cli/index.js +7 -4
- package/dist/src/cli/index.js.map +1 -1
- package/dist/src/middleware/auth.d.ts.map +1 -1
- package/dist/src/middleware/auth.js +28 -6
- package/dist/src/middleware/auth.js.map +1 -1
- package/dist/src/middleware/rate-limit.d.ts +8 -0
- package/dist/src/middleware/rate-limit.d.ts.map +1 -0
- package/dist/src/middleware/rate-limit.js +21 -0
- package/dist/src/middleware/rate-limit.js.map +1 -0
- package/dist/src/routes/agents.route.d.ts.map +1 -1
- package/dist/src/routes/agents.route.js +136 -10
- package/dist/src/routes/agents.route.js.map +1 -1
- package/dist/src/routes/chat.route.d.ts +3 -0
- package/dist/src/routes/chat.route.d.ts.map +1 -0
- package/dist/src/routes/chat.route.js +155 -0
- package/dist/src/routes/chat.route.js.map +1 -0
- package/dist/src/routes/files.route.d.ts.map +1 -1
- package/dist/src/routes/files.route.js +37 -2
- package/dist/src/routes/files.route.js.map +1 -1
- package/dist/src/routes/llm.route.d.ts.map +1 -1
- package/dist/src/routes/llm.route.js +40 -5
- package/dist/src/routes/llm.route.js.map +1 -1
- package/dist/src/routes/tasks.route.d.ts.map +1 -1
- package/dist/src/routes/tasks.route.js +15 -1
- package/dist/src/routes/tasks.route.js.map +1 -1
- package/dist/src/routes/vnc.route.d.ts +3 -0
- package/dist/src/routes/vnc.route.d.ts.map +1 -0
- package/dist/src/routes/vnc.route.js +49 -0
- package/dist/src/routes/vnc.route.js.map +1 -0
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +5 -1
- package/dist/src/server.js.map +1 -1
- package/dist/templates/Demo.md +152 -0
- package/dist/templates/README.md +12 -3
- package/dist/templates/agents/architect.agent.yaml +20 -12
- package/dist/templates/agents/chatbot.agent.yaml +23 -26
- package/dist/templates/agents/corporate.agent.yaml +65 -0
- package/dist/templates/agents/investment-analyst.agent.yaml +80 -0
- package/dist/templates/agents/music-librarian.agent.yaml +70 -0
- package/dist/templates/agents/network-security.agent.yaml +82 -0
- package/dist/templates/agents/transport-security.agent.yaml +70 -0
- package/dist/templates/agents/web-engineer.agent.yaml +99 -0
- package/dist/templates/agents/web-pilot.agent.yaml +58 -0
- package/dist/templates/knowledge/music-store/LICENSE.md +11 -0
- package/dist/templates/knowledge/music-store/musicstore.sqlite +0 -0
- package/dist/templates/knowledge/music-store/tables.png +0 -0
- package/dist/templates/knowledge/music-store.knowledge.yaml +138 -0
- package/dist/templates/knowledge/org-chart/personnel.csv +21 -21
- package/dist/templates/knowledge/org-chart.knowledge.yaml +4 -0
- package/dist/templates/knowledge/pet-store.knowledge.yaml +3 -0
- package/dist/templates/knowledge/security-incidents/incidents.json +55935 -0
- package/dist/templates/knowledge/security-incidents.knowledge.yaml +46 -0
- package/dist/templates/knowledge/{example.knowledge.yaml → transcripts.knowledge.yaml} +9 -5
- package/dist/templates/knowledge/transport-ot/systems.csv +117 -0
- package/dist/templates/knowledge/transport-ot.knowledge.yaml +55 -0
- package/dist/templates/llm.json +7 -30
- package/dist/templates/mcp.json +7 -4
- package/dist/templates/skills/orcha-builder/SKILL.md +106 -226
- package/dist/templates/skills/pii-guard/SKILL.md +22 -0
- package/dist/templates/skills/sandbox/SKILL.md +25 -48
- package/dist/templates/skills/web-pilot/SKILL.md +51 -0
- package/package.json +8 -3
- package/dist/templates/agents/knowledge-broker.agent.yaml +0 -39
- package/dist/templates/agents/sandbox.agent.yaml +0 -56
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import { Component } from '../utils/Component.js';
|
|
3
3
|
import { api } from '../services/ApiService.js';
|
|
4
|
+
import { sessionStore } from '../services/SessionStore.js';
|
|
4
5
|
import { store } from '../store.js';
|
|
5
6
|
import { markdownRenderer } from '../utils/markdown.js';
|
|
6
7
|
|
|
@@ -12,11 +13,13 @@ export class AgentsView extends Component {
|
|
|
12
13
|
this.streamStartTime = null;
|
|
13
14
|
this.streamTimerInterval = null;
|
|
14
15
|
this.streamUsageData = null;
|
|
16
|
+
this.pendingAttachments = [];
|
|
15
17
|
}
|
|
16
18
|
|
|
17
19
|
async connectedCallback() {
|
|
18
20
|
super.connectedCallback();
|
|
19
21
|
await Promise.all([this.loadAgents(), this.loadLLMs()]);
|
|
22
|
+
this.restoreActiveSession();
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
disconnectedCallback() {
|
|
@@ -116,15 +119,8 @@ export class AgentsView extends Component {
|
|
|
116
119
|
async loadAgents() {
|
|
117
120
|
try {
|
|
118
121
|
const agents = await api.getAgents();
|
|
122
|
+
agents.sort((a, b) => (a.name || '').localeCompare(b.name || ''));
|
|
119
123
|
store.set('agents', agents);
|
|
120
|
-
|
|
121
|
-
if (agents.length > 0 && !store.get('selectedAgent') && !store.get('selectedLlm')) {
|
|
122
|
-
store.set('selectedAgent', agents[agents.length - 1]);
|
|
123
|
-
store.set('selectionType', 'agent');
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
this.renderAgentDropdown();
|
|
127
|
-
this.updateSelectedAgentUI();
|
|
128
124
|
} catch (e) {
|
|
129
125
|
console.error('Failed to load agents', e);
|
|
130
126
|
}
|
|
@@ -134,152 +130,491 @@ export class AgentsView extends Component {
|
|
|
134
130
|
try {
|
|
135
131
|
const llms = await api.getLLMs();
|
|
136
132
|
store.set('llms', llms);
|
|
137
|
-
this.renderAgentDropdown();
|
|
138
133
|
} catch (e) {
|
|
139
134
|
console.error('Failed to load LLMs', e);
|
|
140
135
|
}
|
|
141
136
|
}
|
|
142
137
|
|
|
143
|
-
|
|
144
|
-
|
|
138
|
+
// --- Sidebar toggle (mobile) ---
|
|
139
|
+
|
|
140
|
+
_isMobile() {
|
|
141
|
+
return !window.matchMedia('(min-width: 768px)').matches;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
toggleSidebar(show) {
|
|
145
|
+
const sidebar = this.querySelector('#sidebar');
|
|
146
|
+
const backdrop = this.querySelector('#sidebarBackdrop');
|
|
147
|
+
if (!sidebar || !backdrop) return;
|
|
148
|
+
|
|
149
|
+
if (show) {
|
|
150
|
+
sidebar.classList.remove('hidden');
|
|
151
|
+
sidebar.classList.add('flex', 'sidebar-open');
|
|
152
|
+
backdrop.classList.remove('hidden');
|
|
153
|
+
} else {
|
|
154
|
+
sidebar.classList.add('hidden');
|
|
155
|
+
sidebar.classList.remove('flex', 'sidebar-open');
|
|
156
|
+
backdrop.classList.add('hidden');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// --- Session management ---
|
|
161
|
+
|
|
162
|
+
restoreActiveSession() {
|
|
163
|
+
const activeId = sessionStore.getActiveId();
|
|
164
|
+
if (activeId && sessionStore.get(activeId)) {
|
|
165
|
+
this.switchToSession(activeId);
|
|
166
|
+
} else {
|
|
167
|
+
this.showEmptyState();
|
|
168
|
+
}
|
|
169
|
+
this.renderSessionList();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
renderSessionList() {
|
|
173
|
+
const list = this.querySelector('#sessionList');
|
|
174
|
+
if (!list) return;
|
|
175
|
+
|
|
176
|
+
const sessions = sessionStore.getAll();
|
|
177
|
+
const activeId = sessionStore.getActiveId();
|
|
178
|
+
|
|
179
|
+
if (sessions.length === 0) {
|
|
180
|
+
list.innerHTML = '<div class="text-gray-500 text-sm text-center py-8">No conversations yet</div>';
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
list.innerHTML = sessions.map(s => {
|
|
185
|
+
const isActive = s.id === activeId;
|
|
186
|
+
const isAgent = s.agentType === 'agent';
|
|
187
|
+
const displayName = isAgent ? (s.agentName || 'Agent') : (s.llmName || 'LLM');
|
|
188
|
+
const icon = isAgent ? 'fa-robot' : 'fa-microchip';
|
|
189
|
+
|
|
190
|
+
const activeClasses = isActive
|
|
191
|
+
? 'bg-dark-hover/80 border-l-2 border-l-blue-500'
|
|
192
|
+
: 'hover:bg-dark-hover/40 border-l-2 border-l-transparent';
|
|
193
|
+
|
|
194
|
+
return `
|
|
195
|
+
<div data-session-id="${s.id}" class="session-item group flex items-start gap-2 px-3 py-2.5 cursor-pointer rounded-lg mb-0.5 transition-colors ${activeClasses}">
|
|
196
|
+
<div class="flex-1 min-w-0">
|
|
197
|
+
<div class="text-sm ${isActive ? 'text-gray-100' : 'text-gray-300'} truncate">${this.escapeHtml(s.title)}</div>
|
|
198
|
+
<div class="flex items-center gap-1.5 mt-0.5 text-xs text-gray-500">
|
|
199
|
+
<i class="fas ${icon} text-[10px]"></i>
|
|
200
|
+
<span class="truncate">${this.escapeHtml(displayName)}</span>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
<button data-delete-id="${s.id}" class="session-delete-btn flex-shrink-0 text-gray-600 hover:text-red-400 opacity-0 group-hover:opacity-100 transition-opacity p-1 mt-0.5" title="Delete">
|
|
204
|
+
<i class="fas fa-xmark text-xs"></i>
|
|
205
|
+
</button>
|
|
206
|
+
</div>
|
|
207
|
+
`;
|
|
208
|
+
}).join('');
|
|
209
|
+
|
|
210
|
+
// Event listeners
|
|
211
|
+
list.querySelectorAll('.session-item').forEach(item => {
|
|
212
|
+
item.addEventListener('click', (e) => {
|
|
213
|
+
if (e.target.closest('.session-delete-btn')) return;
|
|
214
|
+
this.switchToSession(item.dataset.sessionId);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
list.querySelectorAll('.session-delete-btn').forEach(btn => {
|
|
219
|
+
btn.addEventListener('click', (e) => {
|
|
220
|
+
e.stopPropagation();
|
|
221
|
+
this.deleteSession(btn.dataset.deleteId);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
switchToSession(sessionId) {
|
|
227
|
+
// Abort any running stream
|
|
228
|
+
if (this.currentAbortController) {
|
|
229
|
+
this.currentAbortController.abort();
|
|
230
|
+
this.currentAbortController = null;
|
|
231
|
+
}
|
|
232
|
+
this.isLoading = false;
|
|
233
|
+
|
|
234
|
+
const session = sessionStore.get(sessionId);
|
|
235
|
+
if (!session) return;
|
|
236
|
+
|
|
237
|
+
sessionStore.setActiveId(sessionId);
|
|
238
|
+
|
|
239
|
+
// Update store with this session's agent/LLM
|
|
145
240
|
const agents = store.get('agents') || [];
|
|
146
241
|
const llms = store.get('llms') || [];
|
|
147
|
-
const selectionType = store.get('selectionType');
|
|
148
|
-
const selectedAgent = store.get('selectedAgent');
|
|
149
|
-
const selectedLlm = store.get('selectedLlm');
|
|
150
242
|
|
|
151
|
-
if (
|
|
243
|
+
if (session.agentType === 'agent') {
|
|
244
|
+
const agent = agents.find(a => a.name === session.agentName);
|
|
245
|
+
store.set('selectedAgent', agent || null);
|
|
246
|
+
store.set('selectedLlm', null);
|
|
247
|
+
store.set('selectionType', 'agent');
|
|
248
|
+
} else {
|
|
249
|
+
const llm = llms.find(l => l.name === session.llmName);
|
|
250
|
+
store.set('selectedLlm', llm || null);
|
|
251
|
+
store.set('selectedAgent', null);
|
|
252
|
+
store.set('selectionType', 'llm');
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
this.restoreMessages(session);
|
|
256
|
+
this.updateChatHeader(session);
|
|
257
|
+
this.renderSessionList();
|
|
258
|
+
this.updateUiState();
|
|
259
|
+
|
|
260
|
+
// Close sidebar on mobile after selecting
|
|
261
|
+
if (this._isMobile()) {
|
|
262
|
+
this.toggleSidebar(false);
|
|
263
|
+
}
|
|
152
264
|
|
|
153
|
-
|
|
154
|
-
|
|
265
|
+
const input = this.querySelector('#chatInput');
|
|
266
|
+
if (input) {
|
|
267
|
+
input.disabled = false;
|
|
268
|
+
input.readOnly = false;
|
|
269
|
+
input.classList.remove('cursor-pointer');
|
|
270
|
+
input.focus();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
async restoreMessages(session) {
|
|
275
|
+
const container = this.querySelector('#chatMessages');
|
|
276
|
+
if (!container) return;
|
|
277
|
+
container.innerHTML = '';
|
|
278
|
+
|
|
279
|
+
if (session.messages.length === 0) {
|
|
280
|
+
this._appendWelcomeMessage(container);
|
|
155
281
|
return;
|
|
156
282
|
}
|
|
157
283
|
|
|
158
|
-
|
|
284
|
+
for (const msg of session.messages) {
|
|
285
|
+
if (msg.role === 'user') {
|
|
286
|
+
this.appendMessage('user', msg.content);
|
|
287
|
+
} else {
|
|
288
|
+
this.appendRestoredAssistantMessage(msg.content);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Check if server still has this session (survives restarts)
|
|
293
|
+
try {
|
|
294
|
+
const exists = await api.checkSession(session.id);
|
|
295
|
+
if (!exists) {
|
|
296
|
+
this._appendSessionResetBanner(container);
|
|
297
|
+
}
|
|
298
|
+
} catch {
|
|
299
|
+
// Server unreachable — skip banner
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
appendRestoredAssistantMessage(content) {
|
|
304
|
+
const container = this.querySelector('#chatMessages');
|
|
305
|
+
const div = document.createElement('div');
|
|
306
|
+
div.className = 'response-wrapper';
|
|
307
|
+
|
|
308
|
+
const bubble = document.createElement('div');
|
|
309
|
+
bubble.className = 'flex justify-start';
|
|
310
|
+
bubble.innerHTML = `
|
|
311
|
+
<div class="response-bubble-inner max-w-4xl bg-dark-surface border border-dark-border rounded-3xl px-5 py-3 text-gray-100 text-[15px] leading-relaxed relative group">
|
|
312
|
+
<div class="response-content markdown-content"></div>
|
|
313
|
+
</div>
|
|
314
|
+
`;
|
|
315
|
+
|
|
316
|
+
const contentDiv = bubble.querySelector('.response-content');
|
|
317
|
+
const rendered = markdownRenderer.render(content);
|
|
318
|
+
contentDiv.innerHTML = rendered;
|
|
319
|
+
markdownRenderer.highlightCode(contentDiv);
|
|
320
|
+
|
|
321
|
+
div.appendChild(bubble);
|
|
322
|
+
container.appendChild(div);
|
|
323
|
+
container.scrollTop = container.scrollHeight;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
showNewSessionModal() {
|
|
327
|
+
// Remove existing modal if any
|
|
328
|
+
const existing = document.querySelector('#newSessionModal');
|
|
329
|
+
if (existing) existing.remove();
|
|
330
|
+
|
|
331
|
+
const agents = store.get('agents') || [];
|
|
332
|
+
const llms = store.get('llms') || [];
|
|
333
|
+
|
|
334
|
+
const overlay = document.createElement('div');
|
|
335
|
+
overlay.id = 'newSessionModal';
|
|
336
|
+
overlay.className = 'modal-backdrop fixed inset-0 z-50 flex items-center justify-center bg-black/70';
|
|
337
|
+
|
|
338
|
+
let itemsHtml = '';
|
|
159
339
|
|
|
160
|
-
// Agents section
|
|
161
340
|
if (agents.length > 0) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
<div
|
|
167
|
-
<div class="
|
|
168
|
-
|
|
169
|
-
<div class="font-medium text-gray-200 mb-0.5">${agent.name}</div>
|
|
170
|
-
<div class="text-xs text-gray-500 line-clamp-2">${agent.description}</div>
|
|
171
|
-
</div>
|
|
172
|
-
${isSelected ? '<i class="fas fa-check text-blue-400 ml-2 mt-1"></i>' : ''}
|
|
173
|
-
</div>
|
|
341
|
+
itemsHtml += '<div class="px-4 py-2 text-xs font-semibold text-gray-400 uppercase tracking-wider">Agents</div>';
|
|
342
|
+
itemsHtml += agents.map(a => `
|
|
343
|
+
<button data-type="agent" data-name="${this.escapeHtml(a.name)}" class="modal-pick-item w-full text-left px-4 py-3 hover:bg-dark-hover cursor-pointer transition-colors border-b border-dark-border/50 flex items-center gap-3">
|
|
344
|
+
<i class="fas fa-robot text-blue-400 text-sm"></i>
|
|
345
|
+
<div class="flex-1 min-w-0">
|
|
346
|
+
<div class="text-sm font-medium text-gray-200">${this.escapeHtml(a.name)}</div>
|
|
347
|
+
<div class="text-xs text-gray-500 truncate">${this.escapeHtml(a.description || '')}</div>
|
|
174
348
|
</div>
|
|
175
|
-
|
|
176
|
-
|
|
349
|
+
</button>
|
|
350
|
+
`).join('');
|
|
177
351
|
}
|
|
178
352
|
|
|
179
|
-
// LLMs section
|
|
180
353
|
if (llms.length > 0) {
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
<div
|
|
186
|
-
<div class="
|
|
187
|
-
|
|
188
|
-
<div class="font-medium text-gray-200 mb-0.5">${llm.name}</div>
|
|
189
|
-
<div class="text-xs text-gray-500 line-clamp-2">${llm.model}</div>
|
|
190
|
-
</div>
|
|
191
|
-
${isSelected ? '<i class="fas fa-check text-blue-400 ml-2 mt-1"></i>' : ''}
|
|
192
|
-
</div>
|
|
354
|
+
itemsHtml += '<div class="px-4 py-2 text-xs font-semibold text-gray-400 uppercase tracking-wider">LLMs</div>';
|
|
355
|
+
itemsHtml += llms.map(l => `
|
|
356
|
+
<button data-type="llm" data-name="${this.escapeHtml(l.name)}" class="modal-pick-item w-full text-left px-4 py-3 hover:bg-dark-hover cursor-pointer transition-colors border-b border-dark-border/50 flex items-center gap-3">
|
|
357
|
+
<i class="fas fa-microchip text-purple-400 text-sm"></i>
|
|
358
|
+
<div class="flex-1 min-w-0">
|
|
359
|
+
<div class="text-sm font-medium text-gray-200">${this.escapeHtml(l.name)}</div>
|
|
360
|
+
<div class="text-xs text-gray-500 truncate">${this.escapeHtml(l.model || '')}</div>
|
|
193
361
|
</div>
|
|
194
|
-
|
|
195
|
-
|
|
362
|
+
</button>
|
|
363
|
+
`).join('');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (!itemsHtml) {
|
|
367
|
+
itemsHtml = '<div class="text-gray-500 text-sm text-center py-8">No agents or LLMs available</div>';
|
|
196
368
|
}
|
|
197
369
|
|
|
198
|
-
|
|
370
|
+
overlay.innerHTML = `
|
|
371
|
+
<div class="modal-content bg-dark-surface border border-dark-border rounded-2xl shadow-2xl w-[420px] max-w-[90vw] max-h-[70vh] flex flex-col overflow-hidden">
|
|
372
|
+
<div class="flex items-center justify-between px-5 py-4 border-b border-dark-border">
|
|
373
|
+
<h3 class="text-lg font-semibold text-gray-100">New conversation</h3>
|
|
374
|
+
<button id="closeNewSessionModal" class="text-gray-400 hover:text-gray-200 transition-colors p-1">
|
|
375
|
+
<i class="fas fa-xmark"></i>
|
|
376
|
+
</button>
|
|
377
|
+
</div>
|
|
378
|
+
<div class="overflow-y-auto custom-scrollbar flex-1">
|
|
379
|
+
${itemsHtml}
|
|
380
|
+
</div>
|
|
381
|
+
</div>
|
|
382
|
+
`;
|
|
383
|
+
|
|
384
|
+
document.body.appendChild(overlay);
|
|
385
|
+
|
|
386
|
+
// Close on backdrop click
|
|
387
|
+
overlay.addEventListener('click', (e) => {
|
|
388
|
+
if (e.target === overlay) overlay.remove();
|
|
389
|
+
});
|
|
199
390
|
|
|
200
|
-
|
|
391
|
+
overlay.querySelector('#closeNewSessionModal').addEventListener('click', () => overlay.remove());
|
|
392
|
+
|
|
393
|
+
// Pick handler
|
|
394
|
+
overlay.querySelectorAll('.modal-pick-item').forEach(item => {
|
|
201
395
|
item.addEventListener('click', () => {
|
|
202
396
|
const type = item.dataset.type;
|
|
203
397
|
const name = item.dataset.name;
|
|
204
398
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
const isSwitching = !(type === currentType && name === currentName);
|
|
212
|
-
|
|
213
|
-
if (type === 'agent') {
|
|
214
|
-
const agent = agents.find(a => a.name === name);
|
|
215
|
-
store.set('selectedAgent', agent);
|
|
216
|
-
store.set('selectedLlm', null);
|
|
217
|
-
store.set('selectionType', 'agent');
|
|
218
|
-
} else if (type === 'llm') {
|
|
219
|
-
const llm = llms.find(l => l.name === name);
|
|
220
|
-
store.set('selectedLlm', llm);
|
|
221
|
-
store.set('selectedAgent', null);
|
|
222
|
-
store.set('selectionType', 'llm');
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
// Clear chat history when switching
|
|
226
|
-
if (isSwitching) {
|
|
227
|
-
this.clearChatHistory();
|
|
228
|
-
}
|
|
399
|
+
const session = sessionStore.create({
|
|
400
|
+
agentName: type === 'agent' ? name : null,
|
|
401
|
+
agentType: type,
|
|
402
|
+
llmName: type === 'llm' ? name : null
|
|
403
|
+
});
|
|
229
404
|
|
|
230
|
-
|
|
231
|
-
this.
|
|
232
|
-
this.renderAgentDropdown(); // Re-render to update selection checkmark
|
|
405
|
+
overlay.remove();
|
|
406
|
+
this.switchToSession(session.id);
|
|
233
407
|
});
|
|
234
408
|
});
|
|
235
409
|
}
|
|
236
410
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
|
|
411
|
+
deleteSession(sessionId) {
|
|
412
|
+
sessionStore.delete(sessionId);
|
|
413
|
+
const activeId = sessionStore.getActiveId();
|
|
414
|
+
|
|
415
|
+
if (!activeId || activeId === sessionId) {
|
|
416
|
+
// Switch to most recent remaining session
|
|
417
|
+
const sessions = sessionStore.getAll();
|
|
418
|
+
if (sessions.length > 0) {
|
|
419
|
+
this.switchToSession(sessions[0].id);
|
|
420
|
+
} else {
|
|
421
|
+
this.showEmptyState();
|
|
422
|
+
this.renderSessionList();
|
|
423
|
+
}
|
|
424
|
+
} else {
|
|
425
|
+
this.renderSessionList();
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
showEmptyState() {
|
|
430
|
+
const container = this.querySelector('#chatMessages');
|
|
431
|
+
if (container) {
|
|
432
|
+
container.innerHTML = `
|
|
433
|
+
<div class="flex-1 flex items-center justify-center h-full">
|
|
434
|
+
<div class="text-center text-gray-500">
|
|
435
|
+
<i class="fas fa-comments text-4xl mb-4 text-gray-600"></i>
|
|
436
|
+
<p class="text-lg">Start a new conversation</p>
|
|
437
|
+
<p class="text-sm mt-1">Click "New chat" to begin</p>
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
`;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
this.updateChatHeader(null);
|
|
444
|
+
|
|
445
|
+
const input = this.querySelector('#chatInput');
|
|
446
|
+
if (input) {
|
|
447
|
+
input.disabled = false;
|
|
448
|
+
input.readOnly = true;
|
|
449
|
+
input.classList.add('cursor-pointer');
|
|
450
|
+
}
|
|
451
|
+
|
|
242
452
|
const btn = this.querySelector('#sendMessageBtn');
|
|
453
|
+
if (btn) btn.disabled = true;
|
|
454
|
+
}
|
|
243
455
|
|
|
244
|
-
|
|
456
|
+
updateChatHeader(session) {
|
|
457
|
+
const header = this.querySelector('#chatHeader');
|
|
458
|
+
if (!header) return;
|
|
245
459
|
|
|
246
|
-
if (
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
nameEl.textContent = 'Select Agent/LLM';
|
|
460
|
+
if (!session) {
|
|
461
|
+
header.innerHTML = '<span class="text-gray-500">No conversation selected</span>';
|
|
462
|
+
return;
|
|
250
463
|
}
|
|
251
464
|
|
|
252
|
-
|
|
465
|
+
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
|
+
|
|
471
|
+
let extraBadges = '';
|
|
472
|
+
if (isAgent) {
|
|
473
|
+
const agents = store.get('agents') || [];
|
|
474
|
+
const agent = agents.find(a => a.name === session.agentName);
|
|
475
|
+
if (agent) {
|
|
476
|
+
if (agent.publish?.enabled) {
|
|
477
|
+
const chatUrl = `/chat/${encodeURIComponent(agent.name)}`;
|
|
478
|
+
extraBadges += `<a href="${chatUrl}" target="_blank" class="text-xs px-2 py-0.5 rounded-full bg-green-500/20 text-green-400 hover:bg-green-500/30 transition-colors no-underline" title="Open published chat"><i class="fas fa-globe text-[10px]"></i> Published</a>`;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const hasMemory = agent.memory === true || (agent.memory && agent.memory.enabled);
|
|
482
|
+
if (hasMemory) {
|
|
483
|
+
extraBadges += `<span class="text-xs px-2 py-0.5 rounded-full bg-amber-500/20 text-amber-400" title="Persistent memory enabled"><i class="fas fa-brain text-[10px]"></i> Memory</span>`;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
if (agent.tools?.length) {
|
|
487
|
+
const toolNames = agent.tools.map(t => typeof t === 'string' ? t : t.name);
|
|
488
|
+
const toolListHtml = toolNames.map(t => `<div class="tools-popover-item">${this.escapeHtml(t)}</div>`).join('');
|
|
489
|
+
extraBadges += `
|
|
490
|
+
<span class="tools-badge-wrapper">
|
|
491
|
+
<span class="text-xs px-2 py-0.5 rounded-full bg-gray-500/20 text-gray-400 cursor-default"><i class="fas fa-wrench text-[10px]"></i> ${toolNames.length} tool${toolNames.length !== 1 ? 's' : ''}</span>
|
|
492
|
+
<div class="tools-popover">${toolListHtml}</div>
|
|
493
|
+
</span>`;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
header.innerHTML = `
|
|
499
|
+
<div class="flex items-center gap-2 flex-wrap">
|
|
500
|
+
<i class="fas ${icon} text-sm text-gray-400"></i>
|
|
501
|
+
<span class="font-medium text-gray-200">${this.escapeHtml(name)}</span>
|
|
502
|
+
<span class="text-xs px-2 py-0.5 rounded-full ${badgeColor}">${badgeText}</span>
|
|
503
|
+
${extraBadges}
|
|
504
|
+
</div>
|
|
505
|
+
`;
|
|
253
506
|
}
|
|
254
507
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
508
|
+
// --- File Attachments ---
|
|
509
|
+
|
|
510
|
+
handleFileSelect(e) {
|
|
511
|
+
const files = Array.from(e.target.files);
|
|
512
|
+
e.target.value = '';
|
|
513
|
+
|
|
514
|
+
const needsConversion = ['image/webp', 'image/bmp', 'image/tiff'];
|
|
515
|
+
|
|
516
|
+
for (const file of files) {
|
|
517
|
+
if (needsConversion.includes(file.type)) {
|
|
518
|
+
this.convertImageToJpeg(file);
|
|
262
519
|
} else {
|
|
263
|
-
|
|
520
|
+
const reader = new FileReader();
|
|
521
|
+
reader.onload = () => {
|
|
522
|
+
const dataUrl = reader.result;
|
|
523
|
+
const commaIdx = dataUrl.indexOf(',');
|
|
524
|
+
const base64 = dataUrl.slice(commaIdx + 1);
|
|
525
|
+
const mediaType = file.type || 'application/octet-stream';
|
|
526
|
+
|
|
527
|
+
this.pendingAttachments.push({ data: base64, mediaType, name: file.name });
|
|
528
|
+
this.renderAttachmentPreview();
|
|
529
|
+
};
|
|
530
|
+
reader.readAsDataURL(file);
|
|
264
531
|
}
|
|
265
532
|
}
|
|
266
533
|
}
|
|
267
534
|
|
|
535
|
+
convertImageToJpeg(file) {
|
|
536
|
+
const img = new Image();
|
|
537
|
+
const url = URL.createObjectURL(file);
|
|
538
|
+
img.onload = () => {
|
|
539
|
+
const canvas = document.createElement('canvas');
|
|
540
|
+
canvas.width = img.naturalWidth;
|
|
541
|
+
canvas.height = img.naturalHeight;
|
|
542
|
+
canvas.getContext('2d').drawImage(img, 0, 0);
|
|
543
|
+
URL.revokeObjectURL(url);
|
|
544
|
+
|
|
545
|
+
const dataUrl = canvas.toDataURL('image/jpeg', 0.92);
|
|
546
|
+
const base64 = dataUrl.split(',')[1];
|
|
547
|
+
this.pendingAttachments.push({ data: base64, mediaType: 'image/jpeg', name: file.name });
|
|
548
|
+
this.renderAttachmentPreview();
|
|
549
|
+
};
|
|
550
|
+
img.src = url;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
renderAttachmentPreview() {
|
|
554
|
+
const preview = this.querySelector('#attachmentPreview');
|
|
555
|
+
if (!preview) return;
|
|
556
|
+
|
|
557
|
+
if (this.pendingAttachments.length === 0) {
|
|
558
|
+
preview.classList.add('hidden');
|
|
559
|
+
preview.innerHTML = '';
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
preview.classList.remove('hidden');
|
|
564
|
+
preview.innerHTML = this.pendingAttachments.map((att, i) => {
|
|
565
|
+
const isImage = att.mediaType.startsWith('image/');
|
|
566
|
+
const thumb = isImage
|
|
567
|
+
? `<img src="data:${att.mediaType};base64,${att.data}" class="w-10 h-10 object-cover rounded">`
|
|
568
|
+
: `<i class="fas fa-file text-gray-400 text-lg"></i>`;
|
|
569
|
+
return `
|
|
570
|
+
<div class="attachment-pill flex items-center gap-2 bg-dark-bg/60 border border-dark-border/50 rounded-lg px-2 py-1.5 text-xs text-gray-400">
|
|
571
|
+
${thumb}
|
|
572
|
+
<span class="max-w-[120px] truncate">${this.escapeHtml(att.name)}</span>
|
|
573
|
+
<button class="attachment-remove text-gray-500 hover:text-gray-300 ml-1" data-index="${i}">
|
|
574
|
+
<i class="fas fa-xmark text-xs"></i>
|
|
575
|
+
</button>
|
|
576
|
+
</div>
|
|
577
|
+
`;
|
|
578
|
+
}).join('');
|
|
579
|
+
|
|
580
|
+
preview.querySelectorAll('.attachment-remove').forEach(btn => {
|
|
581
|
+
btn.addEventListener('click', (e) => {
|
|
582
|
+
const idx = parseInt(e.currentTarget.dataset.index, 10);
|
|
583
|
+
this.pendingAttachments.splice(idx, 1);
|
|
584
|
+
this.renderAttachmentPreview();
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
clearAttachments() {
|
|
590
|
+
this.pendingAttachments = [];
|
|
591
|
+
this.renderAttachmentPreview();
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// --- Messaging ---
|
|
595
|
+
|
|
268
596
|
async sendMessage() {
|
|
269
597
|
const input = this.querySelector('#chatInput');
|
|
270
598
|
const message = input.value.trim();
|
|
271
599
|
const selectionType = store.get('selectionType');
|
|
272
600
|
const agent = store.get('selectedAgent');
|
|
273
601
|
const llm = store.get('selectedLlm');
|
|
602
|
+
const activeId = sessionStore.getActiveId();
|
|
274
603
|
|
|
275
604
|
const selected = selectionType === 'agent' ? agent : llm;
|
|
605
|
+
const hasAttachments = this.pendingAttachments.length > 0;
|
|
276
606
|
|
|
277
|
-
if (!message || !selected || this.isLoading) return;
|
|
607
|
+
if ((!message && !hasAttachments) || !selected || this.isLoading || !activeId) return;
|
|
278
608
|
|
|
279
|
-
//
|
|
280
|
-
this.
|
|
609
|
+
// Capture attachments before clearing
|
|
610
|
+
const attachments = hasAttachments ? [...this.pendingAttachments] : null;
|
|
611
|
+
|
|
612
|
+
// Add user message (with optional attachment thumbnails)
|
|
613
|
+
this.appendMessage('user', message || '(attached files)', { attachments });
|
|
614
|
+
sessionStore.addMessage(activeId, 'user', message || '(attached files)');
|
|
281
615
|
input.value = '';
|
|
282
616
|
input.style.height = 'auto';
|
|
617
|
+
this.clearAttachments();
|
|
283
618
|
|
|
284
619
|
this.isLoading = true;
|
|
285
620
|
this.updateUiState();
|
|
@@ -296,9 +631,9 @@ export class AgentsView extends Component {
|
|
|
296
631
|
|
|
297
632
|
try {
|
|
298
633
|
if (selectionType === 'agent') {
|
|
299
|
-
finalContent = await this.sendAgentMessage(agent, message, responseId);
|
|
634
|
+
finalContent = await this.sendAgentMessage(agent, message, responseId, attachments);
|
|
300
635
|
} else if (selectionType === 'llm') {
|
|
301
|
-
finalContent = await this.sendLlmMessage(llm, message, responseId);
|
|
636
|
+
finalContent = await this.sendLlmMessage(llm, message, responseId, attachments);
|
|
302
637
|
}
|
|
303
638
|
} catch (e) {
|
|
304
639
|
if (e.name === 'AbortError') {
|
|
@@ -313,14 +648,26 @@ export class AgentsView extends Component {
|
|
|
313
648
|
this.updateUiState();
|
|
314
649
|
input.focus();
|
|
315
650
|
}
|
|
651
|
+
|
|
652
|
+
// Persist assistant response
|
|
653
|
+
if (finalContent) {
|
|
654
|
+
sessionStore.addMessage(activeId, 'assistant', finalContent);
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// Re-render sidebar (title/ordering may have changed)
|
|
658
|
+
this.renderSessionList();
|
|
316
659
|
}
|
|
317
660
|
|
|
318
|
-
async sendAgentMessage(agent, message, responseId) {
|
|
661
|
+
async sendAgentMessage(agent, message, responseId, attachments) {
|
|
319
662
|
const inputVars = agent.inputVariables || ['message'];
|
|
320
663
|
const inputObj = {};
|
|
321
664
|
inputObj[inputVars[0] || 'message'] = message;
|
|
665
|
+
if (attachments) {
|
|
666
|
+
inputObj.attachments = attachments;
|
|
667
|
+
}
|
|
322
668
|
|
|
323
|
-
const
|
|
669
|
+
const activeId = sessionStore.getActiveId();
|
|
670
|
+
const res = await api.streamAgent(agent.name, inputObj, activeId, { signal: this.currentAbortController?.signal });
|
|
324
671
|
const reader = res.body.getReader();
|
|
325
672
|
const decoder = new TextDecoder();
|
|
326
673
|
|
|
@@ -377,6 +724,10 @@ export class AgentsView extends Component {
|
|
|
377
724
|
}
|
|
378
725
|
}
|
|
379
726
|
|
|
727
|
+
// Finalize any remaining thinking pill
|
|
728
|
+
const toolsDiv = bubble.querySelector('.tool-invocations');
|
|
729
|
+
this.finalizeThinkingPill(toolsDiv, thinkingState);
|
|
730
|
+
|
|
380
731
|
// If tools were called but no text content was produced, clear loading state
|
|
381
732
|
if (hasToolCalls && !currentContent.trim()) {
|
|
382
733
|
const loadingDots = contentDiv.querySelector('.loading-dots');
|
|
@@ -392,8 +743,9 @@ export class AgentsView extends Component {
|
|
|
392
743
|
return currentContent;
|
|
393
744
|
}
|
|
394
745
|
|
|
395
|
-
async sendLlmMessage(llm, message, responseId) {
|
|
396
|
-
const
|
|
746
|
+
async sendLlmMessage(llm, message, responseId, attachments) {
|
|
747
|
+
const activeId = sessionStore.getActiveId();
|
|
748
|
+
const res = await api.streamLLM(llm.name, message, activeId, attachments, { signal: this.currentAbortController?.signal });
|
|
397
749
|
const reader = res.body.getReader();
|
|
398
750
|
const decoder = new TextDecoder();
|
|
399
751
|
|
|
@@ -471,153 +823,82 @@ export class AgentsView extends Component {
|
|
|
471
823
|
}
|
|
472
824
|
|
|
473
825
|
renderLlmContentStreaming(contentDiv, fullContent, responseId, state) {
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
}
|
|
488
|
-
break;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Add text before [THINK]
|
|
492
|
-
if (thinkStart > pos) {
|
|
493
|
-
const text = fullContent.slice(pos, thinkStart).trim();
|
|
494
|
-
if (text) {
|
|
495
|
-
parts.push({ type: 'text', content: text });
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
// Find the end of this think section
|
|
500
|
-
const thinkContentStart = thinkStart + 7; // After [THINK]
|
|
501
|
-
const thinkEnd = fullContent.indexOf('[/THINK]', thinkContentStart);
|
|
826
|
+
const existing = contentDiv.querySelector('.content-text');
|
|
827
|
+
if (existing) {
|
|
828
|
+
const renderedHtml = markdownRenderer.render(fullContent);
|
|
829
|
+
existing.innerHTML = renderedHtml;
|
|
830
|
+
markdownRenderer.highlightCode(existing);
|
|
831
|
+
} else {
|
|
832
|
+
const div = document.createElement('div');
|
|
833
|
+
div.className = 'content-text markdown-content';
|
|
834
|
+
div.innerHTML = markdownRenderer.render(fullContent);
|
|
835
|
+
markdownRenderer.highlightCode(div);
|
|
836
|
+
contentDiv.appendChild(div);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
502
839
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
840
|
+
handleThinkingEvent(event, toolsDiv, thinkingState, container) {
|
|
841
|
+
if (!thinkingState.thinkingContent) {
|
|
842
|
+
thinkingState.thinkingContent = '';
|
|
843
|
+
}
|
|
844
|
+
thinkingState.thinkingContent += event.content;
|
|
845
|
+
|
|
846
|
+
// Create pill on first thinking chunk
|
|
847
|
+
if (!thinkingState.thinkingPill) {
|
|
848
|
+
const pill = document.createElement('div');
|
|
849
|
+
pill.className = 'thinking-pill tool-pill inline-flex items-center gap-1.5 bg-dark-bg/50 border border-dark-border/60 rounded-full px-2.5 py-1 text-xs text-purple-400 font-mono';
|
|
850
|
+
pill.innerHTML = `
|
|
851
|
+
<i class="fas fa-brain animate-pulse text-[10px]"></i>
|
|
852
|
+
<span>Thinking...</span>
|
|
853
|
+
`;
|
|
854
|
+
toolsDiv.appendChild(pill);
|
|
855
|
+
thinkingState.thinkingPill = pill;
|
|
856
|
+
container.scrollTop = container.scrollHeight;
|
|
516
857
|
}
|
|
858
|
+
}
|
|
517
859
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
for (let i = 0; i < parts.length; i++) {
|
|
522
|
-
const part = parts[i];
|
|
523
|
-
const existingChild = contentDiv.children[currentChildIndex];
|
|
524
|
-
|
|
525
|
-
if (part.type === 'text') {
|
|
526
|
-
if (existingChild && existingChild.classList.contains('content-text')) {
|
|
527
|
-
// Update existing text element with markdown
|
|
528
|
-
const renderedHtml = markdownRenderer.render(part.content);
|
|
529
|
-
existingChild.innerHTML = renderedHtml;
|
|
530
|
-
markdownRenderer.highlightCode(existingChild);
|
|
531
|
-
} else {
|
|
532
|
-
// Create new text element
|
|
533
|
-
const div = document.createElement('div');
|
|
534
|
-
div.className = 'content-text markdown-content';
|
|
535
|
-
const renderedHtml = markdownRenderer.render(part.content);
|
|
536
|
-
div.innerHTML = renderedHtml;
|
|
537
|
-
markdownRenderer.highlightCode(div);
|
|
538
|
-
if (existingChild) {
|
|
539
|
-
contentDiv.insertBefore(div, existingChild);
|
|
540
|
-
} else {
|
|
541
|
-
contentDiv.appendChild(div);
|
|
542
|
-
}
|
|
543
|
-
}
|
|
544
|
-
currentChildIndex++;
|
|
545
|
-
} else if (part.type === 'think') {
|
|
546
|
-
const thinkId = `think-${responseId}-${part.index}`;
|
|
547
|
-
|
|
548
|
-
if (existingChild && existingChild.classList.contains('think-section')) {
|
|
549
|
-
// Update existing think section
|
|
550
|
-
const label = existingChild.querySelector('.think-label');
|
|
551
|
-
const content = existingChild.querySelector('.think-content');
|
|
552
|
-
if (label) {
|
|
553
|
-
label.textContent = part.complete ? 'Thinking' : 'Thinking...';
|
|
554
|
-
}
|
|
555
|
-
if (content) {
|
|
556
|
-
const renderedHtml = markdownRenderer.render(part.content);
|
|
557
|
-
content.innerHTML = renderedHtml;
|
|
558
|
-
content.classList.add('markdown-content');
|
|
559
|
-
markdownRenderer.highlightCode(content);
|
|
560
|
-
}
|
|
561
|
-
} else {
|
|
562
|
-
// Create new think section with event listener
|
|
563
|
-
const section = document.createElement('div');
|
|
564
|
-
section.className = 'think-section mb-3 border-l-2 border-blue-500/40 pl-3 py-1';
|
|
565
|
-
section.dataset.thinkIndex = part.index;
|
|
566
|
-
|
|
567
|
-
const toggle = document.createElement('button');
|
|
568
|
-
toggle.className = 'think-toggle flex items-center gap-1.5 text-xs text-blue-400 hover:text-blue-300 py-1 cursor-pointer';
|
|
569
|
-
toggle.dataset.thinkId = thinkId;
|
|
570
|
-
|
|
571
|
-
toggle.innerHTML = `
|
|
572
|
-
<i class="fas fa-brain text-xs"></i>
|
|
573
|
-
<span class="font-medium think-label">${part.complete ? 'Thinking' : 'Thinking...'}</span>
|
|
574
|
-
<i class="fas fa-chevron-right text-[10px] transition-transform think-chevron"></i>
|
|
575
|
-
`;
|
|
576
|
-
|
|
577
|
-
const thinkContent = document.createElement('div');
|
|
578
|
-
thinkContent.id = thinkId;
|
|
579
|
-
thinkContent.className = 'think-content hidden text-sm text-gray-400 markdown-content mt-1 leading-relaxed';
|
|
580
|
-
const renderedHtml = markdownRenderer.render(part.content);
|
|
581
|
-
thinkContent.innerHTML = renderedHtml;
|
|
582
|
-
markdownRenderer.highlightCode(thinkContent);
|
|
583
|
-
|
|
584
|
-
// Add click handler ONCE when creating
|
|
585
|
-
toggle.addEventListener('click', (e) => {
|
|
586
|
-
e.preventDefault();
|
|
587
|
-
e.stopPropagation();
|
|
588
|
-
const content = section.querySelector('.think-content');
|
|
589
|
-
const chevron = section.querySelector('.think-chevron');
|
|
590
|
-
|
|
591
|
-
if (content && chevron) {
|
|
592
|
-
if (content.classList.contains('hidden')) {
|
|
593
|
-
content.classList.remove('hidden');
|
|
594
|
-
chevron.classList.remove('fa-chevron-right');
|
|
595
|
-
chevron.classList.add('fa-chevron-down');
|
|
596
|
-
} else {
|
|
597
|
-
content.classList.add('hidden');
|
|
598
|
-
chevron.classList.remove('fa-chevron-down');
|
|
599
|
-
chevron.classList.add('fa-chevron-right');
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
});
|
|
860
|
+
finalizeThinkingPill(toolsDiv, thinkingState) {
|
|
861
|
+
const pill = thinkingState.thinkingPill;
|
|
862
|
+
if (!pill) return;
|
|
603
863
|
|
|
604
|
-
|
|
605
|
-
|
|
864
|
+
const content = thinkingState.thinkingContent || '';
|
|
865
|
+
thinkingState.thinkingPill = null;
|
|
866
|
+
thinkingState.thinkingContent = '';
|
|
606
867
|
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
} else {
|
|
610
|
-
contentDiv.appendChild(section);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
currentChildIndex++;
|
|
614
|
-
}
|
|
615
|
-
}
|
|
868
|
+
pill.className = 'thinking-pill tool-pill relative inline-flex items-center gap-1.5 bg-dark-bg/30 border border-dark-border/50 rounded-full px-2.5 py-1 text-xs text-gray-500 font-mono cursor-pointer hover:bg-dark-bg/60 hover:border-dark-border transition-colors';
|
|
869
|
+
pill.innerHTML = '';
|
|
616
870
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
871
|
+
const pillContent = document.createElement('span');
|
|
872
|
+
pillContent.className = 'inline-flex items-center gap-1.5';
|
|
873
|
+
pillContent.innerHTML = `
|
|
874
|
+
<i class="fas fa-brain text-purple-400 text-[10px]"></i>
|
|
875
|
+
<span>Thinking</span>
|
|
876
|
+
`;
|
|
877
|
+
pill.appendChild(pillContent);
|
|
878
|
+
|
|
879
|
+
const popover = document.createElement('div');
|
|
880
|
+
popover.className = 'hidden fixed z-50 bg-dark-surface border border-dark-border rounded-lg shadow-xl w-[400px] max-w-[90vw] p-3';
|
|
881
|
+
|
|
882
|
+
const popoverContent = document.createElement('div');
|
|
883
|
+
popoverContent.className = 'text-xs text-gray-400 max-h-64 overflow-y-auto markdown-content custom-scrollbar';
|
|
884
|
+
popoverContent.innerHTML = markdownRenderer.render(content);
|
|
885
|
+
markdownRenderer.highlightCode(popoverContent);
|
|
886
|
+
popover.appendChild(popoverContent);
|
|
887
|
+
pill.appendChild(popover);
|
|
888
|
+
|
|
889
|
+
pill.addEventListener('mouseenter', () => {
|
|
890
|
+
popover.classList.remove('hidden');
|
|
891
|
+
const pillRect = pill.getBoundingClientRect();
|
|
892
|
+
popover.style.bottom = (window.innerHeight - pillRect.top + 4) + 'px';
|
|
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'));
|
|
621
902
|
}
|
|
622
903
|
|
|
623
904
|
createResponseBubble(id) {
|
|
@@ -643,7 +924,6 @@ export class AgentsView extends Component {
|
|
|
643
924
|
|
|
644
925
|
wrapper.appendChild(div);
|
|
645
926
|
|
|
646
|
-
// Stream status bar (visible during streaming)
|
|
647
927
|
const statusBar = document.createElement('div');
|
|
648
928
|
statusBar.className = 'stream-status-bar flex items-center gap-2 mt-1.5 ml-1 text-xs text-gray-400';
|
|
649
929
|
statusBar.innerHTML = `
|
|
@@ -656,10 +936,8 @@ export class AgentsView extends Component {
|
|
|
656
936
|
`;
|
|
657
937
|
wrapper.appendChild(statusBar);
|
|
658
938
|
|
|
659
|
-
// Wire up cancel button
|
|
660
939
|
statusBar.querySelector('.stream-cancel-btn').addEventListener('click', () => this.cancelCurrentStream());
|
|
661
940
|
|
|
662
|
-
// Stats bar (visible after completion)
|
|
663
941
|
const statsBar = document.createElement('div');
|
|
664
942
|
statsBar.className = 'stream-stats-bar hidden flex items-center gap-3 mt-1.5 ml-1 text-xs text-gray-500';
|
|
665
943
|
statsBar.innerHTML = `
|
|
@@ -698,7 +976,11 @@ export class AgentsView extends Component {
|
|
|
698
976
|
const loadingDots = contentDiv.querySelector('.loading-dots');
|
|
699
977
|
const container = this.querySelector('#chatMessages');
|
|
700
978
|
|
|
701
|
-
if (event.type === '
|
|
979
|
+
if (event.type === 'thinking') {
|
|
980
|
+
this.handleThinkingEvent(event, toolsDiv, thinkingState, container);
|
|
981
|
+
} else if (event.type === 'content') {
|
|
982
|
+
// Finalize any in-progress thinking pill
|
|
983
|
+
this.finalizeThinkingPill(toolsDiv, thinkingState);
|
|
702
984
|
if (loadingDots) {
|
|
703
985
|
loadingDots.remove();
|
|
704
986
|
bubble.querySelector('.response-bubble-inner').classList.remove('py-4');
|
|
@@ -709,6 +991,7 @@ export class AgentsView extends Component {
|
|
|
709
991
|
this.renderLlmContentStreaming(contentDiv, currentContent, responseId, thinkingState);
|
|
710
992
|
container.scrollTop = container.scrollHeight;
|
|
711
993
|
} else if (event.type === 'tool_start') {
|
|
994
|
+
this.finalizeThinkingPill(toolsDiv, thinkingState);
|
|
712
995
|
const toolId = `tool-${event.runId}`;
|
|
713
996
|
const toolEl = document.createElement('div');
|
|
714
997
|
toolEl.id = toolId;
|
|
@@ -731,7 +1014,6 @@ export class AgentsView extends Component {
|
|
|
731
1014
|
toolEl.className = 'tool-pill relative inline-flex items-center gap-1.5 bg-dark-bg/30 border border-dark-border/50 rounded-full px-2.5 py-1 text-xs text-gray-500 font-mono cursor-pointer hover:bg-dark-bg/60 hover:border-dark-border transition-colors';
|
|
732
1015
|
toolEl.innerHTML = '';
|
|
733
1016
|
|
|
734
|
-
// Pill content (icon + name)
|
|
735
1017
|
const pillContent = document.createElement('span');
|
|
736
1018
|
pillContent.className = 'inline-flex items-center gap-1.5';
|
|
737
1019
|
pillContent.innerHTML = `
|
|
@@ -740,11 +1022,9 @@ export class AgentsView extends Component {
|
|
|
740
1022
|
`;
|
|
741
1023
|
toolEl.appendChild(pillContent);
|
|
742
1024
|
|
|
743
|
-
// Popover details panel (positioned below the pill)
|
|
744
1025
|
const details = document.createElement('div');
|
|
745
|
-
details.className = 'tool-invocation-details hidden absolute
|
|
1026
|
+
details.className = 'tool-invocation-details hidden absolute bottom-full mb-1 z-50 bg-dark-surface border border-dark-border rounded-lg shadow-xl w-[400px] max-w-[90vw]';
|
|
746
1027
|
|
|
747
|
-
// Input section
|
|
748
1028
|
if (toolInput) {
|
|
749
1029
|
const inputSection = document.createElement('div');
|
|
750
1030
|
inputSection.className = 'p-3 border-b border-dark-border/50';
|
|
@@ -756,7 +1036,6 @@ export class AgentsView extends Component {
|
|
|
756
1036
|
details.appendChild(inputSection);
|
|
757
1037
|
}
|
|
758
1038
|
|
|
759
|
-
// Output section
|
|
760
1039
|
const outputSection = document.createElement('div');
|
|
761
1040
|
outputSection.className = 'p-3';
|
|
762
1041
|
outputSection.innerHTML = `<div class="text-xs font-semibold text-gray-400 mb-1">Output</div>`;
|
|
@@ -768,18 +1047,29 @@ export class AgentsView extends Component {
|
|
|
768
1047
|
|
|
769
1048
|
toolEl.appendChild(details);
|
|
770
1049
|
|
|
771
|
-
// Toggle popover on click
|
|
772
1050
|
toolEl.addEventListener('click', (e) => {
|
|
1051
|
+
if (details.contains(e.target)) return;
|
|
773
1052
|
e.preventDefault();
|
|
774
1053
|
e.stopPropagation();
|
|
775
|
-
// Close any other open popovers
|
|
776
1054
|
toolsDiv.querySelectorAll('.tool-invocation-details:not(.hidden)').forEach(d => {
|
|
777
1055
|
if (d !== details) d.classList.add('hidden');
|
|
778
1056
|
});
|
|
1057
|
+
const wasHidden = details.classList.contains('hidden');
|
|
779
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
|
+
}
|
|
780
1071
|
});
|
|
781
1072
|
|
|
782
|
-
// Close popover when clicking outside
|
|
783
1073
|
const closeHandler = (e) => {
|
|
784
1074
|
if (!toolEl.contains(e.target)) {
|
|
785
1075
|
details.classList.add('hidden');
|
|
@@ -787,6 +1077,13 @@ export class AgentsView extends Component {
|
|
|
787
1077
|
};
|
|
788
1078
|
document.addEventListener('click', closeHandler, { capture: true });
|
|
789
1079
|
container.scrollTop = container.scrollHeight;
|
|
1080
|
+
|
|
1081
|
+
if (event.tool === 'workspace_write' || event.tool === 'workspace_delete') {
|
|
1082
|
+
try {
|
|
1083
|
+
const result = JSON.parse(typeof event.output === 'string' ? event.output : JSON.stringify(event.output));
|
|
1084
|
+
if (result.success && (result.reloaded === 'agent' || result.unloaded === 'agent')) this.loadAgents();
|
|
1085
|
+
} catch { /* ignore parse errors */ }
|
|
1086
|
+
}
|
|
790
1087
|
}
|
|
791
1088
|
} else if (event.type === 'result') {
|
|
792
1089
|
if (loadingDots) {
|
|
@@ -797,7 +1094,6 @@ export class AgentsView extends Component {
|
|
|
797
1094
|
contentDiv.innerHTML = '';
|
|
798
1095
|
}
|
|
799
1096
|
|
|
800
|
-
// Display structured output as formatted JSON
|
|
801
1097
|
const resultContainer = document.createElement('div');
|
|
802
1098
|
resultContainer.className = 'bg-dark-bg/50 border border-dark-border rounded-lg p-4';
|
|
803
1099
|
|
|
@@ -808,7 +1104,6 @@ export class AgentsView extends Component {
|
|
|
808
1104
|
resultContainer.appendChild(resultPre);
|
|
809
1105
|
contentDiv.appendChild(resultContainer);
|
|
810
1106
|
|
|
811
|
-
// Scroll to bottom
|
|
812
1107
|
container.scrollTop = container.scrollHeight;
|
|
813
1108
|
} else if (event.type === 'error') {
|
|
814
1109
|
if (loadingDots) {
|
|
@@ -828,6 +1123,13 @@ export class AgentsView extends Component {
|
|
|
828
1123
|
output_tokens: event.output_tokens || 0,
|
|
829
1124
|
total_tokens: event.total_tokens || 0,
|
|
830
1125
|
};
|
|
1126
|
+
} else if (event.type === 'react_iteration') {
|
|
1127
|
+
const wrapper = bubble.closest('.response-wrapper');
|
|
1128
|
+
const statusText = wrapper?.querySelector('.stream-status-text');
|
|
1129
|
+
if (statusText) {
|
|
1130
|
+
const contextKB = (event.contextChars / 1024).toFixed(1);
|
|
1131
|
+
statusText.textContent = `Iteration ${event.iteration} · ${contextKB} KB context`;
|
|
1132
|
+
}
|
|
831
1133
|
}
|
|
832
1134
|
}
|
|
833
1135
|
|
|
@@ -842,8 +1144,13 @@ export class AgentsView extends Component {
|
|
|
842
1144
|
updateUiState() {
|
|
843
1145
|
const btn = this.querySelector('#sendMessageBtn');
|
|
844
1146
|
const input = this.querySelector('#chatInput');
|
|
845
|
-
|
|
846
|
-
if (
|
|
1147
|
+
const hasActiveSession = !!sessionStore.getActiveId();
|
|
1148
|
+
if (btn) btn.disabled = this.isLoading || !hasActiveSession;
|
|
1149
|
+
if (input) {
|
|
1150
|
+
input.disabled = this.isLoading;
|
|
1151
|
+
input.readOnly = !hasActiveSession;
|
|
1152
|
+
input.classList.toggle('cursor-pointer', !hasActiveSession);
|
|
1153
|
+
}
|
|
847
1154
|
}
|
|
848
1155
|
|
|
849
1156
|
appendMessage(role, content, metadata = {}) {
|
|
@@ -857,8 +1164,24 @@ export class AgentsView extends Component {
|
|
|
857
1164
|
const bubbleColor = isUser ? 'bg-dark-surface' : (hasError ? 'bg-red-900/20 border-red-900/30' : 'bg-dark-surface');
|
|
858
1165
|
const textColor = hasError ? 'text-red-300' : 'text-gray-100';
|
|
859
1166
|
|
|
1167
|
+
// Build attachment thumbnails for user messages
|
|
1168
|
+
let attachmentHtml = '';
|
|
1169
|
+
if (isUser && metadata.attachments && metadata.attachments.length > 0) {
|
|
1170
|
+
const thumbs = metadata.attachments.map(att => {
|
|
1171
|
+
if (att.mediaType.startsWith('image/')) {
|
|
1172
|
+
return `<img src="data:${att.mediaType};base64,${att.data}" class="w-16 h-16 object-cover rounded-lg border border-dark-border/50">`;
|
|
1173
|
+
}
|
|
1174
|
+
return `<div class="flex items-center gap-1.5 bg-dark-bg/60 border border-dark-border/50 rounded-lg px-2 py-1.5 text-xs text-gray-400">
|
|
1175
|
+
<i class="fas fa-file"></i>
|
|
1176
|
+
<span class="max-w-[100px] truncate">${this.escapeHtml(att.name)}</span>
|
|
1177
|
+
</div>`;
|
|
1178
|
+
}).join('');
|
|
1179
|
+
attachmentHtml = `<div class="flex flex-wrap gap-2 mb-2">${thumbs}</div>`;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
860
1182
|
div.innerHTML = `
|
|
861
1183
|
<div class="max-w-4xl ${bubbleColor} border ${isUser ? 'border-transparent' : 'border-dark-border'} rounded-3xl px-5 py-3 ${textColor} text-[15px] leading-relaxed relative group">
|
|
1184
|
+
${attachmentHtml}
|
|
862
1185
|
<div class="whitespace-pre-wrap">${this.escapeHtml(content)}</div>
|
|
863
1186
|
${!isUser && !hasError ? `
|
|
864
1187
|
<button class="copy-btn absolute -bottom-6 left-0 text-gray-500 hover:text-gray-300 opacity-0 group-hover:opacity-100 transition-opacity p-1" title="Copy">
|
|
@@ -912,30 +1235,116 @@ export class AgentsView extends Component {
|
|
|
912
1235
|
return div.innerHTML;
|
|
913
1236
|
}
|
|
914
1237
|
|
|
915
|
-
|
|
916
|
-
const
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
1238
|
+
_getRandomWelcomeMessage() {
|
|
1239
|
+
const messages = [
|
|
1240
|
+
'Awaiting your command, master.',
|
|
1241
|
+
'The agents are restless. Give them purpose.',
|
|
1242
|
+
'Ready when you are. No pressure... okay, maybe a little.',
|
|
1243
|
+
'Spinning up neurons... just kidding, I was already ready.',
|
|
1244
|
+
'All systems nominal. Your move, human.',
|
|
1245
|
+
'The orchestrator awaits. What shall we build today?',
|
|
1246
|
+
'Standing by. The agents are stretching their digital legs.',
|
|
1247
|
+
'Another day, another chance to orchestrate greatness.',
|
|
1248
|
+
'Agents assembled. Awaiting mission briefing.',
|
|
1249
|
+
'The stage is set. You are the conductor.',
|
|
1250
|
+
];
|
|
1251
|
+
return messages[Math.floor(Math.random() * messages.length)];
|
|
1252
|
+
}
|
|
921
1253
|
|
|
922
|
-
|
|
1254
|
+
_appendWelcomeMessage(container) {
|
|
923
1255
|
const div = document.createElement('div');
|
|
924
|
-
div.className = '
|
|
1256
|
+
div.className = 'welcome-container';
|
|
925
1257
|
div.innerHTML = `
|
|
926
|
-
<
|
|
927
|
-
|
|
928
|
-
|
|
1258
|
+
<svg class="welcome-orca" viewBox="0 0 220 140" xmlns="http://www.w3.org/2000/svg">
|
|
1259
|
+
<!-- Main body -->
|
|
1260
|
+
<path class="orca-body" d="
|
|
1261
|
+
M 30,68
|
|
1262
|
+
C 28,58 38,42 58,38
|
|
1263
|
+
C 68,35 74,30 78,18
|
|
1264
|
+
C 80,12 84,12 85,18
|
|
1265
|
+
C 87,28 86,35 92,38
|
|
1266
|
+
C 112,34 148,40 172,54
|
|
1267
|
+
C 176,50 182,44 188,40
|
|
1268
|
+
C 192,38 194,42 190,46
|
|
1269
|
+
C 186,50 184,54 182,56
|
|
1270
|
+
C 186,60 188,66 184,68
|
|
1271
|
+
C 180,70 178,66 176,62
|
|
1272
|
+
C 168,72 142,78 112,76
|
|
1273
|
+
C 82,74 52,70 38,66
|
|
1274
|
+
C 34,64 30,68 30,68 Z
|
|
1275
|
+
"/>
|
|
1276
|
+
<!-- Dorsal fin accent line -->
|
|
1277
|
+
<path class="orca-detail" d="M 72,38 C 74,28 78,18 80,14"/>
|
|
1278
|
+
<!-- Belly line -->
|
|
1279
|
+
<path class="orca-detail" d="M 42,64 C 62,70 100,74 140,72 C 158,70 170,66 176,62"/>
|
|
1280
|
+
<!-- Saddle patch -->
|
|
1281
|
+
<path class="orca-patch" d="M 92,40 C 102,38 112,40 108,48 C 104,54 90,50 92,40 Z"/>
|
|
1282
|
+
<!-- Eye patch -->
|
|
1283
|
+
<path class="orca-patch" d="M 44,52 C 50,48 60,50 56,58 C 52,62 42,58 44,52 Z"/>
|
|
1284
|
+
<!-- Eye -->
|
|
1285
|
+
<circle class="orca-eye" cx="42" cy="54" r="2.5"/>
|
|
1286
|
+
<!-- Pectoral fin -->
|
|
1287
|
+
<path class="orca-body" d="M 65,66 C 70,74 64,82 58,76 C 54,72 60,66 65,66 Z"/>
|
|
1288
|
+
<!-- Tail detail -->
|
|
1289
|
+
<path class="orca-detail" d="M 172,54 C 176,52 180,48 184,44"/>
|
|
1290
|
+
<path class="orca-detail" d="M 176,62 C 180,64 184,66 186,64"/>
|
|
1291
|
+
</svg>
|
|
1292
|
+
<div class="welcome-text">${this._getRandomWelcomeMessage()}</div>
|
|
1293
|
+
${this._renderSampleQuestionChips()}
|
|
929
1294
|
`;
|
|
930
1295
|
container.appendChild(div);
|
|
931
1296
|
|
|
932
|
-
|
|
933
|
-
|
|
1297
|
+
div.querySelectorAll('.sample-question-chip').forEach(chip => {
|
|
1298
|
+
chip.addEventListener('click', () => {
|
|
1299
|
+
const input = this.querySelector('#chatInput');
|
|
1300
|
+
if (input) {
|
|
1301
|
+
input.value = chip.textContent;
|
|
1302
|
+
input.focus();
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
});
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
_renderSampleQuestionChips() {
|
|
1309
|
+
const agent = store.get('selectedAgent');
|
|
1310
|
+
const questions = agent?.sampleQuestions;
|
|
1311
|
+
if (!questions || questions.length === 0) return '';
|
|
1312
|
+
|
|
1313
|
+
const chips = questions.map(q =>
|
|
1314
|
+
`<button class="sample-question-chip bg-dark-surface border border-dark-border/60 hover:border-gray-500 text-gray-300 text-sm px-4 py-2 rounded-2xl transition-colors text-left">${this.escapeHtml(q)}</button>`
|
|
1315
|
+
).join('');
|
|
1316
|
+
|
|
1317
|
+
return `
|
|
1318
|
+
<div class="flex flex-wrap justify-center gap-2 max-w-2xl mt-4">${chips}</div>
|
|
1319
|
+
`;
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
_appendSessionResetBanner(container) {
|
|
1323
|
+
const div = document.createElement('div');
|
|
1324
|
+
div.className = 'session-reset-banner';
|
|
1325
|
+
div.innerHTML = `
|
|
1326
|
+
<div class="session-reset-line"></div>
|
|
1327
|
+
<span class="session-reset-label">
|
|
1328
|
+
<i class="fas fa-rotate-right"></i>
|
|
1329
|
+
Server restarted — new session
|
|
1330
|
+
</span>
|
|
1331
|
+
<div class="session-reset-line"></div>
|
|
1332
|
+
`;
|
|
1333
|
+
container.appendChild(div);
|
|
1334
|
+
container.scrollTop = container.scrollHeight;
|
|
934
1335
|
}
|
|
935
1336
|
|
|
936
1337
|
postRender() {
|
|
937
1338
|
const input = this.querySelector('#chatInput');
|
|
938
1339
|
|
|
1340
|
+
// Open new conversation modal when clicking input with no active session
|
|
1341
|
+
input.addEventListener('mousedown', (e) => {
|
|
1342
|
+
if (!sessionStore.getActiveId()) {
|
|
1343
|
+
e.preventDefault();
|
|
1344
|
+
this.showNewSessionModal();
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1347
|
+
|
|
939
1348
|
// Auto-resize
|
|
940
1349
|
input.addEventListener('input', () => {
|
|
941
1350
|
input.style.height = 'auto';
|
|
@@ -952,60 +1361,74 @@ export class AgentsView extends Component {
|
|
|
952
1361
|
|
|
953
1362
|
this.querySelector('#sendMessageBtn').addEventListener('click', () => this.sendMessage());
|
|
954
1363
|
|
|
955
|
-
//
|
|
956
|
-
|
|
957
|
-
|
|
1364
|
+
// Attach button + file input
|
|
1365
|
+
this.querySelector('#attachBtn').addEventListener('click', () => this.querySelector('#fileInput').click());
|
|
1366
|
+
this.querySelector('#fileInput').addEventListener('change', (e) => this.handleFileSelect(e));
|
|
958
1367
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
this.toggleDropdown();
|
|
962
|
-
});
|
|
1368
|
+
// New chat button
|
|
1369
|
+
this.querySelector('#newChatBtn').addEventListener('click', () => this.showNewSessionModal());
|
|
963
1370
|
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
}
|
|
968
|
-
});
|
|
1371
|
+
// Mobile sidebar toggle
|
|
1372
|
+
this.querySelector('#sidebarToggleBtn').addEventListener('click', () => this.toggleSidebar(true));
|
|
1373
|
+
this.querySelector('#sidebarBackdrop').addEventListener('click', () => this.toggleSidebar(false));
|
|
969
1374
|
}
|
|
970
1375
|
|
|
971
1376
|
template() {
|
|
972
1377
|
return `
|
|
973
|
-
<div class="flex
|
|
974
|
-
<!--
|
|
975
|
-
<div id="
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
1378
|
+
<div class="flex h-full relative border border-dark-border rounded-xl overflow-hidden bg-dark-surface/30">
|
|
1379
|
+
<!-- Mobile sidebar backdrop -->
|
|
1380
|
+
<div id="sidebarBackdrop" class="hidden fixed inset-0 bg-black/50 z-30 md:hidden"></div>
|
|
1381
|
+
|
|
1382
|
+
<!-- Sidebar -->
|
|
1383
|
+
<div id="sidebar" class="hidden md:flex w-64 flex-shrink-0 bg-dark-bg md:bg-dark-bg/60 border-r border-dark-border/60 flex-col
|
|
1384
|
+
fixed md:relative inset-y-0 left-0 z-40 md:z-auto">
|
|
1385
|
+
<div class="p-3">
|
|
1386
|
+
<button id="newChatBtn" class="w-full flex items-center justify-center gap-2 px-3 py-2.5 hover:bg-dark-hover rounded-lg text-sm font-medium text-gray-300 transition-colors">
|
|
1387
|
+
<i class="fas fa-plus text-xs text-blue-400"></i>
|
|
1388
|
+
<span>New chat</span>
|
|
1389
|
+
</button>
|
|
980
1390
|
</div>
|
|
1391
|
+
<div id="sessionList" class="flex-1 overflow-y-auto custom-scrollbar px-2 pb-2"></div>
|
|
981
1392
|
</div>
|
|
982
1393
|
|
|
983
|
-
<!--
|
|
984
|
-
<div class="
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
<div class="
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
<span id="selectedAgentName">Select Agent/LLM</span>
|
|
995
|
-
<i class="fas fa-chevron-down text-xs text-gray-400"></i>
|
|
996
|
-
</button>
|
|
1394
|
+
<!-- Chat Area -->
|
|
1395
|
+
<div class="flex-1 flex flex-col min-w-0">
|
|
1396
|
+
<!-- Chat Header -->
|
|
1397
|
+
<div class="flex-shrink-0 flex items-center gap-2 px-3 md:px-5 py-3 border-b border-dark-border/40 text-sm">
|
|
1398
|
+
<button id="sidebarToggleBtn" class="md:hidden text-gray-400 hover:text-gray-200 p-1 -ml-1">
|
|
1399
|
+
<i class="fas fa-bars"></i>
|
|
1400
|
+
</button>
|
|
1401
|
+
<div id="chatHeader" class="flex-1 min-w-0">
|
|
1402
|
+
<span class="text-gray-500">No conversation selected</span>
|
|
1403
|
+
</div>
|
|
1404
|
+
</div>
|
|
997
1405
|
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1406
|
+
<!-- Chat Messages -->
|
|
1407
|
+
<div id="chatMessages" class="flex-1 overflow-y-auto space-y-4 p-4 pr-2 pb-6 custom-scrollbar"></div>
|
|
1408
|
+
|
|
1409
|
+
<!-- Input Area -->
|
|
1410
|
+
<div class="p-3 pt-0">
|
|
1411
|
+
<div id="attachmentPreview" class="hidden flex flex-wrap gap-2 px-2 pb-2"></div>
|
|
1412
|
+
<div class="relative bg-dark-surface border border-dark-border/60 rounded-2xl focus-within:border-gray-500 transition-colors">
|
|
1413
|
+
<input type="file" id="fileInput" multiple accept="image/*,.pdf" class="hidden">
|
|
1414
|
+
<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
|
+
placeholder="Ask anything"></textarea>
|
|
1417
|
+
|
|
1418
|
+
<div class="absolute bottom-2 left-2 flex items-center">
|
|
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">
|
|
1422
|
+
<i class="fas fa-plus text-sm"></i>
|
|
1423
|
+
</button>
|
|
1003
1424
|
</div>
|
|
1004
1425
|
|
|
1005
|
-
<
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1426
|
+
<div class="absolute bottom-2 right-2 flex items-center gap-2">
|
|
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">
|
|
1429
|
+
<i class="fas fa-paper-plane text-sm"></i>
|
|
1430
|
+
</button>
|
|
1431
|
+
</div>
|
|
1009
1432
|
</div>
|
|
1010
1433
|
</div>
|
|
1011
1434
|
</div>
|