@nextclaw/ui 0.11.23 → 0.12.1
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/CHANGELOG.md +37 -0
- package/dist/assets/{ChannelsList-DVDu1xvz.js → ChannelsList-DekMP4a3.js} +1 -1
- package/dist/assets/ChatPage-Dgw4vlDt.js +43 -0
- package/dist/assets/DocBrowser-CExjX5is.js +1 -0
- package/dist/assets/{DocBrowser-BmtBLFU0.js → DocBrowser-DjcghYGO.js} +1 -1
- package/dist/assets/{DocBrowserContext-YIKkPb76.js → DocBrowserContext-CLlq7rZQ.js} +1 -1
- package/dist/assets/{LogoBadge-F7ZWdxLT.js → LogoBadge-D_dOy5U3.js} +1 -1
- package/dist/assets/{MarketplacePage-Buo9HrOz.js → MarketplacePage-BlIeNn3x.js} +2 -2
- package/dist/assets/MarketplacePage-DGfzg1LG.js +1 -0
- package/dist/assets/{McpMarketplacePage-JnkYwK7p.js → McpMarketplacePage-mz2_IX1O.js} +2 -2
- package/dist/assets/{ModelConfig-BYRhgp0c.js → ModelConfig-C_49_a9v.js} +1 -1
- package/dist/assets/{ProvidersList-DmLyyHvX.js → ProvidersList-B0RCb_Vg.js} +1 -1
- package/dist/assets/{RemoteAccessPage-CDSSvH7Z.js → RemoteAccessPage-CcfQjLtx.js} +1 -1
- package/dist/assets/RuntimeConfig-DBWzwoY-.js +1 -0
- package/dist/assets/{SearchConfig-D5f1EkLE.js → SearchConfig-jSdwlH4b.js} +1 -1
- package/dist/assets/{SecretsConfig-D61IKcYt.js → SecretsConfig-DbiS3txa.js} +1 -1
- package/dist/assets/{SessionsConfig-BRIxVTEv.js → SessionsConfig-CbIOcAp8.js} +2 -2
- package/dist/assets/{book-open-CXoF5nQC.js → book-open-BLxSL7Dk.js} +1 -1
- package/dist/assets/chat-session-display-8yW6-mtm.js +1 -0
- package/dist/assets/{chunk-JZWAC4HX-CvRWvTy5.js → chunk-JZWAC4HX-Bp0t5xoO.js} +1 -1
- package/dist/assets/{config-DJswxxE8.js → config-C96FWufn.js} +1 -1
- package/dist/assets/{createLucideIcon-CjGHOWb6.js → createLucideIcon-B_U7Nq4F.js} +1 -1
- package/dist/assets/{dist-Cl2QB-2y.js → dist-BFY-GyT4.js} +1 -1
- package/dist/assets/{dist-nqTTbVdA.js → dist-D9pHzW9z.js} +1 -1
- package/dist/assets/{external-link-tIO7zING.js → external-link-BydIQTIH.js} +1 -1
- package/dist/assets/{hash-JWUyl1pT.js → hash-Djdf0x1C.js} +1 -1
- package/dist/assets/i18n-DAekxt_G.js +1 -0
- package/dist/assets/index-CHEgQIiO.css +1 -0
- package/dist/assets/index-DqSv8Azv.js +6 -0
- package/dist/assets/{label-BIpeNu4r.js → label-Bvv4Mrea.js} +1 -1
- package/dist/assets/loader-circle-CGXXikVG.js +1 -0
- package/dist/assets/{logos-DThdM9lk.js → logos-CGJJRI5_.js} +1 -1
- package/dist/assets/{page-layout-D3Xo605Z.js → page-layout-6Nm4Cnvr.js} +1 -1
- package/dist/assets/plus-CrW9BJDy.js +1 -0
- package/dist/assets/{popover-BJRUGA_H.js → popover-b9rSYI6X.js} +1 -1
- package/dist/assets/{provider-models-bz5y28rq.js → provider-models-IJDA940D.js} +1 -1
- package/dist/assets/{react-7ZHqQtEV.js → react-CDZz_StC.js} +1 -1
- package/dist/assets/{refresh-ccw-CC6-_QuL.js → refresh-ccw-BvSSnnCw.js} +1 -1
- package/dist/assets/{save-DJM5RRWW.js → save-CAf0_-b9.js} +1 -1
- package/dist/assets/search-DgoXxocn.js +1 -0
- package/dist/assets/{security-config-DbUyWcQz.js → security-config-DF66-l25.js} +1 -1
- package/dist/assets/{select-DSkTc61S.js → select-CEIMqc0H.js} +1 -1
- package/dist/assets/skeleton-BiPUQkOD.js +1 -0
- package/dist/assets/{status-dot-LNBlDu3q.js → status-dot-CmQI5Qq2.js} +1 -1
- package/dist/assets/{switch-Bo-Y46HZ.js → switch-B7SxDXyR.js} +1 -1
- package/dist/assets/{tabs-custom-DXv507_2.js → tabs-custom-Dxt6EJJW.js} +1 -1
- package/dist/assets/{trash-2-DFZmW6Gg.js → trash-2-BnQ1PDTw.js} +1 -1
- package/dist/assets/{useConfirmDialog-COwYXDKm.js → useConfirmDialog-B-vMOmhG.js} +1 -1
- package/dist/assets/{useMutation-DrZrOgVL.js → useMutation-Bi39Z9_J.js} +1 -1
- package/dist/assets/x-PBSiWt3l.js +1 -0
- package/dist/index.html +18 -18
- package/package.json +7 -7
- package/src/App.tsx +2 -0
- package/src/api/agents.ts +26 -0
- package/src/api/types.ts +23 -5
- package/src/components/agents/AgentsPage.test.tsx +70 -0
- package/src/components/agents/AgentsPage.tsx +353 -0
- package/src/components/chat/ChatConversationPanel.test.tsx +172 -13
- package/src/components/chat/ChatConversationPanel.tsx +30 -7
- package/src/components/chat/ChatSidebar.test.tsx +48 -0
- package/src/components/chat/ChatSidebar.tsx +11 -0
- package/src/components/chat/ChatWelcome.test.tsx +30 -0
- package/src/components/chat/ChatWelcome.tsx +50 -1
- package/src/components/chat/adapters/chat-message-part.adapter.ts +5 -0
- package/src/components/chat/adapters/chat-message-tool-agent-id.test.ts +102 -0
- package/src/components/chat/adapters/chat-message-tool-agent-id.ts +47 -0
- package/src/components/chat/adapters/chat-message.adapter.test.ts +6 -0
- package/src/components/chat/adapters/chat-message.session-request-tool-card.ts +24 -15
- package/src/components/chat/chat-child-session-panel.tsx +115 -49
- package/src/components/chat/chat-page-runtime.test.ts +30 -0
- package/src/components/chat/chat-page-shell.tsx +8 -17
- package/src/components/chat/chat-session-route.ts +0 -14
- package/src/components/chat/chat-sidebar-session-item.tsx +20 -1
- package/src/components/chat/containers/chat-input-bar.container.tsx +2 -1
- package/src/components/chat/containers/chat-message-list.container.tsx +7 -0
- package/src/components/chat/ncp/NcpChatPage.tsx +77 -158
- package/src/components/chat/ncp/README.md +3 -0
- package/src/components/chat/ncp/ncp-chat-page-data.test.ts +2 -0
- package/src/components/chat/ncp/ncp-chat-thread.manager.ts +66 -10
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +2 -0
- package/src/components/chat/ncp/ncp-session-adapter.ts +1 -0
- package/src/components/chat/ncp/page/ncp-chat-derived-state.ts +128 -0
- package/src/components/chat/ncp/session-conversation/use-ncp-child-session-tabs-view.ts +52 -0
- package/src/components/chat/ncp/session-conversation/use-ncp-session-conversation.test.tsx +101 -0
- package/src/components/chat/ncp/session-conversation/use-ncp-session-conversation.ts +72 -0
- package/src/components/chat/presenter/chat-presenter-context.tsx +1 -0
- package/src/components/chat/stores/chat-thread.store.ts +20 -6
- package/src/components/common/AgentAvatar.tsx +63 -0
- package/src/components/common/agent-identity/agent-identity-avatar.tsx +27 -0
- package/src/components/common/agent-identity/index.ts +3 -0
- package/src/components/common/agent-identity/use-agent-identity.ts +50 -0
- package/src/components/config/RuntimeConfig.tsx +14 -101
- package/src/components/config/runtime-config-agent.utils.ts +95 -0
- package/src/components/layout/AppLayout.tsx +3 -1
- package/src/components/layout/Sidebar.tsx +6 -1
- package/src/components/layout/app-layout.test.tsx +30 -0
- package/src/components/ui/tabs.tsx +2 -0
- package/src/hooks/README.md +3 -0
- package/src/hooks/agents/useAgents.ts +44 -0
- package/src/lib/i18n.agents.ts +66 -0
- package/src/lib/i18n.chat.ts +5 -0
- package/src/lib/i18n.ts +4 -6
- package/src/lib/ui-document-title.ts +1 -0
- package/dist/assets/ChatPage-Z9tRzm_n.js +0 -43
- package/dist/assets/DocBrowser-B9OaZjmg.js +0 -1
- package/dist/assets/MarketplacePage-D6rVQEQR.js +0 -1
- package/dist/assets/RuntimeConfig-v7a7Fe3x.js +0 -1
- package/dist/assets/chat-session-display-D0WpnuRZ.js +0 -1
- package/dist/assets/i18n-CDHMXlRZ.js +0 -1
- package/dist/assets/index-BuwbBgmT.js +0 -6
- package/dist/assets/index-bZ8cqQIS.css +0 -1
- package/dist/assets/loader-circle-Cs8XVFTw.js +0 -1
- package/dist/assets/plus-PHf8q-Ct.js +0 -1
- package/dist/assets/search-C91yH_6y.js +0 -1
- package/dist/assets/skeleton-Dzg-HOiN.js +0 -1
- package/dist/assets/x-D7Q1yqSF.js +0 -1
- /package/src/lib/{i18n → i18n-runtime}/i18n-language-owner.ts +0 -0
- /package/src/lib/{i18n → i18n-runtime}/i18n.path-picker.ts +0 -0
|
@@ -1,38 +1,88 @@
|
|
|
1
|
-
import { useMemo } from 'react';
|
|
2
|
-
import { useQuery } from '@tanstack/react-query';
|
|
3
1
|
import { ArrowLeft, Loader2, X } from 'lucide-react';
|
|
4
|
-
import { fetchNcpSessionMessages } from '@/api/ncp-session';
|
|
5
2
|
import { ChatMessageListContainer } from '@/components/chat/containers/chat-message-list.container';
|
|
6
|
-
import {
|
|
3
|
+
import { useNcpChildSessionTabsView } from '@/components/chat/ncp/session-conversation/use-ncp-child-session-tabs-view';
|
|
4
|
+
import { useNcpSessionConversation } from '@/components/chat/ncp/session-conversation/use-ncp-session-conversation';
|
|
5
|
+
import type { ChatChildSessionTab } from '@/components/chat/stores/chat-thread.store';
|
|
6
|
+
import { AgentIdentityAvatar } from '@/components/common/agent-identity';
|
|
7
|
+
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
8
|
+
import { t } from '@/lib/i18n';
|
|
7
9
|
import { cn } from '@/lib/utils';
|
|
8
10
|
import type { ChatToolActionViewModel } from '@nextclaw/agent-chat-ui';
|
|
9
11
|
|
|
10
12
|
type ChatChildSessionPanelProps = {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
+
tabs: readonly ChatChildSessionTab[];
|
|
14
|
+
activeSessionKey: string;
|
|
15
|
+
onSelectSession: (sessionKey: string) => void;
|
|
13
16
|
onClose: () => void;
|
|
14
17
|
onBackToParent: () => void;
|
|
15
18
|
onToolAction?: (action: ChatToolActionViewModel) => void;
|
|
16
19
|
};
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
function ChildSessionPanelConversation({
|
|
19
22
|
sessionKey,
|
|
20
|
-
|
|
23
|
+
onToolAction,
|
|
24
|
+
}: {
|
|
25
|
+
sessionKey: string;
|
|
26
|
+
onToolAction?: (action: ChatToolActionViewModel) => void;
|
|
27
|
+
}) {
|
|
28
|
+
const agent = useNcpSessionConversation(sessionKey);
|
|
29
|
+
const messages = agent.visibleMessages;
|
|
30
|
+
|
|
31
|
+
if (agent.isHydrating) {
|
|
32
|
+
return (
|
|
33
|
+
<div className="flex h-full items-center justify-center text-sm text-gray-500">
|
|
34
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
35
|
+
{t('chatChildSessionLoading')}
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (agent.hydrateError) {
|
|
41
|
+
return (
|
|
42
|
+
<div className="px-4 py-5 text-sm text-rose-600">
|
|
43
|
+
{agent.hydrateError.message}
|
|
44
|
+
</div>
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (messages.length === 0 && !agent.isRunning) {
|
|
49
|
+
return (
|
|
50
|
+
<div className="px-4 py-5 text-sm text-gray-500">
|
|
51
|
+
{t('chatChildSessionEmpty')}
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<div className="px-4 py-5">
|
|
58
|
+
<ChatMessageListContainer
|
|
59
|
+
messages={messages}
|
|
60
|
+
isSending={agent.isRunning}
|
|
61
|
+
onToolAction={onToolAction}
|
|
62
|
+
/>
|
|
63
|
+
</div>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function ChatChildSessionPanel({
|
|
68
|
+
tabs,
|
|
69
|
+
activeSessionKey,
|
|
70
|
+
onSelectSession,
|
|
21
71
|
onClose,
|
|
22
72
|
onBackToParent,
|
|
23
73
|
onToolAction,
|
|
24
74
|
}: ChatChildSessionPanelProps) {
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
75
|
+
const resolvedTabs = useNcpChildSessionTabsView(tabs);
|
|
76
|
+
const activeTab =
|
|
77
|
+
resolvedTabs.find((tab) => tab.sessionKey === activeSessionKey) ??
|
|
78
|
+
resolvedTabs[0] ??
|
|
79
|
+
null;
|
|
80
|
+
const hasParentSession = resolvedTabs.some((tab) => Boolean(tab.parentSessionKey));
|
|
81
|
+
const shouldShowTabs = resolvedTabs.length > 1;
|
|
33
82
|
|
|
34
|
-
|
|
35
|
-
|
|
83
|
+
if (!activeTab) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
36
86
|
|
|
37
87
|
return (
|
|
38
88
|
<aside className="hidden md:flex md:w-[24rem] lg:w-[28rem] shrink-0 border-l border-gray-200/70 bg-white/90 backdrop-blur-sm">
|
|
@@ -43,56 +93,72 @@ export function ChatChildSessionPanel({
|
|
|
43
93
|
type="button"
|
|
44
94
|
onClick={onBackToParent}
|
|
45
95
|
className={cn(
|
|
46
|
-
'inline-flex items-center gap-1 text-xs font-medium text-gray-600 hover:text-gray-900',
|
|
47
|
-
!
|
|
96
|
+
'inline-flex items-center gap-1 text-xs font-medium text-gray-600 transition-colors hover:text-gray-900',
|
|
97
|
+
!hasParentSession && 'pointer-events-none opacity-0',
|
|
48
98
|
)}
|
|
49
99
|
>
|
|
50
100
|
<ArrowLeft className="h-3.5 w-3.5" />
|
|
51
|
-
<span>
|
|
101
|
+
<span>{t('chatBackToParent')}</span>
|
|
52
102
|
</button>
|
|
53
103
|
<button
|
|
54
104
|
type="button"
|
|
55
105
|
onClick={onClose}
|
|
56
|
-
className="rounded-
|
|
57
|
-
aria-label=
|
|
106
|
+
className="rounded-full border border-gray-200/80 p-1.5 text-gray-500 transition-colors hover:bg-gray-100 hover:text-gray-900"
|
|
107
|
+
aria-label={t('chatChildSessionClosePanel')}
|
|
58
108
|
>
|
|
59
109
|
<X className="h-4 w-4" />
|
|
60
110
|
</button>
|
|
61
111
|
</div>
|
|
62
|
-
|
|
63
|
-
<div className="
|
|
64
|
-
|
|
112
|
+
{!shouldShowTabs ? (
|
|
113
|
+
<div className="mt-3 flex min-w-0 items-center gap-2 text-sm font-semibold text-gray-900">
|
|
114
|
+
{activeTab.agentId ? (
|
|
115
|
+
<AgentIdentityAvatar
|
|
116
|
+
agentId={activeTab.agentId}
|
|
117
|
+
className="h-5 w-5 shrink-0"
|
|
118
|
+
/>
|
|
119
|
+
) : null}
|
|
120
|
+
<span className="truncate" title={activeTab.sessionKey}>
|
|
121
|
+
{activeTab.title}
|
|
122
|
+
</span>
|
|
65
123
|
</div>
|
|
66
|
-
|
|
67
|
-
|
|
124
|
+
) : null}
|
|
125
|
+
{shouldShowTabs ? (
|
|
126
|
+
<div className="mt-3 overflow-x-auto custom-scrollbar">
|
|
127
|
+
<Tabs value={activeSessionKey} onValueChange={onSelectSession}>
|
|
128
|
+
<TabsList className="h-auto min-w-max justify-start gap-1.5 rounded-none bg-transparent p-0 text-gray-500">
|
|
129
|
+
{resolvedTabs.map((tab) => (
|
|
130
|
+
<TabsTrigger
|
|
131
|
+
key={tab.sessionKey}
|
|
132
|
+
value={tab.sessionKey}
|
|
133
|
+
className="gap-2 rounded-full border border-gray-200/80 bg-white/85 px-2.5 py-1.5 text-xs font-medium text-gray-600 shadow-none hover:border-primary/30 hover:text-primary data-[state=active]:border-primary/30 data-[state=active]:bg-primary-50/70 data-[state=active]:text-primary data-[state=active]:shadow-sm"
|
|
134
|
+
>
|
|
135
|
+
{tab.agentId ? (
|
|
136
|
+
<AgentIdentityAvatar
|
|
137
|
+
agentId={tab.agentId}
|
|
138
|
+
className="h-4 w-4 shrink-0"
|
|
139
|
+
/>
|
|
140
|
+
) : null}
|
|
141
|
+
<span className="max-w-[132px] truncate">{tab.title}</span>
|
|
142
|
+
</TabsTrigger>
|
|
143
|
+
))}
|
|
144
|
+
</TabsList>
|
|
145
|
+
</Tabs>
|
|
68
146
|
</div>
|
|
69
|
-
|
|
70
|
-
</div>
|
|
147
|
+
) : null}
|
|
71
148
|
</div>
|
|
72
149
|
|
|
73
150
|
<div className="flex-1 min-h-0 overflow-y-auto custom-scrollbar">
|
|
74
|
-
{
|
|
75
|
-
<div
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
{(query.error as Error).message}
|
|
82
|
-
</div>
|
|
83
|
-
) : messages.length === 0 ? (
|
|
84
|
-
<div className="px-4 py-5 text-sm text-gray-500">
|
|
85
|
-
No child session messages yet.
|
|
86
|
-
</div>
|
|
87
|
-
) : (
|
|
88
|
-
<div className="px-4 py-5">
|
|
89
|
-
<ChatMessageListContainer
|
|
90
|
-
messages={messages}
|
|
91
|
-
isSending={false}
|
|
151
|
+
{resolvedTabs.map((tab) => (
|
|
152
|
+
<div
|
|
153
|
+
key={tab.sessionKey}
|
|
154
|
+
className={cn(tab.sessionKey === activeSessionKey ? 'block' : 'hidden')}
|
|
155
|
+
>
|
|
156
|
+
<ChildSessionPanelConversation
|
|
157
|
+
sessionKey={tab.sessionKey}
|
|
92
158
|
onToolAction={onToolAction}
|
|
93
159
|
/>
|
|
94
160
|
</div>
|
|
95
|
-
)}
|
|
161
|
+
))}
|
|
96
162
|
</div>
|
|
97
163
|
</div>
|
|
98
164
|
</aside>
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
resolveSelectedModelValue,
|
|
7
7
|
resolveSelectedThinkingLevelValue
|
|
8
8
|
} from '@/components/chat/chat-session-preference-governance';
|
|
9
|
+
import { shouldRefreshDraftSessionId } from '@/components/chat/ncp/NcpChatPage';
|
|
9
10
|
|
|
10
11
|
const modelOptions = [
|
|
11
12
|
{
|
|
@@ -154,6 +155,35 @@ describe('resolveSelectedModelValue', () => {
|
|
|
154
155
|
});
|
|
155
156
|
});
|
|
156
157
|
|
|
158
|
+
describe('shouldRefreshDraftSessionId', () => {
|
|
159
|
+
it('does not replace the initial draft session id on first mount', () => {
|
|
160
|
+
expect(
|
|
161
|
+
shouldRefreshDraftSessionId({
|
|
162
|
+
previousSelectedSessionKey: undefined,
|
|
163
|
+
nextSelectedSessionKey: null
|
|
164
|
+
})
|
|
165
|
+
).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('replaces the draft session id after leaving an existing session', () => {
|
|
169
|
+
expect(
|
|
170
|
+
shouldRefreshDraftSessionId({
|
|
171
|
+
previousSelectedSessionKey: 'session-1',
|
|
172
|
+
nextSelectedSessionKey: null
|
|
173
|
+
})
|
|
174
|
+
).toBe(true);
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('does not replace the draft session id while staying on the same session', () => {
|
|
178
|
+
expect(
|
|
179
|
+
shouldRefreshDraftSessionId({
|
|
180
|
+
previousSelectedSessionKey: 'session-1',
|
|
181
|
+
nextSelectedSessionKey: 'session-1'
|
|
182
|
+
})
|
|
183
|
+
).toBe(false);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
157
187
|
describe('resolveRecentSessionPreferredModel', () => {
|
|
158
188
|
it('returns the most recent preferred model from the same runtime', () => {
|
|
159
189
|
const sessions = [
|
|
@@ -2,10 +2,11 @@ import { useEffect } from 'react';
|
|
|
2
2
|
import type { Dispatch, MutableRefObject, SetStateAction } from 'react';
|
|
3
3
|
import { ChatSidebar } from '@/components/chat/ChatSidebar';
|
|
4
4
|
import { ChatConversationPanel } from '@/components/chat/ChatConversationPanel';
|
|
5
|
+
import { AgentsPage } from '@/components/agents/AgentsPage';
|
|
5
6
|
import { CronConfig } from '@/components/config/CronConfig';
|
|
6
7
|
import { MarketplacePage } from '@/components/marketplace/MarketplacePage';
|
|
7
8
|
|
|
8
|
-
export type MainPanelView = 'chat' | 'cron' | 'skills';
|
|
9
|
+
export type MainPanelView = 'chat' | 'cron' | 'skills' | 'agents';
|
|
9
10
|
|
|
10
11
|
export type ChatPageProps = {
|
|
11
12
|
view: MainPanelView;
|
|
@@ -15,12 +16,9 @@ type UseChatSessionSyncParams = {
|
|
|
15
16
|
view: MainPanelView;
|
|
16
17
|
routeSessionKey: string | null;
|
|
17
18
|
selectedSessionKey: string | null;
|
|
18
|
-
selectedAgentId: string;
|
|
19
19
|
setSelectedSessionKey: Dispatch<SetStateAction<string | null>>;
|
|
20
|
-
setSelectedAgentId: Dispatch<SetStateAction<string>>;
|
|
21
20
|
selectedSessionKeyRef: MutableRefObject<string | null>;
|
|
22
21
|
resetStreamState: () => void;
|
|
23
|
-
resolveAgentIdFromSessionKey: (sessionKey: string) => string | null;
|
|
24
22
|
};
|
|
25
23
|
|
|
26
24
|
export function useChatSessionSync(params: UseChatSessionSyncParams): void {
|
|
@@ -28,12 +26,9 @@ export function useChatSessionSync(params: UseChatSessionSyncParams): void {
|
|
|
28
26
|
view,
|
|
29
27
|
routeSessionKey,
|
|
30
28
|
selectedSessionKey,
|
|
31
|
-
selectedAgentId,
|
|
32
29
|
setSelectedSessionKey,
|
|
33
|
-
setSelectedAgentId,
|
|
34
30
|
selectedSessionKeyRef,
|
|
35
31
|
resetStreamState,
|
|
36
|
-
resolveAgentIdFromSessionKey
|
|
37
32
|
} = params;
|
|
38
33
|
|
|
39
34
|
useEffect(() => {
|
|
@@ -52,16 +47,6 @@ export function useChatSessionSync(params: UseChatSessionSyncParams): void {
|
|
|
52
47
|
}
|
|
53
48
|
}, [resetStreamState, routeSessionKey, selectedSessionKey, setSelectedSessionKey, view]);
|
|
54
49
|
|
|
55
|
-
useEffect(() => {
|
|
56
|
-
const inferred = selectedSessionKey ? resolveAgentIdFromSessionKey(selectedSessionKey) : null;
|
|
57
|
-
if (!inferred) {
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
if (selectedAgentId !== inferred) {
|
|
61
|
-
setSelectedAgentId(inferred);
|
|
62
|
-
}
|
|
63
|
-
}, [resolveAgentIdFromSessionKey, selectedAgentId, selectedSessionKey, setSelectedAgentId]);
|
|
64
|
-
|
|
65
50
|
useEffect(() => {
|
|
66
51
|
selectedSessionKeyRef.current = selectedSessionKey;
|
|
67
52
|
}, [selectedSessionKey, selectedSessionKeyRef]);
|
|
@@ -87,6 +72,12 @@ export function ChatPageLayout({ view, confirmDialog }: ChatPageLayoutProps) {
|
|
|
87
72
|
<CronConfig />
|
|
88
73
|
</div>
|
|
89
74
|
</div>
|
|
75
|
+
) : view === 'agents' ? (
|
|
76
|
+
<div className="h-full overflow-auto custom-scrollbar">
|
|
77
|
+
<div className="mx-auto w-full max-w-[min(1180px,100%)] px-6 py-5">
|
|
78
|
+
<AgentsPage />
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
90
81
|
) : (
|
|
91
82
|
<div className="h-full overflow-hidden">
|
|
92
83
|
<div className="mx-auto flex h-full min-h-0 w-full max-w-[min(1120px,100%)] flex-col px-6 py-5">
|
|
@@ -1,19 +1,5 @@
|
|
|
1
1
|
const SESSION_ROUTE_PREFIX = 'sid_';
|
|
2
2
|
|
|
3
|
-
export function resolveAgentIdFromSessionKey(sessionKey: string): string | null {
|
|
4
|
-
const match = /^agent:([^:]+):/i.exec(sessionKey.trim());
|
|
5
|
-
if (!match) {
|
|
6
|
-
return null;
|
|
7
|
-
}
|
|
8
|
-
const value = match[1]?.trim();
|
|
9
|
-
return value ? value : null;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export function buildNewSessionKey(agentId: string): string {
|
|
13
|
-
const slug = Math.random().toString(36).slice(2, 8);
|
|
14
|
-
return `agent:${agentId}:ui:direct:web-${Date.now().toString(36)}${slug}`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
3
|
export function encodeSessionRouteId(sessionKey: string): string {
|
|
18
4
|
const bytes = new TextEncoder().encode(sessionKey);
|
|
19
5
|
let binary = '';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SessionEntryView } from '@/api/types';
|
|
2
|
+
import { AgentAvatar } from '@/components/common/AgentAvatar';
|
|
2
3
|
import { SessionContextIconNode } from '@/components/common/session-context-icon';
|
|
3
4
|
import { SessionRunBadge } from '@/components/common/SessionRunBadge';
|
|
4
5
|
import { Button } from '@/components/ui/button';
|
|
@@ -15,6 +16,9 @@ type ChatSidebarSessionItemProps = {
|
|
|
15
16
|
runStatus?: SessionRunStatus;
|
|
16
17
|
context: SessionContextView;
|
|
17
18
|
title: string;
|
|
19
|
+
agentId?: string | null;
|
|
20
|
+
agentLabel?: string | null;
|
|
21
|
+
agentAvatarUrl?: string | null;
|
|
18
22
|
isEditing: boolean;
|
|
19
23
|
draftLabel: string;
|
|
20
24
|
isSaving: boolean;
|
|
@@ -32,6 +36,9 @@ export function ChatSidebarSessionItem(props: ChatSidebarSessionItemProps) {
|
|
|
32
36
|
runStatus,
|
|
33
37
|
context,
|
|
34
38
|
title,
|
|
39
|
+
agentId,
|
|
40
|
+
agentLabel,
|
|
41
|
+
agentAvatarUrl,
|
|
35
42
|
isEditing,
|
|
36
43
|
draftLabel,
|
|
37
44
|
isSaving,
|
|
@@ -43,6 +50,10 @@ export function ChatSidebarSessionItem(props: ChatSidebarSessionItemProps) {
|
|
|
43
50
|
} = props;
|
|
44
51
|
|
|
45
52
|
const iconTone = active ? 'text-gray-700' : 'text-gray-500';
|
|
53
|
+
const normalizedAgentId = agentId?.trim() ?? '';
|
|
54
|
+
const shouldShowAgentAvatar = Boolean(
|
|
55
|
+
normalizedAgentId && normalizedAgentId.toLowerCase() !== 'main',
|
|
56
|
+
);
|
|
46
57
|
|
|
47
58
|
return (
|
|
48
59
|
<div
|
|
@@ -105,6 +116,14 @@ export function ChatSidebarSessionItem(props: ChatSidebarSessionItemProps) {
|
|
|
105
116
|
<button type="button" onClick={onSelect} className="w-full text-left">
|
|
106
117
|
<div className="grid grid-cols-[minmax(0,1fr)_0.875rem] items-center gap-1.5 pr-8">
|
|
107
118
|
<span className="flex min-w-0 items-center gap-1.5">
|
|
119
|
+
{shouldShowAgentAvatar ? (
|
|
120
|
+
<AgentAvatar
|
|
121
|
+
agentId={normalizedAgentId}
|
|
122
|
+
displayName={agentLabel}
|
|
123
|
+
avatarUrl={agentAvatarUrl}
|
|
124
|
+
className="h-5 w-5 shrink-0"
|
|
125
|
+
/>
|
|
126
|
+
) : null}
|
|
108
127
|
<span className="truncate font-medium">{title}</span>
|
|
109
128
|
{context.label ? (
|
|
110
129
|
<span
|
|
@@ -129,7 +148,7 @@ export function ChatSidebarSessionItem(props: ChatSidebarSessionItemProps) {
|
|
|
129
148
|
</span>
|
|
130
149
|
</div>
|
|
131
150
|
<div className="mt-0.5 text-[11px] text-gray-400 truncate">
|
|
132
|
-
{session.messageCount} · {formatDateTime(session.updatedAt)}
|
|
151
|
+
{agentLabel?.trim() ? `${agentLabel} · ` : ''}{session.messageCount} · {formatDateTime(session.updatedAt)}
|
|
133
152
|
</div>
|
|
134
153
|
</button>
|
|
135
154
|
<button
|
|
@@ -87,8 +87,9 @@ export function ChatInputBarContainer() {
|
|
|
87
87
|
const inputBarRef = useRef<ChatInputBarHandle | null>(null);
|
|
88
88
|
const fileInputRef = useRef<HTMLInputElement | null>(null);
|
|
89
89
|
|
|
90
|
-
const skillScopeLabels = useMemo<Record<'project' | 'workspace', string>>(() => {
|
|
90
|
+
const skillScopeLabels = useMemo<Record<'builtin' | 'project' | 'workspace', string>>(() => {
|
|
91
91
|
return {
|
|
92
|
+
builtin: t('chatSkillScopeBuiltin'),
|
|
92
93
|
project: t('chatSkillScopeProject'),
|
|
93
94
|
workspace: t('chatSkillScopeWorkspace'),
|
|
94
95
|
};
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from "@/components/chat/adapters/chat-message.adapter";
|
|
13
13
|
import { readInlineTokensFromMetadata } from "@/components/chat/chat-inline-token.utils";
|
|
14
14
|
import { adaptNcpMessageToUiMessage } from "@/components/chat/ncp/ncp-session-adapter";
|
|
15
|
+
import { AgentIdentityAvatar } from "@/components/common/agent-identity";
|
|
15
16
|
import { useI18n } from "@/components/providers/I18nProvider";
|
|
16
17
|
import { formatDateTime, t } from "@/lib/i18n";
|
|
17
18
|
|
|
@@ -129,6 +130,12 @@ export function ChatMessageListContainer({
|
|
|
129
130
|
className={className}
|
|
130
131
|
texts={messageTexts}
|
|
131
132
|
onToolAction={onToolAction}
|
|
133
|
+
renderToolAgent={(agentId) => (
|
|
134
|
+
<AgentIdentityAvatar
|
|
135
|
+
agentId={agentId}
|
|
136
|
+
className="h-4 w-4 shrink-0"
|
|
137
|
+
/>
|
|
138
|
+
)}
|
|
132
139
|
/>
|
|
133
140
|
);
|
|
134
141
|
}
|