@agent-link/server 0.1.132 → 0.1.134
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/package.json +1 -1
- package/web/app.js +469 -3
- package/web/modules/connection.js +29 -5
- package/web/modules/markdown.js +1 -0
- package/web/modules/messageHelpers.js +4 -3
- package/web/modules/team.js +342 -0
- package/web/style.css +1281 -133
package/package.json
CHANGED
package/web/app.js
CHANGED
|
@@ -18,6 +18,7 @@ import { createSidebar } from './modules/sidebar.js';
|
|
|
18
18
|
import { createConnection } from './modules/connection.js';
|
|
19
19
|
import { createFileBrowser } from './modules/fileBrowser.js';
|
|
20
20
|
import { createFilePreview } from './modules/filePreview.js';
|
|
21
|
+
import { createTeam } from './modules/team.js';
|
|
21
22
|
|
|
22
23
|
// ── App ─────────────────────────────────────────────────────────────────────
|
|
23
24
|
const App = {
|
|
@@ -110,6 +111,28 @@ const App = {
|
|
|
110
111
|
const isMobile = ref(window.innerWidth <= 768);
|
|
111
112
|
const workdirMenuOpen = ref(false);
|
|
112
113
|
|
|
114
|
+
// Team creation state
|
|
115
|
+
const teamInstruction = ref('');
|
|
116
|
+
const teamExamples = [
|
|
117
|
+
{
|
|
118
|
+
icon: '<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M12 7V3H2v18h20V7H12zM6 19H4v-2h2v2zm0-4H4v-2h2v2zm0-4H4V9h2v2zm0-4H4V5h2v2zm4 12H8v-2h2v2zm0-4H8v-2h2v2zm0-4H8V9h2v2zm0-4H8V5h2v2zm10 12h-8v-2h2v-2h-2v-2h2v-2h-2V9h8v10zm-2-8h-2v2h2v-2zm0 4h-2v2h2v-2z"/></svg>',
|
|
119
|
+
title: 'Full-stack App',
|
|
120
|
+
text: 'Build a single-page calculator app: one agent creates the HTML/CSS UI, one implements the JavaScript logic, and one writes tests.',
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
icon: '<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>',
|
|
124
|
+
title: 'Code Review',
|
|
125
|
+
text: 'Review this project for code quality, security vulnerabilities, and test coverage. Generate a prioritized report for each area.',
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
icon: '<svg viewBox="0 0 24 24" width="20" height="20"><path fill="currentColor" d="M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z"/></svg>',
|
|
129
|
+
title: 'Doc + Dev + Test',
|
|
130
|
+
text: '创建一个 Markdown 转 HTML 的在线预览工具:一个 Agent 编写 Design文档(左右分栏,左侧输入 Markdown,右侧实时预览),一个基于设计文档实现核心功能,一个等实现完的设计测试并执行同时给出测试结果。',
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
const kanbanExpanded = ref(false);
|
|
134
|
+
const instructionExpanded = ref(false);
|
|
135
|
+
|
|
113
136
|
// File preview state
|
|
114
137
|
const previewPanelOpen = ref(false);
|
|
115
138
|
const previewPanelWidth = ref(parseInt(localStorage.getItem('agentlink-preview-panel-width'), 10) || 400);
|
|
@@ -259,7 +282,7 @@ const App = {
|
|
|
259
282
|
switchConversation,
|
|
260
283
|
});
|
|
261
284
|
|
|
262
|
-
const { connect, wsSend, closeWs, submitPassword, setDequeueNext, setFileBrowser, setFilePreview, getToolMsgMap, restoreToolMsgMap, clearToolMsgMap } = createConnection({
|
|
285
|
+
const { connect, wsSend, closeWs, submitPassword, setDequeueNext, setFileBrowser, setFilePreview, setTeam, getToolMsgMap, restoreToolMsgMap, clearToolMsgMap } = createConnection({
|
|
263
286
|
status, agentName, hostname, workDir, sessionId, error,
|
|
264
287
|
serverVersion, agentVersion, latency,
|
|
265
288
|
messages, isProcessing, isCompacting, visibleLimit, queuedMessages, usageStats,
|
|
@@ -281,6 +304,12 @@ const App = {
|
|
|
281
304
|
_restoreToolMsgMap = restoreToolMsgMap;
|
|
282
305
|
_clearToolMsgMap = clearToolMsgMap;
|
|
283
306
|
|
|
307
|
+
// Team module
|
|
308
|
+
const team = createTeam({
|
|
309
|
+
wsSend, scrollToBottom,
|
|
310
|
+
});
|
|
311
|
+
setTeam(team);
|
|
312
|
+
|
|
284
313
|
// File browser module
|
|
285
314
|
const fileBrowser = createFileBrowser({
|
|
286
315
|
wsSend, workDir, inputText, inputRef, sendMessage,
|
|
@@ -543,6 +572,69 @@ const App = {
|
|
|
543
572
|
workdirMenuOpen.value = false;
|
|
544
573
|
navigator.clipboard.writeText(workDir.value);
|
|
545
574
|
},
|
|
575
|
+
// Team mode
|
|
576
|
+
team,
|
|
577
|
+
teamState: team.teamState,
|
|
578
|
+
teamMode: team.teamMode,
|
|
579
|
+
activeAgentView: team.activeAgentView,
|
|
580
|
+
historicalTeam: team.historicalTeam,
|
|
581
|
+
teamsList: team.teamsList,
|
|
582
|
+
isTeamActive: team.isTeamActive,
|
|
583
|
+
isTeamRunning: team.isTeamRunning,
|
|
584
|
+
displayTeam: team.displayTeam,
|
|
585
|
+
pendingTasks: team.pendingTasks,
|
|
586
|
+
activeTasks: team.activeTasks,
|
|
587
|
+
doneTasks: team.doneTasks,
|
|
588
|
+
failedTasks: team.failedTasks,
|
|
589
|
+
launchTeam: team.launchTeam,
|
|
590
|
+
dissolveTeam: team.dissolveTeam,
|
|
591
|
+
viewAgent: team.viewAgent,
|
|
592
|
+
viewDashboard: team.viewDashboard,
|
|
593
|
+
viewHistoricalTeam: team.viewHistoricalTeam,
|
|
594
|
+
requestTeamsList: team.requestTeamsList,
|
|
595
|
+
getAgentColor: team.getAgentColor,
|
|
596
|
+
findAgent: team.findAgent,
|
|
597
|
+
getAgentMessages: team.getAgentMessages,
|
|
598
|
+
backToChat: team.backToChat,
|
|
599
|
+
newTeam: team.newTeam,
|
|
600
|
+
teamInstruction,
|
|
601
|
+
teamExamples,
|
|
602
|
+
kanbanExpanded,
|
|
603
|
+
instructionExpanded,
|
|
604
|
+
launchTeamFromPanel() {
|
|
605
|
+
const inst = teamInstruction.value.trim();
|
|
606
|
+
if (!inst) return;
|
|
607
|
+
team.launchTeam(inst, 'custom');
|
|
608
|
+
teamInstruction.value = '';
|
|
609
|
+
},
|
|
610
|
+
formatTeamTime(ts) {
|
|
611
|
+
if (!ts) return '';
|
|
612
|
+
const d = new Date(ts);
|
|
613
|
+
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
614
|
+
},
|
|
615
|
+
getTaskAgent(task) {
|
|
616
|
+
if (!task.assignedTo) return null;
|
|
617
|
+
return team.findAgent(task.assignedTo);
|
|
618
|
+
},
|
|
619
|
+
viewAgentWithHistory(agentId) {
|
|
620
|
+
team.viewAgent(agentId);
|
|
621
|
+
// For historical teams, request agent conversation history from server
|
|
622
|
+
if (team.historicalTeam.value && team.historicalTeam.value.teamId) {
|
|
623
|
+
team.requestAgentHistory(team.historicalTeam.value.teamId, agentId);
|
|
624
|
+
}
|
|
625
|
+
},
|
|
626
|
+
getLatestAgentActivity(agentId) {
|
|
627
|
+
// Find the latest feed entry for this agent
|
|
628
|
+
const t = team.displayTeam.value;
|
|
629
|
+
if (!t || !t.feed) return '';
|
|
630
|
+
for (let i = t.feed.length - 1; i >= 0; i--) {
|
|
631
|
+
const entry = t.feed[i];
|
|
632
|
+
if (entry.agentId === agentId && entry.type === 'tool_call') {
|
|
633
|
+
return entry.content;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
return '';
|
|
637
|
+
},
|
|
546
638
|
};
|
|
547
639
|
},
|
|
548
640
|
template: `
|
|
@@ -558,6 +650,10 @@ const App = {
|
|
|
558
650
|
<span :class="['badge', status.toLowerCase()]">{{ status }}</span>
|
|
559
651
|
<span v-if="latency !== null && status === 'Connected'" class="latency" :class="{ good: latency < 100, ok: latency >= 100 && latency < 500, bad: latency >= 500 }">{{ latency }}ms</span>
|
|
560
652
|
<span v-if="agentName" class="agent-label">{{ agentName }}</span>
|
|
653
|
+
<div class="team-mode-toggle">
|
|
654
|
+
<button :class="['team-mode-btn', { active: teamMode === 'chat' }]" @click="teamMode = 'chat'">Chat</button>
|
|
655
|
+
<button :class="['team-mode-btn', { active: teamMode === 'team' }]" @click="teamMode = 'team'">Team</button>
|
|
656
|
+
</div>
|
|
561
657
|
<button class="theme-toggle" @click="toggleTheme" :title="theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'">
|
|
562
658
|
<svg v-if="theme === 'dark'" viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58a.996.996 0 0 0-1.41 0 .996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37a.996.996 0 0 0-1.41 0 .996.996 0 0 0 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0a.996.996 0 0 0 0-1.41l-1.06-1.06zm1.06-10.96a.996.996 0 0 0 0-1.41.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36a.996.996 0 0 0 0-1.41.996.996 0 0 0-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"/></svg>
|
|
563
659
|
<svg v-else viewBox="0 0 24 24" width="18" height="18"><path fill="currentColor" d="M12 3a9 9 0 1 0 9 9c0-.46-.04-.92-.1-1.36a5.389 5.389 0 0 1-4.4 2.26 5.403 5.403 0 0 1-3.14-9.8c-.44-.06-.9-.1-1.36-.1z"/></svg>
|
|
@@ -717,6 +813,31 @@ const App = {
|
|
|
717
813
|
</div>
|
|
718
814
|
</div>
|
|
719
815
|
|
|
816
|
+
<!-- Teams section -->
|
|
817
|
+
<div v-if="teamsList.length > 0" class="sidebar-section sidebar-teams">
|
|
818
|
+
<div class="sidebar-section-header">
|
|
819
|
+
<span>Teams</span>
|
|
820
|
+
</div>
|
|
821
|
+
<div class="team-history-list">
|
|
822
|
+
<div
|
|
823
|
+
v-for="t in teamsList" :key="t.teamId"
|
|
824
|
+
:class="['team-history-item', { active: displayTeam && displayTeam.teamId === t.teamId }]"
|
|
825
|
+
@click="viewHistoricalTeam(t.teamId)"
|
|
826
|
+
:title="t.title"
|
|
827
|
+
>
|
|
828
|
+
<svg class="team-history-icon" viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>
|
|
829
|
+
<div class="team-history-info">
|
|
830
|
+
<div class="team-history-title">{{ t.title || 'Untitled team' }}</div>
|
|
831
|
+
<div class="team-history-meta">
|
|
832
|
+
<span :class="['team-status-badge', 'team-status-badge-sm', 'team-status-' + t.status]">{{ t.status }}</span>
|
|
833
|
+
<span v-if="t.taskCount" class="team-history-tasks">{{ t.taskCount }} tasks</span>
|
|
834
|
+
<span v-if="t.totalCost" class="team-history-tasks">{{'$' + t.totalCost.toFixed(2) }}</span>
|
|
835
|
+
</div>
|
|
836
|
+
</div>
|
|
837
|
+
</div>
|
|
838
|
+
</div>
|
|
839
|
+
</div>
|
|
840
|
+
|
|
720
841
|
<div class="sidebar-section sidebar-sessions">
|
|
721
842
|
<div class="sidebar-section-header">
|
|
722
843
|
<span>History</span>
|
|
@@ -758,7 +879,10 @@ const App = {
|
|
|
758
879
|
<button class="session-rename-ok" @click.stop="confirmRename" title="Confirm">✓</button>
|
|
759
880
|
<button class="session-rename-cancel" @click.stop="cancelRename" title="Cancel">×</button>
|
|
760
881
|
</div>
|
|
761
|
-
<div v-else class="session-title">
|
|
882
|
+
<div v-else class="session-title">
|
|
883
|
+
<svg v-if="s.title && s.title.startsWith('You are a team lead')" class="session-team-icon" viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>
|
|
884
|
+
{{ s.title }}
|
|
885
|
+
</div>
|
|
762
886
|
<div class="session-meta">
|
|
763
887
|
<span>{{ formatRelativeTime(s.lastModified) }}</span>
|
|
764
888
|
<span v-if="renamingSessionId !== s.sessionId" class="session-actions">
|
|
@@ -837,6 +961,323 @@ const App = {
|
|
|
837
961
|
|
|
838
962
|
<!-- Chat area -->
|
|
839
963
|
<div class="chat-area">
|
|
964
|
+
|
|
965
|
+
<!-- ══ Team Dashboard ══ -->
|
|
966
|
+
<template v-if="teamMode === 'team'">
|
|
967
|
+
|
|
968
|
+
<!-- Team creation panel (no active team) -->
|
|
969
|
+
<div v-if="!displayTeam" class="team-create-panel">
|
|
970
|
+
<div class="team-create-inner">
|
|
971
|
+
<div class="team-create-header">
|
|
972
|
+
<svg viewBox="0 0 24 24" width="28" height="28"><path fill="currentColor" opacity="0.5" d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>
|
|
973
|
+
<h2>Launch Agent Team</h2>
|
|
974
|
+
</div>
|
|
975
|
+
<p class="team-create-desc">Describe your task and a lead agent will plan and delegate work across multiple parallel agents.</p>
|
|
976
|
+
<textarea
|
|
977
|
+
v-model="teamInstruction"
|
|
978
|
+
class="team-create-textarea"
|
|
979
|
+
placeholder="Describe the task for the team... e.g. 'Review the authentication module for security issues and fix any vulnerabilities found.'"
|
|
980
|
+
rows="4"
|
|
981
|
+
></textarea>
|
|
982
|
+
<div class="team-create-actions">
|
|
983
|
+
<button class="team-create-launch" :disabled="!teamInstruction.trim()" @click="launchTeamFromPanel()">
|
|
984
|
+
<svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
|
|
985
|
+
Launch Team
|
|
986
|
+
</button>
|
|
987
|
+
<button class="team-create-cancel" @click="backToChat()">Back to Chat</button>
|
|
988
|
+
</div>
|
|
989
|
+
|
|
990
|
+
<!-- Example instructions -->
|
|
991
|
+
<div class="team-examples-section">
|
|
992
|
+
<div class="team-examples-header">Examples</div>
|
|
993
|
+
<div class="team-examples-list">
|
|
994
|
+
<div class="team-example-card" v-for="(ex, i) in teamExamples" :key="i">
|
|
995
|
+
<div class="team-example-icon" v-html="ex.icon"></div>
|
|
996
|
+
<div class="team-example-body">
|
|
997
|
+
<div class="team-example-title">{{ ex.title }}</div>
|
|
998
|
+
<div class="team-example-text">{{ ex.text }}</div>
|
|
999
|
+
</div>
|
|
1000
|
+
<button class="team-example-try" @click="teamInstruction = ex.text">Try it</button>
|
|
1001
|
+
</div>
|
|
1002
|
+
</div>
|
|
1003
|
+
</div>
|
|
1004
|
+
|
|
1005
|
+
<!-- Historical teams -->
|
|
1006
|
+
<div v-if="teamsList.length > 0" class="team-history-section">
|
|
1007
|
+
<div class="team-history-section-header">Previous Teams</div>
|
|
1008
|
+
<div class="team-history-list">
|
|
1009
|
+
<div
|
|
1010
|
+
v-for="t in teamsList" :key="t.teamId"
|
|
1011
|
+
class="team-history-item"
|
|
1012
|
+
@click="viewHistoricalTeam(t.teamId)"
|
|
1013
|
+
:title="t.title"
|
|
1014
|
+
>
|
|
1015
|
+
<svg class="team-history-icon" viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M16 11c1.66 0 2.99-1.34 2.99-3S17.66 5 16 5c-1.66 0-3 1.34-3 3s1.34 3 3 3zm-8 0c1.66 0 2.99-1.34 2.99-3S9.66 5 8 5C6.34 5 5 6.34 5 8s1.34 3 3 3zm0 2c-2.33 0-7 1.17-7 3.5V19h14v-2.5c0-2.33-4.67-3.5-7-3.5zm8 0c-.29 0-.62.02-.97.05 1.16.84 1.97 1.97 1.97 3.45V19h6v-2.5c0-2.33-4.67-3.5-7-3.5z"/></svg>
|
|
1016
|
+
<div class="team-history-info">
|
|
1017
|
+
<div class="team-history-title">{{ t.title || 'Untitled team' }}</div>
|
|
1018
|
+
<div class="team-history-meta">
|
|
1019
|
+
<span :class="['team-status-badge', 'team-status-badge-sm', 'team-status-' + t.status]">{{ t.status }}</span>
|
|
1020
|
+
<span v-if="t.taskCount" class="team-history-tasks">{{ t.taskCount }} tasks</span>
|
|
1021
|
+
<span v-if="t.totalCost" class="team-history-tasks">{{'$' + t.totalCost.toFixed(2) }}</span>
|
|
1022
|
+
</div>
|
|
1023
|
+
</div>
|
|
1024
|
+
</div>
|
|
1025
|
+
</div>
|
|
1026
|
+
</div>
|
|
1027
|
+
</div>
|
|
1028
|
+
</div>
|
|
1029
|
+
|
|
1030
|
+
<!-- Active/historical team dashboard -->
|
|
1031
|
+
<div v-else class="team-dashboard">
|
|
1032
|
+
<!-- Dashboard header -->
|
|
1033
|
+
<div class="team-dash-header">
|
|
1034
|
+
<div class="team-dash-header-top">
|
|
1035
|
+
<span :class="['team-status-badge', 'team-status-' + displayTeam.status]">{{ displayTeam.status }}</span>
|
|
1036
|
+
<div class="team-dash-header-right">
|
|
1037
|
+
<button v-if="isTeamRunning" class="team-dissolve-btn" @click="dissolveTeam()">Dissolve Team</button>
|
|
1038
|
+
<button v-if="!isTeamActive" class="team-new-btn" @click="newTeam()">New Team</button>
|
|
1039
|
+
<button v-if="!isTeamActive" class="team-back-btn" @click="backToChat()">Back to Chat</button>
|
|
1040
|
+
</div>
|
|
1041
|
+
</div>
|
|
1042
|
+
<div class="team-dash-instruction" :class="{ expanded: instructionExpanded }">
|
|
1043
|
+
<div class="team-dash-instruction-text">{{ displayTeam.config?.instruction || displayTeam.title || 'Agent Team' }}</div>
|
|
1044
|
+
<button v-if="(displayTeam.config?.instruction || '').length > 120" class="team-dash-instruction-toggle" @click="instructionExpanded = !instructionExpanded">
|
|
1045
|
+
{{ instructionExpanded ? 'Show less' : 'Show more' }}
|
|
1046
|
+
</button>
|
|
1047
|
+
</div>
|
|
1048
|
+
</div>
|
|
1049
|
+
|
|
1050
|
+
<!-- Lead status bar (clickable to view lead detail) -->
|
|
1051
|
+
<div v-if="displayTeam.leadStatus && (displayTeam.status === 'planning' || displayTeam.status === 'running' || displayTeam.status === 'summarizing')" class="team-lead-bar team-lead-bar-clickable" @click="viewAgent('lead')">
|
|
1052
|
+
<span class="team-lead-dot"></span>
|
|
1053
|
+
<span class="team-lead-label">Lead</span>
|
|
1054
|
+
<span class="team-lead-text">{{ displayTeam.leadStatus }}</span>
|
|
1055
|
+
</div>
|
|
1056
|
+
|
|
1057
|
+
<!-- Dashboard body -->
|
|
1058
|
+
<div class="team-dash-body">
|
|
1059
|
+
|
|
1060
|
+
<!-- Main content: kanban + agents + feed (dashboard view) -->
|
|
1061
|
+
<div v-if="!activeAgentView" class="team-dash-main">
|
|
1062
|
+
|
|
1063
|
+
<!-- Kanban board (collapsible) -->
|
|
1064
|
+
<div class="team-kanban-section">
|
|
1065
|
+
<div class="team-kanban-section-header" @click="kanbanExpanded = !kanbanExpanded">
|
|
1066
|
+
<span class="team-kanban-section-toggle">{{ kanbanExpanded ? '\u25BC' : '\u25B6' }}</span>
|
|
1067
|
+
<span class="team-kanban-section-title">Tasks</span>
|
|
1068
|
+
<span class="team-kanban-section-summary">{{ doneTasks.length }}/{{ displayTeam.tasks.length }} done</span>
|
|
1069
|
+
</div>
|
|
1070
|
+
<div v-show="kanbanExpanded" class="team-kanban">
|
|
1071
|
+
<div class="team-kanban-col">
|
|
1072
|
+
<div class="team-kanban-col-header">
|
|
1073
|
+
<span class="team-kanban-col-dot pending"></span>
|
|
1074
|
+
Pending
|
|
1075
|
+
<span class="team-kanban-col-count">{{ pendingTasks.length }}</span>
|
|
1076
|
+
</div>
|
|
1077
|
+
<div class="team-kanban-col-body">
|
|
1078
|
+
<div v-for="task in pendingTasks" :key="task.id" class="team-task-card">
|
|
1079
|
+
<div class="team-task-title">{{ task.title }}</div>
|
|
1080
|
+
<div v-if="task.description" class="team-task-desc team-task-desc-clamp" @click.stop="$event.target.classList.toggle('team-task-desc-expanded')">{{ task.description }}</div>
|
|
1081
|
+
</div>
|
|
1082
|
+
<div v-if="pendingTasks.length === 0" class="team-kanban-empty">No tasks</div>
|
|
1083
|
+
</div>
|
|
1084
|
+
</div>
|
|
1085
|
+
<div class="team-kanban-col">
|
|
1086
|
+
<div class="team-kanban-col-header">
|
|
1087
|
+
<span class="team-kanban-col-dot active"></span>
|
|
1088
|
+
Active
|
|
1089
|
+
<span class="team-kanban-col-count">{{ activeTasks.length }}</span>
|
|
1090
|
+
</div>
|
|
1091
|
+
<div class="team-kanban-col-body">
|
|
1092
|
+
<div v-for="task in activeTasks" :key="task.id" class="team-task-card active">
|
|
1093
|
+
<div class="team-task-title">{{ task.title }}</div>
|
|
1094
|
+
<div v-if="task.description" class="team-task-desc team-task-desc-clamp" @click.stop="$event.target.classList.toggle('team-task-desc-expanded')">{{ task.description }}</div>
|
|
1095
|
+
<div v-if="getTaskAgent(task)" class="team-task-assignee">
|
|
1096
|
+
<span class="team-agent-dot" :style="{ background: getAgentColor(task.assignedTo) }"></span>
|
|
1097
|
+
{{ getTaskAgent(task).name || task.assignedTo }}
|
|
1098
|
+
</div>
|
|
1099
|
+
</div>
|
|
1100
|
+
<div v-if="activeTasks.length === 0" class="team-kanban-empty">No tasks</div>
|
|
1101
|
+
</div>
|
|
1102
|
+
</div>
|
|
1103
|
+
<div class="team-kanban-col">
|
|
1104
|
+
<div class="team-kanban-col-header">
|
|
1105
|
+
<span class="team-kanban-col-dot done"></span>
|
|
1106
|
+
Done
|
|
1107
|
+
<span class="team-kanban-col-count">{{ doneTasks.length }}</span>
|
|
1108
|
+
</div>
|
|
1109
|
+
<div class="team-kanban-col-body">
|
|
1110
|
+
<div v-for="task in doneTasks" :key="task.id" class="team-task-card done">
|
|
1111
|
+
<div class="team-task-title">{{ task.title }}</div>
|
|
1112
|
+
<div v-if="task.description" class="team-task-desc team-task-desc-clamp" @click.stop="$event.target.classList.toggle('team-task-desc-expanded')">{{ task.description }}</div>
|
|
1113
|
+
<div v-if="getTaskAgent(task)" class="team-task-assignee">
|
|
1114
|
+
<span class="team-agent-dot" :style="{ background: getAgentColor(task.assignedTo) }"></span>
|
|
1115
|
+
{{ getTaskAgent(task).name || task.assignedTo }}
|
|
1116
|
+
</div>
|
|
1117
|
+
</div>
|
|
1118
|
+
<div v-if="doneTasks.length === 0" class="team-kanban-empty">No tasks</div>
|
|
1119
|
+
</div>
|
|
1120
|
+
</div>
|
|
1121
|
+
<div v-if="failedTasks.length > 0" class="team-kanban-col">
|
|
1122
|
+
<div class="team-kanban-col-header">
|
|
1123
|
+
<span class="team-kanban-col-dot failed"></span>
|
|
1124
|
+
Failed
|
|
1125
|
+
<span class="team-kanban-col-count">{{ failedTasks.length }}</span>
|
|
1126
|
+
</div>
|
|
1127
|
+
<div class="team-kanban-col-body">
|
|
1128
|
+
<div v-for="task in failedTasks" :key="task.id" class="team-task-card failed">
|
|
1129
|
+
<div class="team-task-title">{{ task.title }}</div>
|
|
1130
|
+
<div v-if="task.description" class="team-task-desc team-task-desc-clamp" @click.stop="$event.target.classList.toggle('team-task-desc-expanded')">{{ task.description }}</div>
|
|
1131
|
+
</div>
|
|
1132
|
+
</div>
|
|
1133
|
+
</div>
|
|
1134
|
+
</div>
|
|
1135
|
+
</div>
|
|
1136
|
+
|
|
1137
|
+
<!-- Agent cards (horizontal) -->
|
|
1138
|
+
<div class="team-agents-bar">
|
|
1139
|
+
<div class="team-agents-bar-header">Agents</div>
|
|
1140
|
+
<div class="team-agents-bar-list">
|
|
1141
|
+
<div
|
|
1142
|
+
v-for="agent in (displayTeam.agents || [])" :key="agent.id"
|
|
1143
|
+
class="team-agent-card"
|
|
1144
|
+
@click="historicalTeam ? viewAgentWithHistory(agent.id) : viewAgent(agent.id)"
|
|
1145
|
+
>
|
|
1146
|
+
<div class="team-agent-card-top">
|
|
1147
|
+
<span :class="['team-agent-dot', { working: agent.status === 'working' || agent.status === 'starting' }]" :style="{ background: getAgentColor(agent.id) }"></span>
|
|
1148
|
+
<span class="team-agent-card-name">{{ agent.name || agent.id }}</span>
|
|
1149
|
+
<span :class="['team-agent-card-status', 'team-agent-card-status-' + agent.status]">{{ agent.status }}</span>
|
|
1150
|
+
</div>
|
|
1151
|
+
<div v-if="getLatestAgentActivity(agent.id)" class="team-agent-card-activity">{{ getLatestAgentActivity(agent.id) }}</div>
|
|
1152
|
+
</div>
|
|
1153
|
+
<div v-if="!displayTeam.agents || displayTeam.agents.length === 0" class="team-agents-empty">
|
|
1154
|
+
<span v-if="displayTeam.status === 'planning'">Planning tasks...</span>
|
|
1155
|
+
<span v-else>No agents</span>
|
|
1156
|
+
</div>
|
|
1157
|
+
</div>
|
|
1158
|
+
</div>
|
|
1159
|
+
|
|
1160
|
+
<!-- Activity feed -->
|
|
1161
|
+
<div v-if="displayTeam.feed && displayTeam.feed.length > 0" class="team-feed">
|
|
1162
|
+
<div class="team-feed-header">Activity</div>
|
|
1163
|
+
<div class="team-feed-list">
|
|
1164
|
+
<div v-for="(entry, fi) in displayTeam.feed" :key="fi" class="team-feed-entry">
|
|
1165
|
+
<span v-if="entry.agentId" class="team-agent-dot" :style="{ background: getAgentColor(entry.agentId) }"></span>
|
|
1166
|
+
<span v-else class="team-agent-dot" style="background: #666;"></span>
|
|
1167
|
+
<span class="team-feed-time">{{ formatTeamTime(entry.timestamp) }}</span>
|
|
1168
|
+
<span class="team-feed-text">{{ entry.content }}</span>
|
|
1169
|
+
</div>
|
|
1170
|
+
</div>
|
|
1171
|
+
</div>
|
|
1172
|
+
|
|
1173
|
+
<!-- Completion stats -->
|
|
1174
|
+
<div v-if="displayTeam.status === 'completed' || displayTeam.status === 'failed'" class="team-stats-bar">
|
|
1175
|
+
<div class="team-stat">
|
|
1176
|
+
<span class="team-stat-label">Tasks</span>
|
|
1177
|
+
<span class="team-stat-value">{{ doneTasks.length }}/{{ displayTeam.tasks.length }}</span>
|
|
1178
|
+
</div>
|
|
1179
|
+
<div v-if="displayTeam.durationMs" class="team-stat">
|
|
1180
|
+
<span class="team-stat-label">Duration</span>
|
|
1181
|
+
<span class="team-stat-value">{{ Math.round(displayTeam.durationMs / 1000) }}s</span>
|
|
1182
|
+
</div>
|
|
1183
|
+
<div v-if="displayTeam.totalCost" class="team-stat">
|
|
1184
|
+
<span class="team-stat-label">Cost</span>
|
|
1185
|
+
<span class="team-stat-value">{{ '$' + displayTeam.totalCost.toFixed(2) }}</span>
|
|
1186
|
+
</div>
|
|
1187
|
+
<div class="team-stat">
|
|
1188
|
+
<span class="team-stat-label">Agents</span>
|
|
1189
|
+
<span class="team-stat-value">{{ (displayTeam.agents || []).length }}</span>
|
|
1190
|
+
</div>
|
|
1191
|
+
</div>
|
|
1192
|
+
|
|
1193
|
+
<!-- Completion summary -->
|
|
1194
|
+
<div v-if="displayTeam.status === 'completed' && displayTeam.summary" class="team-summary">
|
|
1195
|
+
<div class="team-summary-header">Summary</div>
|
|
1196
|
+
<div class="team-summary-body markdown-body" v-html="getRenderedContent({ role: 'assistant', content: displayTeam.summary })"></div>
|
|
1197
|
+
</div>
|
|
1198
|
+
|
|
1199
|
+
<!-- New team launcher after completion -->
|
|
1200
|
+
<div v-if="!historicalTeam && (displayTeam.status === 'completed' || displayTeam.status === 'failed')" class="team-new-launcher">
|
|
1201
|
+
<textarea
|
|
1202
|
+
v-model="teamInstruction"
|
|
1203
|
+
class="team-new-launcher-input"
|
|
1204
|
+
placeholder="Launch another team task..."
|
|
1205
|
+
rows="2"
|
|
1206
|
+
@keydown.enter.ctrl="launchTeamFromPanel()"
|
|
1207
|
+
></textarea>
|
|
1208
|
+
<div class="team-new-launcher-actions">
|
|
1209
|
+
<button class="team-create-launch" :disabled="!teamInstruction.trim()" @click="launchTeamFromPanel()">
|
|
1210
|
+
<svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
|
|
1211
|
+
New Team
|
|
1212
|
+
</button>
|
|
1213
|
+
<button class="team-create-cancel" @click="backToChat()">Back to Chat</button>
|
|
1214
|
+
</div>
|
|
1215
|
+
</div>
|
|
1216
|
+
</div>
|
|
1217
|
+
|
|
1218
|
+
<!-- Agent detail view -->
|
|
1219
|
+
<div v-else class="team-agent-detail">
|
|
1220
|
+
<div class="team-agent-detail-header" :style="{ borderBottom: '2px solid ' + getAgentColor(activeAgentView) }">
|
|
1221
|
+
<button class="team-agent-back-btn" @click="viewDashboard()">
|
|
1222
|
+
<svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
|
|
1223
|
+
Dashboard
|
|
1224
|
+
</button>
|
|
1225
|
+
<span :class="['team-agent-dot', { working: findAgent(activeAgentView)?.status === 'working' || findAgent(activeAgentView)?.status === 'starting' }]" :style="{ background: getAgentColor(activeAgentView) }"></span>
|
|
1226
|
+
<span class="team-agent-detail-name" :style="{ color: getAgentColor(activeAgentView) }">{{ findAgent(activeAgentView)?.name || activeAgentView }}</span>
|
|
1227
|
+
<span class="team-agent-detail-status">{{ findAgent(activeAgentView)?.status }}</span>
|
|
1228
|
+
</div>
|
|
1229
|
+
<div class="team-agent-messages">
|
|
1230
|
+
<div class="team-agent-messages-inner">
|
|
1231
|
+
<div v-if="getAgentMessages(activeAgentView).length === 0" class="team-agent-empty-msg">
|
|
1232
|
+
No messages yet.
|
|
1233
|
+
</div>
|
|
1234
|
+
<template v-for="(msg, mi) in getAgentMessages(activeAgentView)" :key="msg.id">
|
|
1235
|
+
<!-- Agent user/prompt message -->
|
|
1236
|
+
<div v-if="msg.role === 'user' && msg.content" class="team-agent-prompt">
|
|
1237
|
+
<div class="team-agent-prompt-label">Task Prompt</div>
|
|
1238
|
+
<div class="team-agent-prompt-body markdown-body" v-html="getRenderedContent(msg)"></div>
|
|
1239
|
+
</div>
|
|
1240
|
+
<!-- System notice (e.g. completion message) -->
|
|
1241
|
+
<div v-else-if="msg.role === 'system'" class="team-agent-empty-msg">
|
|
1242
|
+
{{ msg.content }}
|
|
1243
|
+
</div>
|
|
1244
|
+
<!-- Agent assistant text -->
|
|
1245
|
+
<div v-else-if="msg.role === 'assistant'" :class="['message', 'message-assistant']">
|
|
1246
|
+
<div class="team-agent-detail-name-tag" :style="{ color: getAgentColor(activeAgentView) }">{{ findAgent(activeAgentView)?.name || activeAgentView }}</div>
|
|
1247
|
+
<div :class="['message-bubble', 'assistant-bubble', { streaming: msg.isStreaming }]">
|
|
1248
|
+
<div class="message-content markdown-body" v-html="getRenderedContent(msg)"></div>
|
|
1249
|
+
</div>
|
|
1250
|
+
</div>
|
|
1251
|
+
<!-- Agent tool use -->
|
|
1252
|
+
<div v-else-if="msg.role === 'tool'" class="tool-line-wrapper">
|
|
1253
|
+
<div :class="['tool-line', { completed: msg.hasResult, running: !msg.hasResult }]" @click="toggleTool(msg)">
|
|
1254
|
+
<span class="tool-icon" v-html="getToolIcon(msg.toolName)"></span>
|
|
1255
|
+
<span class="tool-name">{{ msg.toolName }}</span>
|
|
1256
|
+
<span class="tool-summary">{{ getToolSummary(msg) }}</span>
|
|
1257
|
+
<span class="tool-status-icon" v-if="msg.hasResult">\u{2713}</span>
|
|
1258
|
+
<span class="tool-status-icon running-dots" v-else>
|
|
1259
|
+
<span></span><span></span><span></span>
|
|
1260
|
+
</span>
|
|
1261
|
+
<span class="tool-toggle">{{ msg.expanded ? '\u{25B2}' : '\u{25BC}' }}</span>
|
|
1262
|
+
</div>
|
|
1263
|
+
<div v-show="msg.expanded" class="tool-expand">
|
|
1264
|
+
<div v-if="isEditTool(msg) && getEditDiffHtml(msg)" class="tool-diff" v-html="getEditDiffHtml(msg)"></div>
|
|
1265
|
+
<div v-else-if="getFormattedToolInput(msg)" class="tool-input-formatted" v-html="getFormattedToolInput(msg)"></div>
|
|
1266
|
+
<pre v-else-if="msg.toolInput" class="tool-block">{{ msg.toolInput }}</pre>
|
|
1267
|
+
<pre v-if="msg.toolOutput" class="tool-block tool-output">{{ msg.toolOutput }}</pre>
|
|
1268
|
+
</div>
|
|
1269
|
+
</div>
|
|
1270
|
+
</template>
|
|
1271
|
+
</div>
|
|
1272
|
+
</div>
|
|
1273
|
+
</div>
|
|
1274
|
+
|
|
1275
|
+
</div>
|
|
1276
|
+
</div>
|
|
1277
|
+
</template>
|
|
1278
|
+
|
|
1279
|
+
<!-- ══ Normal Chat ══ -->
|
|
1280
|
+
<template v-else>
|
|
840
1281
|
<div class="message-list" @scroll="onMessageListScroll">
|
|
841
1282
|
<div class="message-list-inner">
|
|
842
1283
|
<div v-if="messages.length === 0 && status === 'Connected' && !loadingHistory" class="empty-state">
|
|
@@ -890,6 +1331,29 @@ const App = {
|
|
|
890
1331
|
</div>
|
|
891
1332
|
</template>
|
|
892
1333
|
|
|
1334
|
+
<!-- Agent tool call (team-styled) -->
|
|
1335
|
+
<div v-else-if="msg.role === 'tool' && msg.toolName === 'Agent'" class="tool-line-wrapper team-agent-tool-wrapper">
|
|
1336
|
+
<div :class="['tool-line', 'team-agent-tool-line', { completed: msg.hasResult, running: !msg.hasResult }]" @click="toggleTool(msg)">
|
|
1337
|
+
<span class="team-agent-tool-icon">
|
|
1338
|
+
<svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M12 12c2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4 1.79 4 4 4zm0 2c-2.67 0-8 1.34-8 4v2h16v-2c0-2.66-5.33-4-8-4z"/></svg>
|
|
1339
|
+
</span>
|
|
1340
|
+
<span class="team-agent-tool-name">Agent</span>
|
|
1341
|
+
<span class="team-agent-tool-desc">{{ getToolSummary(msg) }}</span>
|
|
1342
|
+
<span class="tool-status-icon" v-if="msg.hasResult">\u{2713}</span>
|
|
1343
|
+
<span class="tool-status-icon running-dots" v-else>
|
|
1344
|
+
<span></span><span></span><span></span>
|
|
1345
|
+
</span>
|
|
1346
|
+
<span class="tool-toggle">{{ msg.expanded ? '\u{25B2}' : '\u{25BC}' }}</span>
|
|
1347
|
+
</div>
|
|
1348
|
+
<div v-show="msg.expanded" class="tool-expand team-agent-tool-expand">
|
|
1349
|
+
<pre v-if="msg.toolInput" class="tool-block">{{ msg.toolInput }}</pre>
|
|
1350
|
+
<div v-if="msg.toolOutput" class="team-agent-tool-result">
|
|
1351
|
+
<div class="team-agent-tool-result-label">Agent Result</div>
|
|
1352
|
+
<div class="team-agent-tool-result-content markdown-body" v-html="getRenderedContent({ role: 'assistant', content: msg.toolOutput })"></div>
|
|
1353
|
+
</div>
|
|
1354
|
+
</div>
|
|
1355
|
+
</div>
|
|
1356
|
+
|
|
893
1357
|
<!-- Tool use block (collapsible) -->
|
|
894
1358
|
<div v-else-if="msg.role === 'tool'" class="tool-line-wrapper">
|
|
895
1359
|
<div :class="['tool-line', { completed: msg.hasResult, running: !msg.hasResult }]" @click="toggleTool(msg)">
|
|
@@ -982,8 +1446,10 @@ const App = {
|
|
|
982
1446
|
</div>
|
|
983
1447
|
</div>
|
|
984
1448
|
</div>
|
|
1449
|
+
</template>
|
|
985
1450
|
|
|
986
|
-
|
|
1451
|
+
<!-- Input area (shown in both chat and team create mode) -->
|
|
1452
|
+
<div class="input-area" v-if="teamMode !== 'team'">
|
|
987
1453
|
<input
|
|
988
1454
|
type="file"
|
|
989
1455
|
ref="fileInputRef"
|