@nextclaw/ui 0.12.9 → 0.12.10
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 +61 -0
- package/dist/assets/ChannelsList-M9FTK1Ak.js +8 -0
- package/dist/assets/DocBrowser-CH7-GxlL.js +1 -0
- package/dist/assets/{DocBrowser-6ReNjvzF.js → DocBrowser-DMfr0Oow.js} +1 -1
- package/dist/assets/{DocBrowserContext-B6SpA7Qs.js → DocBrowserContext-BXydqby-.js} +1 -1
- package/dist/assets/{LogoBadge-ByNLYg65.js → LogoBadge-hO7tY7hE.js} +1 -1
- package/dist/assets/ModelConfig-CNIgLf0e.js +1 -0
- package/dist/assets/{ProviderScopedModelInput-Da7khnBA.js → ProviderScopedModelInput-B3HWP4oz.js} +1 -1
- package/dist/assets/ProvidersList-CHjMnRhX.js +1 -0
- package/dist/assets/RuntimeConfig-psp8nMSG.js +1 -0
- package/dist/assets/SearchConfig-CSoKip1f.js +1 -0
- package/dist/assets/{SecretsConfig-D281Rotl.js → SecretsConfig-MEt6MjuD.js} +2 -2
- package/dist/assets/SessionsConfig-DifCiXwR.js +2 -0
- package/dist/assets/{app-query-client-VnFElj4E.js → app-query-client-9jNewezV.js} +1 -1
- package/dist/assets/{book-open-BdcxxoQu.js → book-open-DzdUViDm.js} +1 -1
- package/dist/assets/chat-page-CLp0UV0Y.js +58 -0
- package/dist/assets/chat-session-display-DsYHx0RZ.js +1 -0
- package/dist/assets/{chunk-JZWAC4HX-DK5HPmIK.js → chunk-JZWAC4HX-C5dEc8hV.js} +1 -1
- package/dist/assets/{client-_i4MU2bB.js → client-C-8fH7-c.js} +1 -1
- package/dist/assets/{config-DtIQwrHF.js → config-CBScxsdV.js} +1 -1
- package/dist/assets/config-split-page-BUout_Ak.js +1 -0
- package/dist/assets/{createLucideIcon-BSeTgkZW.js → createLucideIcon-dy5ie7Ox.js} +1 -1
- package/dist/assets/desktop-update-config-2BS6BMkW.js +1 -0
- package/dist/assets/{dist-ccBFUi-o.js → dist-BruyLa92.js} +1 -1
- package/dist/assets/{dist-6TrrnPCR.js → dist-Cy7_j6hA.js} +1 -1
- package/dist/assets/{download-BhDxnyvU.js → download-BD0ETkB-.js} +1 -1
- package/dist/assets/{external-link-BgErLCNT.js → external-link-kZSAO8nT.js} +1 -1
- package/dist/assets/{hash-Bl7dr_UG.js → hash-BHJC2Ovu.js} +1 -1
- package/dist/assets/{i18n-eDHeDY0n.js → i18n-CpTZLchQ.js} +1 -1
- package/dist/assets/index-mW8W2FUu.css +1 -0
- package/dist/assets/index-zDZfXoI4.js +6 -0
- package/dist/assets/{infiniteQueryBehavior-ZDS92Qpp.js → infiniteQueryBehavior-CyER9hv0.js} +1 -1
- package/dist/assets/loader-circle-Bc2gCU33.js +1 -0
- package/dist/assets/{logos-x89HbrZ4.js → logos-B7gRObP8.js} +1 -1
- package/dist/assets/marketplace-page-3qVMnF3d.js +1 -0
- package/dist/assets/marketplace-page-BhFIeQzI.js +49 -0
- package/dist/assets/mcp-marketplace-page-DYfteJ1D.js +40 -0
- package/dist/assets/{page-layout-vZnghcFy.js → page-layout-0UcO9H9Z.js} +1 -1
- package/dist/assets/play-CKDjSQFL.js +1 -0
- package/dist/assets/plus-CG0QrVY_.js +1 -0
- package/dist/assets/{refresh-ccw-DT98i__E.js → refresh-ccw-COVhNHtN.js} +1 -1
- package/dist/assets/{refresh-cw-C47QSEwg.js → refresh-cw-Bcv40SXy.js} +1 -1
- package/dist/assets/remote-access-page-CWHG-sug.js +1 -0
- package/dist/assets/{rotate-cw-JtFzpNn6.js → rotate-cw-oHMKJMC8.js} +1 -1
- package/dist/assets/{save-3S6-H3Xw.js → save-EqJPOF0G.js} +1 -1
- package/dist/assets/search-BCAlB8nz.js +1 -0
- package/dist/assets/security-config-Slh0Mayz.js +1 -0
- package/dist/assets/select-CVz0t7MF.js +41 -0
- package/dist/assets/setting-row-CbVHAuQt.js +1 -0
- package/dist/assets/skeleton-D5rdKvzy.js +1 -0
- package/dist/assets/{status-dot-vbanNPFU.js → status-dot-DpPtVzQT.js} +1 -1
- package/dist/assets/{switch-BsLtHOH-.js → switch-CM29eCAR.js} +1 -1
- package/dist/assets/{tabs-custom-D3HYMt6k.js → tabs-custom-YcZUWn3o.js} +1 -1
- package/dist/assets/tag-chip-DMXdnLcj.js +1 -0
- package/dist/assets/{trash-2-G48scll7.js → trash-2-mJT6oWa2.js} +1 -1
- package/dist/assets/{use-infinite-scroll-loader-DkNhD-42.js → use-infinite-scroll-loader-DJ1L81Dz.js} +1 -1
- package/dist/assets/{useConfirmDialog-BkvTN-vd.js → useConfirmDialog-BsVuqu1x.js} +1 -1
- package/dist/assets/{useMutation-CBWjE2uj.js → useMutation-CNcz2fgt.js} +1 -1
- package/dist/assets/x-Czwxm82I.js +1 -0
- package/dist/index.html +22 -22
- package/dist/runtime-icons/claude.ico +0 -0
- package/dist/runtime-icons/codex-openai.svg +6 -0
- package/dist/runtime-icons/hermes-agent.png +0 -0
- package/package.json +6 -6
- package/public/runtime-icons/claude.ico +0 -0
- package/public/runtime-icons/codex-openai.svg +6 -0
- package/public/runtime-icons/hermes-agent.png +0 -0
- package/src/account/components/account-panel.tsx +217 -97
- package/src/account/managers/account.manager.ts +3 -2
- package/src/api/chat-session-type.types.ts +7 -0
- package/src/api/runtime-control.types.ts +8 -0
- package/src/api/types.ts +8 -0
- package/src/app.tsx +221 -57
- package/src/components/agents/agent-dialogs.tsx +499 -0
- package/src/components/agents/agents-page.test.tsx +238 -0
- package/src/components/agents/agents-page.tsx +435 -0
- package/src/components/chat/ChatSidebar.tsx +11 -35
- package/src/components/chat/chat-conversation-panel.test.tsx +20 -0
- package/src/components/chat/chat-conversation-panel.tsx +83 -13
- package/src/components/chat/chat-page-shell.tsx +19 -13
- package/src/components/chat/chat-session-type-option-item.test.tsx +46 -0
- package/src/components/chat/chat-session-type-option-item.tsx +68 -0
- package/src/components/chat/chat-session-workspace-file-preview.test.tsx +87 -0
- package/src/components/chat/chat-session-workspace-file-preview.tsx +14 -43
- package/src/components/chat/chat-session-workspace-panel-nav.tsx +8 -2
- package/src/components/chat/chat-sidebar-project-groups.tsx +11 -36
- package/src/components/chat/ncp/__tests__/ncp-session-adapter.cancelled-tool.test.ts +77 -0
- package/src/components/chat/ncp/ncp-chat-page.tsx +2 -0
- package/src/components/chat/ncp/ncp-session-adapter.test.ts +1 -0
- package/src/components/chat/ncp/ncp-session-adapter.ts +3 -0
- package/src/components/chat/ncp/page/ncp-chat-derived-state.ts +10 -4
- package/src/components/chat/stores/chat-input.store.ts +2 -1
- package/src/components/chat/stores/chat-thread.store.ts +3 -1
- package/src/components/chat/useChatSessionTypeState.ts +10 -1
- package/src/components/chat/workspace/chat-session-workspace-file-breadcrumbs.tsx +86 -0
- package/src/components/common/BrandHeader.tsx +3 -1
- package/src/components/common/session-context-icon.tsx +15 -2
- package/src/components/common/{TagInput.tsx → tag-input.tsx} +25 -17
- package/src/components/config/ChannelForm.test.tsx +89 -3
- package/src/components/config/ChannelForm.tsx +157 -188
- package/src/components/config/ChannelsList.test.tsx +163 -119
- package/src/components/config/ChannelsList.tsx +90 -101
- package/src/components/config/ProviderForm.tsx +108 -146
- package/src/components/config/ProvidersList.tsx +100 -123
- package/src/components/config/SearchConfig.tsx +423 -393
- package/src/components/config/channel-form-fields-section.tsx +70 -37
- package/src/components/config/config-split-page.tsx +109 -0
- package/src/components/config/provider-enabled-field.tsx +17 -10
- package/src/components/config/runtime-control-card.test.tsx +56 -0
- package/src/components/config/runtime-control-card.tsx +25 -0
- package/src/components/config/runtime-presence-card.tsx +93 -79
- package/src/components/layout/AppLayout.tsx +25 -37
- package/src/components/layout/app-layout.test.tsx +46 -14
- package/src/components/layout/runtime-status-entry.test.tsx +157 -0
- package/src/components/layout/runtime-status-entry.tsx +143 -0
- package/src/components/marketplace/marketplace-detail-doc.ts +93 -0
- package/src/components/marketplace/marketplace-list-card.tsx +288 -0
- package/src/components/marketplace/marketplace-page-data.ts +129 -0
- package/src/components/marketplace/marketplace-page.test.tsx +339 -0
- package/src/components/marketplace/marketplace-page.tsx +596 -0
- package/src/components/marketplace/mcp/mcp-marketplace-card.tsx +128 -0
- package/src/components/marketplace/mcp/mcp-marketplace-dialogs.tsx +191 -0
- package/src/components/marketplace/mcp/mcp-marketplace-doc.ts +152 -0
- package/src/components/marketplace/mcp/mcp-marketplace-page.test.tsx +223 -0
- package/src/components/marketplace/mcp/mcp-marketplace-page.tsx +414 -0
- package/src/components/remote/remote-access-page.test.tsx +105 -0
- package/src/components/remote/remote-access-page.tsx +248 -0
- package/src/components/ui/notice-card.tsx +129 -0
- package/src/components/ui/setting-row.tsx +51 -0
- package/src/components/ui/tag-chip.tsx +39 -0
- package/src/components/ui/textarea.tsx +19 -0
- package/src/hooks/useConfig.ts +2 -1
- package/src/index.css +24 -0
- package/src/lib/app-resource-uri.test.ts +20 -0
- package/src/lib/app-resource-uri.ts +29 -0
- package/src/lib/i18n.remote.ts +1 -1
- package/src/lib/i18n.runtime-control.ts +31 -0
- package/src/lib/i18n.ts +5 -8
- package/src/lib/session-context.utils.test.ts +71 -0
- package/src/lib/session-context.utils.ts +28 -3
- package/src/lib/session-project/workspace-file-breadcrumb.test.ts +83 -0
- package/src/lib/session-project/workspace-file-breadcrumb.ts +188 -0
- package/dist/assets/ChannelsList-Ita2Zm1_.js +0 -8
- package/dist/assets/DocBrowser-BNwbPHf4.js +0 -1
- package/dist/assets/MarketplacePage-CjX2MWww.js +0 -1
- package/dist/assets/MarketplacePage-D0sDlYX4.js +0 -49
- package/dist/assets/McpMarketplacePage-BGKJm1sJ.js +0 -40
- package/dist/assets/ModelConfig-BzZenCH-.js +0 -1
- package/dist/assets/ProvidersList-BbVzRxjY.js +0 -1
- package/dist/assets/RemoteAccessPage-BaDH_X1Q.js +0 -1
- package/dist/assets/RuntimeConfig-F_XKGgLm.js +0 -1
- package/dist/assets/SearchConfig-BGkzXQP-.js +0 -1
- package/dist/assets/SessionsConfig-ChHQ7M5c.js +0 -2
- package/dist/assets/chat-page-Doe0yTtB.js +0 -58
- package/dist/assets/chat-session-display-cw78aiI_.js +0 -1
- package/dist/assets/config-layout-CHs0mAaR.js +0 -1
- package/dist/assets/desktop-update-config-Dpcf4BKG.js +0 -1
- package/dist/assets/index-CF9xve0E.js +0 -6
- package/dist/assets/index-FgA52VBt.css +0 -1
- package/dist/assets/loader-circle-ACM1s51e.js +0 -1
- package/dist/assets/play-CFUwCA2E.js +0 -1
- package/dist/assets/plus-rYsv72JG.js +0 -1
- package/dist/assets/popover-Bg1VoTZ6.js +0 -1
- package/dist/assets/search-3kFR_zh9.js +0 -1
- package/dist/assets/security-config-BWaiARNk.js +0 -1
- package/dist/assets/select-DJ2MUjBB.js +0 -41
- package/dist/assets/skeleton-ByQepn0M.js +0 -1
- package/dist/assets/x-ByDbItbq.js +0 -1
- package/src/components/agents/AgentDialogs.tsx +0 -400
- package/src/components/agents/AgentsPage.test.tsx +0 -217
- package/src/components/agents/AgentsPage.tsx +0 -352
- package/src/components/config/config-layout.ts +0 -10
- package/src/components/marketplace/MarketplacePage.test.tsx +0 -322
- package/src/components/marketplace/MarketplacePage.tsx +0 -827
- package/src/components/marketplace/mcp/McpMarketplacePage.test.tsx +0 -208
- package/src/components/marketplace/mcp/McpMarketplacePage.tsx +0 -580
- package/src/components/remote/RemoteAccessPage.test.tsx +0 -103
- package/src/components/remote/RemoteAccessPage.tsx +0 -144
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { useMemo, useState, type ReactNode } from 'react';
|
|
2
2
|
import { Plus } from 'lucide-react';
|
|
3
3
|
import { Button } from '@/components/ui/button';
|
|
4
|
+
import { ChatSessionTypeOptionItem } from '@/components/chat/chat-session-type-option-item';
|
|
4
5
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
|
5
6
|
import type { ChatInputSnapshot } from '@/components/chat/stores/chat-input.store';
|
|
6
7
|
import type { NcpSessionListItemView } from '@/components/chat/ncp/use-ncp-session-list-view';
|
|
7
8
|
import { t } from '@/lib/i18n';
|
|
8
|
-
import { cn } from '@/lib/utils';
|
|
9
9
|
|
|
10
10
|
export type ChatSidebarProjectGroup = {
|
|
11
11
|
projectRoot: string;
|
|
@@ -34,16 +34,6 @@ function resolveProjectGroupDefaultSessionType(
|
|
|
34
34
|
return sessionTypeOptions[0]?.value ?? defaultSessionType;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
function resolveSessionTypeStatusText(option: {
|
|
38
|
-
ready?: boolean;
|
|
39
|
-
reasonMessage?: string | null;
|
|
40
|
-
}): string {
|
|
41
|
-
if (option.ready === false) {
|
|
42
|
-
return option.reasonMessage?.trim() || t('statusSetup');
|
|
43
|
-
}
|
|
44
|
-
return t('statusReady');
|
|
45
|
-
}
|
|
46
|
-
|
|
47
37
|
export function ChatSidebarProjectGroups(props: ChatSidebarProjectGroupsProps) {
|
|
48
38
|
const { groups, defaultSessionType, sessionTypeOptions, renderSessionItem, onCreateSession } = props;
|
|
49
39
|
const [openProjectRoot, setOpenProjectRoot] = useState<string | null>(null);
|
|
@@ -91,38 +81,23 @@ export function ChatSidebarProjectGroups(props: ChatSidebarProjectGroupsProps) {
|
|
|
91
81
|
<Plus className="h-3.5 w-3.5" />
|
|
92
82
|
</Button>
|
|
93
83
|
</PopoverTrigger>
|
|
94
|
-
<PopoverContent
|
|
95
|
-
|
|
84
|
+
<PopoverContent
|
|
85
|
+
align="end"
|
|
86
|
+
className="w-56 rounded-2xl border border-gray-200/80 bg-white p-1.5 shadow-[0_24px_60px_-28px_rgba(15,23,42,0.38)]"
|
|
87
|
+
>
|
|
88
|
+
<div className="px-3 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-[0.18em] text-gray-400">
|
|
96
89
|
{t('chatSessionTypeLabel')}
|
|
97
90
|
</div>
|
|
98
|
-
<div className="
|
|
91
|
+
<div className="space-y-1">
|
|
99
92
|
{sessionTypeOptions.map((option) => (
|
|
100
|
-
<
|
|
93
|
+
<ChatSessionTypeOptionItem
|
|
101
94
|
key={`${group.projectRoot}:${option.value}`}
|
|
102
|
-
|
|
103
|
-
|
|
95
|
+
option={option}
|
|
96
|
+
onSelect={() => {
|
|
104
97
|
onCreateSession(option.value, group.projectRoot);
|
|
105
98
|
setOpenProjectRoot(null);
|
|
106
99
|
}}
|
|
107
|
-
|
|
108
|
-
>
|
|
109
|
-
<div className="flex items-center justify-between gap-3">
|
|
110
|
-
<div className="text-[13px] font-medium text-gray-900">{option.label}</div>
|
|
111
|
-
<span
|
|
112
|
-
className={cn(
|
|
113
|
-
'shrink-0 rounded-full px-2 py-0.5 text-[10px] font-semibold',
|
|
114
|
-
option.ready === false
|
|
115
|
-
? 'bg-amber-100 text-amber-800'
|
|
116
|
-
: 'bg-emerald-100 text-emerald-700'
|
|
117
|
-
)}
|
|
118
|
-
>
|
|
119
|
-
{option.ready === false ? t('statusSetup') : t('statusReady')}
|
|
120
|
-
</span>
|
|
121
|
-
</div>
|
|
122
|
-
<div className="mt-0.5 text-[11px] text-gray-500">
|
|
123
|
-
{resolveSessionTypeStatusText(option)}
|
|
124
|
-
</div>
|
|
125
|
-
</button>
|
|
100
|
+
/>
|
|
126
101
|
))}
|
|
127
102
|
</div>
|
|
128
103
|
</PopoverContent>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { adaptChatMessage } from '@/components/chat/adapters/chat-message.adapter';
|
|
3
|
+
import { adaptNcpMessageToUiMessage } from '../ncp-session-adapter';
|
|
4
|
+
|
|
5
|
+
const texts = {
|
|
6
|
+
roleLabels: {
|
|
7
|
+
user: 'User',
|
|
8
|
+
assistant: 'Assistant',
|
|
9
|
+
tool: 'Tool',
|
|
10
|
+
system: 'System',
|
|
11
|
+
fallback: 'Message',
|
|
12
|
+
},
|
|
13
|
+
reasoningLabel: 'Reasoning',
|
|
14
|
+
toolCallLabel: 'Tool Call',
|
|
15
|
+
toolResultLabel: 'Tool Result',
|
|
16
|
+
toolInputLabel: 'Input',
|
|
17
|
+
toolNoOutputLabel: 'No output',
|
|
18
|
+
toolOutputLabel: 'Output',
|
|
19
|
+
toolStatusPreparingLabel: 'Preparing',
|
|
20
|
+
toolStatusRunningLabel: 'Running',
|
|
21
|
+
toolStatusCompletedLabel: 'Completed',
|
|
22
|
+
toolStatusFailedLabel: 'Failed',
|
|
23
|
+
toolStatusCancelledLabel: 'Cancelled',
|
|
24
|
+
imageAttachmentLabel: 'Image',
|
|
25
|
+
fileAttachmentLabel: 'File',
|
|
26
|
+
unknownPartLabel: 'Unknown',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
describe('adaptNcpMessageToUiMessage cancelled tools', () => {
|
|
30
|
+
it('renders cancelled tool invocations as cancelled cards', () => {
|
|
31
|
+
const uiMessage = adaptNcpMessageToUiMessage({
|
|
32
|
+
id: 'ncp-message-tool-cancelled-1',
|
|
33
|
+
sessionId: 'ncp-session-1',
|
|
34
|
+
role: 'assistant',
|
|
35
|
+
status: 'final',
|
|
36
|
+
timestamp: '2026-04-01T00:00:00.000Z',
|
|
37
|
+
parts: [
|
|
38
|
+
{
|
|
39
|
+
type: 'tool-invocation',
|
|
40
|
+
toolCallId: 'tool-cancelled-1',
|
|
41
|
+
toolName: 'write_file',
|
|
42
|
+
state: 'cancelled',
|
|
43
|
+
args: JSON.stringify({
|
|
44
|
+
path: 'src/app.ts',
|
|
45
|
+
content: 'hello',
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
const adapted = adaptChatMessage(
|
|
52
|
+
{
|
|
53
|
+
id: uiMessage.id,
|
|
54
|
+
role: uiMessage.role,
|
|
55
|
+
meta: {
|
|
56
|
+
timestamp: uiMessage.meta?.timestamp,
|
|
57
|
+
status: uiMessage.meta?.status,
|
|
58
|
+
},
|
|
59
|
+
parts: uiMessage.parts as never,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
formatTimestamp: (value) => value ?? '',
|
|
63
|
+
texts,
|
|
64
|
+
},
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
expect(adapted.parts[0]).toMatchObject({
|
|
68
|
+
type: 'tool-card',
|
|
69
|
+
card: {
|
|
70
|
+
toolName: 'write_file',
|
|
71
|
+
statusTone: 'cancelled',
|
|
72
|
+
statusLabel: 'Cancelled',
|
|
73
|
+
titleLabel: 'Tool Result',
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -304,6 +304,7 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
304
304
|
currentAgent,
|
|
305
305
|
parentSession,
|
|
306
306
|
currentSessionTypeLabel,
|
|
307
|
+
currentSessionTypeIcon,
|
|
307
308
|
currentChildSessionTabs,
|
|
308
309
|
} = useNcpChatDerivedState({
|
|
309
310
|
sessionKey,
|
|
@@ -340,6 +341,7 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
340
341
|
isSkillsLoading: sessionSkillsQuery.isLoading,
|
|
341
342
|
sessionTypeUnavailableMessage,
|
|
342
343
|
currentSessionTypeLabel,
|
|
344
|
+
currentSessionTypeIcon,
|
|
343
345
|
sessionKey,
|
|
344
346
|
currentAgentId,
|
|
345
347
|
currentAgent,
|
|
@@ -151,6 +151,9 @@ function parseSessionContext(sessionKey: string): { channel?: string; type?: str
|
|
|
151
151
|
}
|
|
152
152
|
|
|
153
153
|
function mapToolStatus(part: Extract<NcpMessagePart, { type: 'tool-invocation' }>): ToolInvocationStatus {
|
|
154
|
+
if (part.state === 'cancelled') {
|
|
155
|
+
return ToolInvocationStatus.CANCELLED;
|
|
156
|
+
}
|
|
154
157
|
if (part.state === 'result') {
|
|
155
158
|
return ToolInvocationStatus.RESULT;
|
|
156
159
|
}
|
|
@@ -11,6 +11,7 @@ import type { NcpChatPresenter } from '@/components/chat/ncp/ncp-chat.presenter'
|
|
|
11
11
|
import type { UseHydratedNcpAgentResult } from '@nextclaw/ncp-react';
|
|
12
12
|
import type { ChatModelOption } from '@/components/chat/chat-input.types';
|
|
13
13
|
import type { ChatChildSessionTab } from '@/components/chat/stores/chat-thread.store';
|
|
14
|
+
import type { ChatSessionTypeOption } from '@/components/chat/useChatSessionTypeState';
|
|
14
15
|
import { resolveSessionTypeLabel } from '@/components/chat/useChatSessionTypeState';
|
|
15
16
|
|
|
16
17
|
function buildChildSessionTabs(params: {
|
|
@@ -40,7 +41,7 @@ export function useNcpChatDerivedState(params: {
|
|
|
40
41
|
parentSessionId: string | null;
|
|
41
42
|
sessionSummaries: NcpSessionSummaryView[];
|
|
42
43
|
selectedSessionType: string;
|
|
43
|
-
sessionTypeOptions:
|
|
44
|
+
sessionTypeOptions: ChatSessionTypeOption[];
|
|
44
45
|
}) {
|
|
45
46
|
const {
|
|
46
47
|
availableAgents,
|
|
@@ -68,9 +69,11 @@ export function useNcpChatDerivedState(params: {
|
|
|
68
69
|
) ?? null;
|
|
69
70
|
return parentSummary ? adaptNcpSessionSummary(parentSummary) : null;
|
|
70
71
|
}, [parentSessionId, sessionSummaries]);
|
|
72
|
+
const currentSessionTypeOption =
|
|
73
|
+
sessionTypeOptions.find((option) => option.value === selectedSessionType) ?? null;
|
|
71
74
|
const currentSessionTypeLabel =
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
currentSessionTypeOption?.label ?? resolveSessionTypeLabel(selectedSessionType);
|
|
76
|
+
const currentSessionTypeIcon = currentSessionTypeOption?.icon ?? null;
|
|
74
77
|
const currentChildSessionTabs = useMemo(
|
|
75
78
|
() =>
|
|
76
79
|
buildChildSessionTabs({
|
|
@@ -86,6 +89,7 @@ export function useNcpChatDerivedState(params: {
|
|
|
86
89
|
currentAgent,
|
|
87
90
|
parentSession,
|
|
88
91
|
currentSessionTypeLabel,
|
|
92
|
+
currentSessionTypeIcon,
|
|
89
93
|
currentChildSessionTabs,
|
|
90
94
|
};
|
|
91
95
|
}
|
|
@@ -99,7 +103,7 @@ export function useNcpChatSnapshotSync(params: {
|
|
|
99
103
|
lastSendError: string | null;
|
|
100
104
|
isSending: boolean;
|
|
101
105
|
modelOptions: ChatModelOption[];
|
|
102
|
-
sessionTypeOptions:
|
|
106
|
+
sessionTypeOptions: ChatSessionTypeOption[];
|
|
103
107
|
selectedSessionType: string;
|
|
104
108
|
canEditSessionType: boolean;
|
|
105
109
|
sessionTypeUnavailable: boolean;
|
|
@@ -107,6 +111,7 @@ export function useNcpChatSnapshotSync(params: {
|
|
|
107
111
|
isSkillsLoading: boolean;
|
|
108
112
|
sessionTypeUnavailableMessage: string | null;
|
|
109
113
|
currentSessionTypeLabel: string;
|
|
114
|
+
currentSessionTypeIcon: ChatSessionTypeOption['icon'];
|
|
110
115
|
sessionKey: string;
|
|
111
116
|
currentAgentId: string;
|
|
112
117
|
currentAgent: AgentProfileView | null;
|
|
@@ -145,6 +150,7 @@ export function useNcpChatSnapshotSync(params: {
|
|
|
145
150
|
sessionTypeUnavailable: params.sessionTypeUnavailable,
|
|
146
151
|
sessionTypeUnavailableMessage: params.sessionTypeUnavailableMessage,
|
|
147
152
|
sessionTypeLabel: params.currentSessionTypeLabel,
|
|
153
|
+
sessionTypeIcon: params.currentSessionTypeIcon,
|
|
148
154
|
sessionKey: params.sessionKey,
|
|
149
155
|
agentId: params.currentAgentId,
|
|
150
156
|
agentDisplayName: params.currentAgent?.displayName ?? null,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { create } from 'zustand';
|
|
2
2
|
import type { ChatComposerNode } from '@nextclaw/agent-chat-ui';
|
|
3
3
|
import type { NcpDraftAttachment } from '@nextclaw/ncp-react';
|
|
4
|
-
import type { SessionSkillEntryView, ThinkingLevel } from '@/api/types';
|
|
4
|
+
import type { SessionSkillEntryView, SessionTypeIconView, ThinkingLevel } from '@/api/types';
|
|
5
5
|
import type { ChatModelOption } from '@/components/chat/chat-input.types';
|
|
6
6
|
import { createInitialChatComposerNodes } from '@/components/chat/chat-composer-state';
|
|
7
7
|
|
|
@@ -24,6 +24,7 @@ export type ChatInputSnapshot = {
|
|
|
24
24
|
sessionTypeOptions: Array<{
|
|
25
25
|
value: string;
|
|
26
26
|
label: string;
|
|
27
|
+
icon?: SessionTypeIconView | null;
|
|
27
28
|
ready?: boolean;
|
|
28
29
|
reason?: string | null;
|
|
29
30
|
reasonMessage?: string | null;
|
|
@@ -3,7 +3,7 @@ import type { MutableRefObject } from 'react';
|
|
|
3
3
|
import type { NcpMessage } from '@nextclaw/ncp';
|
|
4
4
|
import type { ChatFileOperationLineViewModel } from '@nextclaw/agent-chat-ui';
|
|
5
5
|
import type { ChatModelOption } from '@/components/chat/chat-input.types';
|
|
6
|
-
import type { AgentProfileView } from '@/api/types';
|
|
6
|
+
import type { AgentProfileView, SessionTypeIconView } from '@/api/types';
|
|
7
7
|
|
|
8
8
|
export type ChatChildSessionTab = {
|
|
9
9
|
sessionKey: string;
|
|
@@ -35,6 +35,7 @@ export type ChatThreadSnapshot = {
|
|
|
35
35
|
sessionTypeUnavailable: boolean;
|
|
36
36
|
sessionTypeUnavailableMessage?: string | null;
|
|
37
37
|
sessionTypeLabel?: string | null;
|
|
38
|
+
sessionTypeIcon?: SessionTypeIconView | null;
|
|
38
39
|
sessionKey: string | null;
|
|
39
40
|
agentId?: string | null;
|
|
40
41
|
agentDisplayName?: string | null;
|
|
@@ -70,6 +71,7 @@ const initialSnapshot: ChatThreadSnapshot = {
|
|
|
70
71
|
sessionTypeUnavailable: false,
|
|
71
72
|
sessionTypeUnavailableMessage: null,
|
|
72
73
|
sessionTypeLabel: null,
|
|
74
|
+
sessionTypeIcon: null,
|
|
73
75
|
sessionKey: null,
|
|
74
76
|
agentId: null,
|
|
75
77
|
agentDisplayName: null,
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { useEffect, useMemo, useRef } from 'react';
|
|
2
2
|
import type { Dispatch, SetStateAction } from 'react';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
AgentProfileView,
|
|
5
|
+
ChatSessionTypeOptionView,
|
|
6
|
+
SessionEntryView,
|
|
7
|
+
SessionTypeIconView
|
|
8
|
+
} from '@/api/types';
|
|
4
9
|
import { t } from '@/lib/i18n';
|
|
5
10
|
|
|
6
11
|
export const DEFAULT_SESSION_TYPE = 'native';
|
|
@@ -8,6 +13,7 @@ export const DEFAULT_SESSION_TYPE = 'native';
|
|
|
8
13
|
export type ChatSessionTypeOption = {
|
|
9
14
|
value: string;
|
|
10
15
|
label: string;
|
|
16
|
+
icon: SessionTypeIconView | null;
|
|
11
17
|
ready: boolean;
|
|
12
18
|
reason?: string | null;
|
|
13
19
|
reasonMessage?: string | null;
|
|
@@ -71,6 +77,7 @@ export function buildSessionTypeOptions(
|
|
|
71
77
|
deduped.set(value, {
|
|
72
78
|
value,
|
|
73
79
|
label: option.label?.trim() || resolveSessionTypeLabel(value),
|
|
80
|
+
icon: option.icon ?? null,
|
|
74
81
|
ready: option.ready ?? true,
|
|
75
82
|
reason: option.reason ?? null,
|
|
76
83
|
reasonMessage: option.reasonMessage ?? null,
|
|
@@ -83,6 +90,7 @@ export function buildSessionTypeOptions(
|
|
|
83
90
|
deduped.set(DEFAULT_SESSION_TYPE, {
|
|
84
91
|
value: DEFAULT_SESSION_TYPE,
|
|
85
92
|
label: resolveSessionTypeLabel(DEFAULT_SESSION_TYPE),
|
|
93
|
+
icon: null,
|
|
86
94
|
ready: true,
|
|
87
95
|
reason: null,
|
|
88
96
|
reasonMessage: null,
|
|
@@ -129,6 +137,7 @@ export function useChatSessionTypeState(params: UseChatSessionTypeStateParams):
|
|
|
129
137
|
options.push({
|
|
130
138
|
value: currentSessionType,
|
|
131
139
|
label: resolveSessionTypeLabel(currentSessionType),
|
|
140
|
+
icon: null,
|
|
132
141
|
ready: true,
|
|
133
142
|
reason: null,
|
|
134
143
|
reasonMessage: null,
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Fragment } from "react";
|
|
2
|
+
import { ChevronRight, FileCode2, FolderTree } from "lucide-react";
|
|
3
|
+
import type { WorkspaceFileBreadcrumbViewModel } from "@/lib/session-project/workspace-file-breadcrumb";
|
|
4
|
+
import { t } from "@/lib/i18n";
|
|
5
|
+
import { cn } from "@/lib/utils";
|
|
6
|
+
|
|
7
|
+
function WorkspaceBreadcrumbMetaChip({
|
|
8
|
+
tone = "neutral",
|
|
9
|
+
value,
|
|
10
|
+
}: {
|
|
11
|
+
tone?: "neutral" | "warning";
|
|
12
|
+
value: string;
|
|
13
|
+
}) {
|
|
14
|
+
return (
|
|
15
|
+
<span
|
|
16
|
+
className={cn(
|
|
17
|
+
"inline-flex h-5 items-center rounded-sm border px-1.5 text-[10px] font-medium leading-none",
|
|
18
|
+
tone === "warning"
|
|
19
|
+
? "border-amber-200 bg-amber-50 text-amber-700"
|
|
20
|
+
: "border-gray-200 bg-gray-50 text-gray-500",
|
|
21
|
+
)}
|
|
22
|
+
>
|
|
23
|
+
{value}
|
|
24
|
+
</span>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function ChatSessionWorkspaceFileBreadcrumbs({
|
|
29
|
+
breadcrumb,
|
|
30
|
+
}: {
|
|
31
|
+
breadcrumb: WorkspaceFileBreadcrumbViewModel;
|
|
32
|
+
}) {
|
|
33
|
+
return (
|
|
34
|
+
<div
|
|
35
|
+
data-testid="workspace-file-breadcrumbs"
|
|
36
|
+
title={breadcrumb.fullPath}
|
|
37
|
+
className="workspace-horizontal-scrollbar overflow-x-auto overflow-y-hidden border-b border-gray-200/80 bg-gray-50/55"
|
|
38
|
+
>
|
|
39
|
+
<div
|
|
40
|
+
data-testid="workspace-file-breadcrumb-scroll"
|
|
41
|
+
className="flex min-w-max items-center gap-2.5 px-3 py-1.5"
|
|
42
|
+
>
|
|
43
|
+
<div className="flex min-w-0 flex-1 items-center gap-1 pr-1">
|
|
44
|
+
{breadcrumb.segments.map((segment, index) => (
|
|
45
|
+
<Fragment key={segment.key}>
|
|
46
|
+
<span
|
|
47
|
+
className={cn(
|
|
48
|
+
"inline-flex h-5 items-center gap-1 rounded-sm px-1 text-[11px] leading-none",
|
|
49
|
+
segment.kind === "workspace"
|
|
50
|
+
? "bg-primary/8 text-primary"
|
|
51
|
+
: segment.isCurrent
|
|
52
|
+
? "bg-gray-200/70 text-gray-900"
|
|
53
|
+
: "text-gray-500",
|
|
54
|
+
)}
|
|
55
|
+
>
|
|
56
|
+
{segment.kind === "workspace" ? (
|
|
57
|
+
<FolderTree className="h-3 w-3 shrink-0" />
|
|
58
|
+
) : segment.isCurrent ? (
|
|
59
|
+
<FileCode2 className="h-3 w-3 shrink-0" />
|
|
60
|
+
) : null}
|
|
61
|
+
<span>{segment.label}</span>
|
|
62
|
+
</span>
|
|
63
|
+
{index < breadcrumb.segments.length - 1 ? (
|
|
64
|
+
<ChevronRight className="h-3 w-3 shrink-0 text-gray-300" />
|
|
65
|
+
) : null}
|
|
66
|
+
</Fragment>
|
|
67
|
+
))}
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
{breadcrumb.locationLabel || breadcrumb.truncated ? (
|
|
71
|
+
<div className="flex shrink-0 flex-wrap items-center justify-end gap-1">
|
|
72
|
+
{breadcrumb.locationLabel ? (
|
|
73
|
+
<WorkspaceBreadcrumbMetaChip value={breadcrumb.locationLabel} />
|
|
74
|
+
) : null}
|
|
75
|
+
{breadcrumb.truncated ? (
|
|
76
|
+
<WorkspaceBreadcrumbMetaChip
|
|
77
|
+
tone="warning"
|
|
78
|
+
value={t("chatWorkspacePreviewTruncated")}
|
|
79
|
+
/>
|
|
80
|
+
) : null}
|
|
81
|
+
</div>
|
|
82
|
+
) : null}
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useAppMeta } from '@/hooks/useConfig';
|
|
2
2
|
import type { ReactNode } from 'react';
|
|
3
|
+
import { RuntimeStatusEntry } from '@/components/layout/runtime-status-entry';
|
|
3
4
|
|
|
4
5
|
type BrandHeaderProps = {
|
|
5
6
|
className?: string;
|
|
@@ -10,6 +11,7 @@ export function BrandHeader({ className, suffix }: BrandHeaderProps) {
|
|
|
10
11
|
const { data } = useAppMeta();
|
|
11
12
|
const productName = data?.name ?? 'NextClaw';
|
|
12
13
|
const productVersion = data?.productVersion?.trim();
|
|
14
|
+
const resolvedSuffix = suffix ?? <RuntimeStatusEntry />;
|
|
13
15
|
|
|
14
16
|
return (
|
|
15
17
|
<div className={className ?? 'flex items-center gap-2.5'}>
|
|
@@ -19,7 +21,7 @@ export function BrandHeader({ className, suffix }: BrandHeaderProps) {
|
|
|
19
21
|
<div className="flex items-baseline gap-2 min-w-0">
|
|
20
22
|
<span className="truncate text-[15px] font-semibold tracking-[-0.01em] text-gray-800">{productName}</span>
|
|
21
23
|
{productVersion ? <span className="text-[13px] font-medium text-gray-500">v{productVersion}</span> : null}
|
|
22
|
-
{
|
|
24
|
+
{resolvedSuffix ? <span className="inline-flex items-center shrink-0">{resolvedSuffix}</span> : null}
|
|
23
25
|
</div>
|
|
24
26
|
</div>
|
|
25
27
|
);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type SessionContextIcon } from '@/lib/session-context.utils';
|
|
2
|
+
import { resolveAppResourceUri } from '@/lib/app-resource-uri';
|
|
2
3
|
import { LogoBadge } from '@/components/common/LogoBadge';
|
|
3
4
|
import { getChannelLogo } from '@/lib/logos';
|
|
4
5
|
import { cn } from '@/lib/utils';
|
|
@@ -8,6 +9,18 @@ export function SessionContextIconNode({ icon, className }: { icon: SessionConte
|
|
|
8
9
|
if (icon.kind === 'channel-logo') {
|
|
9
10
|
return <ChannelLogoIcon channel={icon.channel} className={className} />;
|
|
10
11
|
}
|
|
12
|
+
if (icon.kind === 'runtime-image') {
|
|
13
|
+
const runtimeIconSrc = resolveAppResourceUri(icon.src);
|
|
14
|
+
return (
|
|
15
|
+
<LogoBadge
|
|
16
|
+
name={icon.name?.trim() || icon.alt?.trim() || 'runtime'}
|
|
17
|
+
src={runtimeIconSrc ?? undefined}
|
|
18
|
+
className={cn('h-[1.125rem] w-[1.125rem]', className)}
|
|
19
|
+
imgClassName="h-full w-full object-contain"
|
|
20
|
+
fallback={<Bot className={cn('h-3 w-3 text-gray-500', className)} />}
|
|
21
|
+
/>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
11
24
|
if (icon.icon === 'heartbeat') {
|
|
12
25
|
return <HeartPulse className={cn('h-3.5 w-3.5', className)} />;
|
|
13
26
|
}
|
|
@@ -22,8 +35,8 @@ function ChannelLogoIcon(
|
|
|
22
35
|
<LogoBadge
|
|
23
36
|
name={channel}
|
|
24
37
|
src={logoSrc}
|
|
25
|
-
className={cn('h-
|
|
26
|
-
imgClassName="h-
|
|
38
|
+
className={cn('h-[1.125rem] w-[1.125rem]', className)}
|
|
39
|
+
imgClassName="h-full w-full object-contain"
|
|
27
40
|
fallback={<Bot className="h-3 w-3 text-gray-500" />}
|
|
28
41
|
/>
|
|
29
42
|
);
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { useState } from
|
|
2
|
-
import { X } from
|
|
3
|
-
import { cn } from
|
|
4
|
-
import { t } from
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { X } from "lucide-react";
|
|
3
|
+
import { cn } from "@/lib/utils";
|
|
4
|
+
import { t } from "@/lib/i18n";
|
|
5
|
+
import { TagChip } from "@/components/ui/tag-chip";
|
|
5
6
|
|
|
6
7
|
interface TagInputProps {
|
|
7
8
|
value: string[];
|
|
@@ -10,15 +11,20 @@ interface TagInputProps {
|
|
|
10
11
|
placeholder?: string;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
export function TagInput({
|
|
14
|
-
|
|
14
|
+
export function TagInput({
|
|
15
|
+
value,
|
|
16
|
+
onChange,
|
|
17
|
+
className,
|
|
18
|
+
placeholder = "",
|
|
19
|
+
}: TagInputProps) {
|
|
20
|
+
const [input, setInput] = useState("");
|
|
15
21
|
|
|
16
22
|
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
|
17
|
-
if (e.key ===
|
|
23
|
+
if (e.key === "Enter" && input.trim()) {
|
|
18
24
|
e.preventDefault();
|
|
19
25
|
onChange([...value, input.trim()]);
|
|
20
|
-
setInput(
|
|
21
|
-
} else if (e.key ===
|
|
26
|
+
setInput("");
|
|
27
|
+
} else if (e.key === "Backspace" && !input && value.length > 0) {
|
|
22
28
|
onChange(value.slice(0, -1));
|
|
23
29
|
}
|
|
24
30
|
};
|
|
@@ -28,21 +34,23 @@ export function TagInput({ value, onChange, className, placeholder = '' }: TagIn
|
|
|
28
34
|
};
|
|
29
35
|
|
|
30
36
|
return (
|
|
31
|
-
<div
|
|
37
|
+
<div
|
|
38
|
+
className={cn(
|
|
39
|
+
"flex min-h-[42px] flex-wrap gap-2 rounded-xl border border-gray-200/80 bg-white p-2",
|
|
40
|
+
className,
|
|
41
|
+
)}
|
|
42
|
+
>
|
|
32
43
|
{value.map((tag, index) => (
|
|
33
|
-
<
|
|
34
|
-
key={index}
|
|
35
|
-
className="inline-flex items-center gap-1 px-2 py-1 bg-primary text-primary-foreground rounded text-sm"
|
|
36
|
-
>
|
|
44
|
+
<TagChip key={index} tone="info" className="gap-1 px-2 py-1 text-sm">
|
|
37
45
|
{tag}
|
|
38
46
|
<button
|
|
39
47
|
type="button"
|
|
40
48
|
onClick={() => removeTag(index)}
|
|
41
|
-
className="hover:text-
|
|
49
|
+
className="transition-colors hover:text-rose-200"
|
|
42
50
|
>
|
|
43
51
|
<X className="h-3 w-3" />
|
|
44
52
|
</button>
|
|
45
|
-
</
|
|
53
|
+
</TagChip>
|
|
46
54
|
))}
|
|
47
55
|
<input
|
|
48
56
|
type="text"
|
|
@@ -50,7 +58,7 @@ export function TagInput({ value, onChange, className, placeholder = '' }: TagIn
|
|
|
50
58
|
onChange={(e) => setInput(e.target.value)}
|
|
51
59
|
onKeyDown={handleKeyDown}
|
|
52
60
|
className="flex-1 outline-none min-w-[100px] bg-transparent text-sm"
|
|
53
|
-
placeholder={placeholder || t(
|
|
61
|
+
placeholder={placeholder || t("enterTag")}
|
|
54
62
|
/>
|
|
55
63
|
</div>
|
|
56
64
|
);
|