@nextclaw/ui 0.12.9 → 0.12.11
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 +102 -0
- package/dist/assets/ChannelsList-SQ7Oxotv.js +8 -0
- package/dist/assets/DocBrowser-BCO2k6XD.js +1 -0
- package/dist/assets/{DocBrowser-6ReNjvzF.js → DocBrowser-rDOjI3ga.js} +1 -1
- package/dist/assets/{DocBrowserContext-B6SpA7Qs.js → DocBrowserContext-BUq3Wo8O.js} +1 -1
- package/dist/assets/{LogoBadge-ByNLYg65.js → LogoBadge-DP8Ye7wJ.js} +1 -1
- package/dist/assets/ModelConfig-C77Ae9ru.js +1 -0
- package/dist/assets/ProviderScopedModelInput-CEnK61uo.js +1 -0
- package/dist/assets/ProvidersList-BCupBayq.js +1 -0
- package/dist/assets/RuntimeConfig-Ad-CAcmy.js +1 -0
- package/dist/assets/SearchConfig-BfCz4wJ4.js +1 -0
- package/dist/assets/SecretsConfig-DjmBIhyy.js +3 -0
- package/dist/assets/{SessionsConfig-ChHQ7M5c.js → SessionsConfig-CvjxU40H.js} +2 -2
- package/dist/assets/{book-open-BdcxxoQu.js → book-open-BE8M56IM.js} +1 -1
- package/dist/assets/chat-page-JKC6ln-y.js +58 -0
- package/dist/assets/chat-session-display-YcRMrAMa.js +1 -0
- package/dist/assets/{chunk-JZWAC4HX-DK5HPmIK.js → chunk-JZWAC4HX-erTUn3b8.js} +1 -1
- package/dist/assets/client-CszWMVKi.js +7 -0
- package/dist/assets/config-split-page-BAGSzUR3.js +1 -0
- package/dist/assets/{createLucideIcon-BSeTgkZW.js → createLucideIcon-CCiTGX8L.js} +1 -1
- package/dist/assets/desktop-DfkLlkG2.js +1 -0
- package/dist/assets/desktop-update-config-BXeGlqHD.js +1 -0
- package/dist/assets/dialog-BghZFPch.js +5 -0
- package/dist/assets/{dist-6TrrnPCR.js → dist-Dd9cr-kz.js} +1 -1
- package/dist/assets/dist-ZwoAXs46.js +9 -0
- package/dist/assets/{download-BhDxnyvU.js → download-D7LOizcW.js} +1 -1
- package/dist/assets/es2015-CEAreese.js +41 -0
- package/dist/assets/{external-link-BgErLCNT.js → external-link-qsnCMhw1.js} +1 -1
- package/dist/assets/{hash-Bl7dr_UG.js → hash-0zjWsNl-.js} +1 -1
- package/dist/assets/{i18n-eDHeDY0n.js → i18n-DvzXOGQX.js} +1 -1
- package/dist/assets/index-DvVTC9FF.css +1 -0
- package/dist/assets/index-lr6rQUSd.js +2 -0
- package/dist/assets/key-round-BLe9D8ND.js +1 -0
- package/dist/assets/loader-circle-wj7kARHv.js +1 -0
- package/dist/assets/{logos-x89HbrZ4.js → logos-_v5b2SdG.js} +1 -1
- package/dist/assets/marketplace-page-CAAk1Khc.js +1 -0
- package/dist/assets/marketplace-page-CfCiq90S.js +49 -0
- package/dist/assets/mcp-marketplace-page-D0Pp9Hs-.js +40 -0
- package/dist/assets/play-o6NmwGTi.js +1 -0
- package/dist/assets/plus-I9pBS4Fl.js +1 -0
- package/dist/assets/{refresh-cw-C47QSEwg.js → refresh-cw-MNqgR3LZ.js} +1 -1
- package/dist/assets/remote-C9fXm4V5.js +1 -0
- package/dist/assets/{save-3S6-H3Xw.js → save-D4bObrmH.js} +1 -1
- package/dist/assets/search-DxmL3IWE.js +1 -0
- package/dist/assets/security-config-BUm6FFfl.js +1 -0
- package/dist/assets/select-BILPf7zs.js +1 -0
- package/dist/assets/setting-row-BATDgg4r.js +1 -0
- package/dist/assets/skeleton-COKMAnJy.js +1 -0
- package/dist/assets/{switch-BsLtHOH-.js → switch-CBOzecWS.js} +1 -1
- package/dist/assets/{tabs-custom-D3HYMt6k.js → tabs-custom-Bx3cNhD-.js} +1 -1
- package/dist/assets/tag-chip-zUaDE2-H.js +1 -0
- package/dist/assets/{trash-2-G48scll7.js → trash-2-CQUgYyRn.js} +1 -1
- package/dist/assets/use-infinite-scroll-loader-B5V2Klve.js +1 -0
- package/dist/assets/useConfirmDialog-patAnl1g.js +1 -0
- package/dist/assets/{useMutation-CBWjE2uj.js → useMutation-__AYv-Pz.js} +1 -1
- package/dist/assets/x-BHUGQIUv.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/module-structure.config.json +7 -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/api/chat-session-type.types.ts +7 -0
- package/src/api/config.ts +10 -0
- package/src/api/raw-client.test.ts +1 -1
- package/src/api/{raw-client.ts → raw-client.utils.ts} +2 -0
- package/src/api/runtime-control.types.ts +8 -0
- package/src/api/types.ts +48 -0
- package/src/app/components/app-manager-provider.tsx +20 -0
- package/src/app/managers/app.manager.ts +12 -0
- package/src/app.tsx +223 -59
- 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/chat-conversation-panel.test.tsx +30 -0
- package/src/components/chat/chat-conversation-panel.tsx +83 -13
- package/src/components/chat/chat-input/ncp-chat-input-availability.utils.test.ts +92 -0
- package/src/components/chat/chat-input/ncp-chat-input-availability.utils.ts +45 -0
- 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/containers/chat-input-bar.container.tsx +24 -12
- package/src/components/chat/{ChatSidebar.test.tsx → containers/chat-sidebar.test.tsx} +5 -4
- package/src/components/chat/{ChatSidebar.tsx → containers/chat-sidebar.tsx} +24 -72
- package/src/components/chat/hooks/use-chat-sidebar-session-label-editor.ts +49 -0
- package/src/components/chat/ncp/__tests__/ncp-session-adapter.cancelled-tool.test.ts +77 -0
- package/src/components/chat/ncp/ncp-app-client-fetch.ts +3 -0
- package/src/components/chat/ncp/ncp-chat-input.manager.ts +13 -5
- package/src/components/chat/ncp/ncp-chat-page.tsx +23 -2
- 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/ncp/session-conversation/use-ncp-session-conversation.test.tsx +48 -4
- package/src/components/chat/ncp/session-conversation/use-ncp-session-conversation.ts +43 -5
- package/src/components/chat/ncp/tests/ncp-chat-input.manager.test.ts +51 -1
- 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/desktop-update-config.test.tsx +10 -4
- package/src/components/config/desktop-update-config.tsx +5 -3
- package/src/components/config/provider-enabled-field.tsx +17 -10
- package/src/components/config/runtime-control-card.test.tsx +136 -158
- package/src/components/config/runtime-control-card.tsx +43 -68
- package/src/components/config/runtime-presence-card.test.tsx +10 -14
- package/src/components/config/runtime-presence-card.tsx +97 -81
- package/src/components/layout/AppLayout.tsx +25 -37
- package/src/components/layout/Sidebar.tsx +4 -4
- package/src/components/layout/app-layout.test.tsx +46 -14
- package/src/components/layout/runtime-status-entry.test.tsx +101 -0
- package/src/components/layout/runtime-status-entry.tsx +95 -0
- package/src/components/layout/sidebar.layout.test.tsx +11 -5
- 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/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/features/account/components/account-panel.tsx +255 -0
- package/src/features/account/index.ts +6 -0
- package/src/{account → features/account}/managers/account.manager.ts +6 -5
- package/src/features/remote/components/remote-access-page.test.tsx +104 -0
- package/src/features/remote/components/remote-access-page.tsx +250 -0
- package/src/{hooks/useRemoteAccess.ts → features/remote/hooks/use-remote-access.ts} +1 -1
- package/src/features/remote/index.ts +27 -0
- package/src/{remote → features/remote}/managers/remote-access.manager.ts +3 -4
- package/src/{remote → features/remote/services}/remote-access-feedback.service.test.ts +1 -1
- package/src/features/system-status/hooks/use-system-status.ts +104 -0
- package/src/features/system-status/index.ts +12 -0
- package/src/features/system-status/managers/system-status.manager.bootstrap-polling.test.ts +126 -0
- package/src/features/system-status/managers/system-status.manager.test.ts +142 -0
- package/src/features/system-status/managers/system-status.manager.ts +511 -0
- package/src/features/system-status/stores/system-status.store.ts +32 -0
- package/src/features/system-status/types/system-status.types.ts +73 -0
- package/src/features/system-status/utils/system-status.utils.test.ts +132 -0
- package/src/features/system-status/utils/system-status.utils.ts +202 -0
- package/src/hooks/use-realtime-query-bridge.ts +34 -18
- 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.chat.ts +8 -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/src/platforms/desktop/index.ts +20 -0
- package/src/{desktop → platforms/desktop}/managers/desktop-presence.manager.ts +2 -2
- package/src/{desktop → platforms/desktop}/managers/desktop-update.manager.ts +2 -2
- package/src/{desktop → platforms/desktop}/stores/desktop-presence.store.ts +1 -1
- package/src/{desktop → platforms/desktop}/stores/desktop-update.store.ts +1 -1
- package/src/stores/ui.store.ts +0 -9
- package/src/transport/{app-client.ts → app-client.service.ts} +9 -9
- package/src/transport/app-client.test.ts +9 -5
- package/src/transport/index.ts +1 -1
- package/src/transport/{local.transport.ts → local-transport.service.ts} +14 -12
- 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/ProviderScopedModelInput-Da7khnBA.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/SecretsConfig-D281Rotl.js +0 -3
- package/dist/assets/app-query-client-VnFElj4E.js +0 -1
- package/dist/assets/chat-page-Doe0yTtB.js +0 -58
- package/dist/assets/chat-session-display-cw78aiI_.js +0 -1
- package/dist/assets/client-_i4MU2bB.js +0 -7
- package/dist/assets/config-DtIQwrHF.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/dist-ccBFUi-o.js +0 -9
- package/dist/assets/index-CF9xve0E.js +0 -6
- package/dist/assets/index-FgA52VBt.css +0 -1
- package/dist/assets/infiniteQueryBehavior-ZDS92Qpp.js +0 -1
- package/dist/assets/loader-circle-ACM1s51e.js +0 -1
- package/dist/assets/page-layout-vZnghcFy.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/refresh-ccw-DT98i__E.js +0 -1
- package/dist/assets/rotate-cw-JtFzpNn6.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/status-dot-vbanNPFU.js +0 -1
- package/dist/assets/use-infinite-scroll-loader-DkNhD-42.js +0 -1
- package/dist/assets/useConfirmDialog-BkvTN-vd.js +0 -1
- package/dist/assets/x-ByDbItbq.js +0 -1
- package/src/account/components/account-panel.tsx +0 -135
- 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
- package/src/hooks/use-runtime-control.ts +0 -24
- package/src/presenter/app-presenter-context.tsx +0 -20
- package/src/presenter/app.presenter.ts +0 -12
- package/src/runtime-control/runtime-control.manager.ts +0 -118
- /package/dist/assets/{config-hints-BhTmc9P1.js → config-hints-DSQQbeOA.js} +0 -0
- /package/src/{account → features/account}/stores/account.store.ts +0 -0
- /package/src/{remote → features/remote/services}/remote-access-feedback.service.ts +0 -0
- /package/src/{remote/remote-access.query.ts → features/remote/services/remote-access-query.service.ts} +0 -0
- /package/src/{remote → features/remote}/stores/remote-access.store.ts +0 -0
- /package/src/{desktop → platforms/desktop/types}/desktop-update.types.ts +0 -0
|
@@ -22,7 +22,10 @@ import {
|
|
|
22
22
|
} from "@/components/chat/chat-session-route";
|
|
23
23
|
import { useNcpChatPageData } from "@/components/chat/ncp/ncp-chat-page-data";
|
|
24
24
|
import { NcpChatPresenter } from "@/components/chat/ncp/ncp-chat.presenter";
|
|
25
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
isNcpAgentStartupUnavailableErrorMessage,
|
|
27
|
+
useNcpSessionConversation,
|
|
28
|
+
} from "@/components/chat/ncp/session-conversation/use-ncp-session-conversation";
|
|
26
29
|
import { useNcpChatDerivedState, useNcpChatSnapshotSync } from "@/components/chat/ncp/page/ncp-chat-derived-state";
|
|
27
30
|
import { ChatPresenterProvider } from "@/components/chat/presenter/chat-presenter-context";
|
|
28
31
|
import type { ResumeRunParams } from "@/components/chat/chat-stream/types";
|
|
@@ -31,6 +34,10 @@ import { useChatSessionListStore } from "@/components/chat/stores/chat-session-l
|
|
|
31
34
|
import { useConfirmDialog } from "@/hooks/useConfirmDialog";
|
|
32
35
|
import { useAgents } from "@/hooks/agents/useAgents";
|
|
33
36
|
import { normalizeRequestedSkills } from "@/lib/chat-runtime-utils";
|
|
37
|
+
import {
|
|
38
|
+
systemStatusManager,
|
|
39
|
+
useChatRuntimeAvailability,
|
|
40
|
+
} from "@/features/system-status";
|
|
34
41
|
import {
|
|
35
42
|
getSessionProjectName,
|
|
36
43
|
normalizeSessionProjectRootValue,
|
|
@@ -119,6 +126,7 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
119
126
|
const pendingProjectRootSessionKey = useChatInputStore(
|
|
120
127
|
(state) => state.snapshot.pendingProjectRootSessionKey,
|
|
121
128
|
);
|
|
129
|
+
const runtimeAvailability = useChatRuntimeAvailability();
|
|
122
130
|
const agentsQuery = useAgents();
|
|
123
131
|
const currentSelectedModel = useChatInputStore(
|
|
124
132
|
(state) => state.snapshot.selectedModel,
|
|
@@ -182,8 +190,19 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
182
190
|
const isAwaitingAssistantOutput = agent.isRunning;
|
|
183
191
|
const canStopCurrentRun = agent.isRunning;
|
|
184
192
|
const stopDisabledReason = agent.isRunning ? null : "__preparing__";
|
|
185
|
-
const
|
|
193
|
+
const rawLastSendError =
|
|
186
194
|
agent.hydrateError?.message ?? agent.snapshot.error?.message ?? null;
|
|
195
|
+
const filteredLastSendError =
|
|
196
|
+
runtimeAvailability.phase === "ready" &&
|
|
197
|
+
isNcpAgentStartupUnavailableErrorMessage(rawLastSendError)
|
|
198
|
+
? null
|
|
199
|
+
: rawLastSendError;
|
|
200
|
+
const lastSendError =
|
|
201
|
+
runtimeAvailability.isBlocked
|
|
202
|
+
? null
|
|
203
|
+
: runtimeAvailability.phase === "ready"
|
|
204
|
+
? filteredLastSendError
|
|
205
|
+
: systemStatusManager.getDisplayMessage(filteredLastSendError);
|
|
187
206
|
|
|
188
207
|
useEffect(() => {
|
|
189
208
|
presenter.chatStreamActionsManager.bind({
|
|
@@ -304,6 +323,7 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
304
323
|
currentAgent,
|
|
305
324
|
parentSession,
|
|
306
325
|
currentSessionTypeLabel,
|
|
326
|
+
currentSessionTypeIcon,
|
|
307
327
|
currentChildSessionTabs,
|
|
308
328
|
} = useNcpChatDerivedState({
|
|
309
329
|
sessionKey,
|
|
@@ -340,6 +360,7 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
340
360
|
isSkillsLoading: sessionSkillsQuery.isLoading,
|
|
341
361
|
sessionTypeUnavailableMessage,
|
|
342
362
|
currentSessionTypeLabel,
|
|
363
|
+
currentSessionTypeIcon,
|
|
343
364
|
sessionKey,
|
|
344
365
|
currentAgentId,
|
|
345
366
|
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,16 +1,20 @@
|
|
|
1
|
-
import { renderHook } from "@testing-library/react";
|
|
1
|
+
import { act, renderHook, waitFor } from "@testing-library/react";
|
|
2
2
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
3
3
|
import { fetchNcpSessionConversationSeed, useNcpSessionConversation } from "./use-ncp-session-conversation";
|
|
4
4
|
|
|
5
5
|
const mocks = vi.hoisted(() => ({
|
|
6
6
|
fetchNcpSessionMessages: vi.fn(),
|
|
7
|
-
hydratedCalls: [] as Array<{ client: unknown }>,
|
|
7
|
+
hydratedCalls: [] as Array<{ client: unknown; loadSeed: unknown }>,
|
|
8
|
+
runtimeAvailability: {
|
|
9
|
+
phase: "cold-starting" as "cold-starting" | "ready",
|
|
10
|
+
lastReadyAt: null as number | null,
|
|
11
|
+
},
|
|
8
12
|
useHydratedNcpAgent: vi.fn(() => ({
|
|
9
13
|
snapshot: {
|
|
10
14
|
messages: [],
|
|
11
15
|
streamingMessage: null,
|
|
12
16
|
activeRun: null,
|
|
13
|
-
error: null,
|
|
17
|
+
error: null as Error | null,
|
|
14
18
|
},
|
|
15
19
|
visibleMessages: [],
|
|
16
20
|
activeRunId: null,
|
|
@@ -30,7 +34,7 @@ vi.mock("@/api/ncp-session", () => ({
|
|
|
30
34
|
}));
|
|
31
35
|
|
|
32
36
|
vi.mock("@nextclaw/ncp-react", () => ({
|
|
33
|
-
useHydratedNcpAgent: vi.fn((params: { client: unknown }) => {
|
|
37
|
+
useHydratedNcpAgent: vi.fn((params: { client: unknown; loadSeed: unknown }) => {
|
|
34
38
|
mocks.hydratedCalls.push(params);
|
|
35
39
|
return mocks.useHydratedNcpAgent();
|
|
36
40
|
}),
|
|
@@ -42,12 +46,18 @@ vi.mock("@nextclaw/ncp-http-agent-client", () => ({
|
|
|
42
46
|
}),
|
|
43
47
|
}));
|
|
44
48
|
|
|
49
|
+
vi.mock("@/features/system-status", () => ({
|
|
50
|
+
useChatRuntimeAvailability: vi.fn(() => mocks.runtimeAvailability),
|
|
51
|
+
}));
|
|
52
|
+
|
|
45
53
|
describe("useNcpSessionConversation", () => {
|
|
46
54
|
beforeEach(() => {
|
|
47
55
|
mocks.fetchNcpSessionMessages.mockReset();
|
|
48
56
|
mocks.useHydratedNcpAgent.mockClear();
|
|
49
57
|
mocks.hydratedCalls.length = 0;
|
|
50
58
|
mocks.clientInstances.length = 0;
|
|
59
|
+
mocks.runtimeAvailability.phase = "cold-starting";
|
|
60
|
+
mocks.runtimeAvailability.lastReadyAt = null;
|
|
51
61
|
});
|
|
52
62
|
|
|
53
63
|
it("hydrates seed from the shared session messages endpoint payload", async () => {
|
|
@@ -98,4 +108,38 @@ describe("useNcpSessionConversation", () => {
|
|
|
98
108
|
expect(mocks.hydratedCalls[0]?.client).toBe(mocks.clientInstances[0]);
|
|
99
109
|
expect(mocks.hydratedCalls[1]?.client).toBe(mocks.clientInstances[1]);
|
|
100
110
|
});
|
|
111
|
+
|
|
112
|
+
it("retries hydration once the runtime becomes ready after a startup placeholder error", async () => {
|
|
113
|
+
mocks.useHydratedNcpAgent.mockImplementation(() => ({
|
|
114
|
+
snapshot: {
|
|
115
|
+
messages: [],
|
|
116
|
+
streamingMessage: null,
|
|
117
|
+
activeRun: null,
|
|
118
|
+
error: new Error("ncp agent unavailable during startup") as Error | null,
|
|
119
|
+
},
|
|
120
|
+
visibleMessages: [],
|
|
121
|
+
activeRunId: null,
|
|
122
|
+
isRunning: false,
|
|
123
|
+
isSending: false,
|
|
124
|
+
send: vi.fn(),
|
|
125
|
+
abort: vi.fn(),
|
|
126
|
+
streamRun: vi.fn(),
|
|
127
|
+
isHydrating: false,
|
|
128
|
+
hydrateError: null,
|
|
129
|
+
}));
|
|
130
|
+
|
|
131
|
+
const { rerender } = renderHook(() => useNcpSessionConversation("session-a"));
|
|
132
|
+
const initialLoadSeed = mocks.hydratedCalls[0]?.loadSeed;
|
|
133
|
+
|
|
134
|
+
act(() => {
|
|
135
|
+
mocks.runtimeAvailability.phase = "ready";
|
|
136
|
+
mocks.runtimeAvailability.lastReadyAt = 123;
|
|
137
|
+
});
|
|
138
|
+
rerender();
|
|
139
|
+
|
|
140
|
+
await waitFor(() => {
|
|
141
|
+
expect(mocks.hydratedCalls.length).toBeGreaterThan(2);
|
|
142
|
+
});
|
|
143
|
+
expect(mocks.hydratedCalls[mocks.hydratedCalls.length - 1]?.loadSeed).not.toBe(initialLoadSeed);
|
|
144
|
+
});
|
|
101
145
|
});
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import { useCallback, useState } from "react";
|
|
1
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
2
2
|
import { NcpHttpAgentClientEndpoint } from "@nextclaw/ncp-http-agent-client";
|
|
3
3
|
import { useHydratedNcpAgent, type NcpConversationSeed } from "@nextclaw/ncp-react";
|
|
4
4
|
import { API_BASE } from "@/api/api-base";
|
|
5
5
|
import { fetchNcpSessionMessages } from "@/api/ncp-session";
|
|
6
6
|
import { createNcpAppClientFetch } from "@/components/chat/ncp/ncp-app-client-fetch";
|
|
7
|
+
import { useChatRuntimeAvailability } from "@/features/system-status";
|
|
7
8
|
|
|
8
9
|
const DEFAULT_MESSAGE_LIMIT = 300;
|
|
10
|
+
const NCP_AGENT_UNAVAILABLE_DURING_STARTUP = "ncp agent unavailable during startup";
|
|
9
11
|
|
|
10
12
|
type UseNcpSessionConversationOptions = {
|
|
11
13
|
messageLimit?: number;
|
|
@@ -18,6 +20,15 @@ function isMissingNcpSessionError(error: unknown): boolean {
|
|
|
18
20
|
return error.message.includes("ncp session not found:");
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
export function isNcpAgentStartupUnavailableErrorMessage(
|
|
24
|
+
message: string | null | undefined,
|
|
25
|
+
): boolean {
|
|
26
|
+
return (
|
|
27
|
+
message?.trim().toLowerCase().includes(NCP_AGENT_UNAVAILABLE_DURING_STARTUP) ??
|
|
28
|
+
false
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
export function createNcpSessionConversationClient(): NcpHttpAgentClientEndpoint {
|
|
22
33
|
return new NcpHttpAgentClientEndpoint({
|
|
23
34
|
baseUrl: API_BASE,
|
|
@@ -57,16 +68,43 @@ export function useNcpSessionConversation(
|
|
|
57
68
|
options: UseNcpSessionConversationOptions = {},
|
|
58
69
|
) {
|
|
59
70
|
const [client] = useState(() => createNcpSessionConversationClient());
|
|
71
|
+
const runtimeAvailability = useChatRuntimeAvailability();
|
|
72
|
+
const [hydrationRetryNonce, setHydrationRetryNonce] = useState(0);
|
|
73
|
+
const retriedReadySignatureRef = useRef<string | null>(null);
|
|
60
74
|
const messageLimit = options.messageLimit ?? DEFAULT_MESSAGE_LIMIT;
|
|
61
75
|
const loadSeed = useCallback(
|
|
62
|
-
(targetSessionId: string, signal: AbortSignal) =>
|
|
63
|
-
|
|
64
|
-
|
|
76
|
+
(targetSessionId: string, signal: AbortSignal) => {
|
|
77
|
+
void hydrationRetryNonce;
|
|
78
|
+
return fetchNcpSessionConversationSeed(targetSessionId, signal, messageLimit);
|
|
79
|
+
},
|
|
80
|
+
[hydrationRetryNonce, messageLimit],
|
|
65
81
|
);
|
|
66
82
|
|
|
67
|
-
|
|
83
|
+
const agent = useHydratedNcpAgent({
|
|
68
84
|
sessionId,
|
|
69
85
|
client,
|
|
70
86
|
loadSeed,
|
|
71
87
|
});
|
|
88
|
+
|
|
89
|
+
const currentAgentError =
|
|
90
|
+
agent.hydrateError?.message ?? agent.snapshot.error?.message ?? null;
|
|
91
|
+
const readyRetrySignature =
|
|
92
|
+
runtimeAvailability.phase === "ready" &&
|
|
93
|
+
isNcpAgentStartupUnavailableErrorMessage(currentAgentError)
|
|
94
|
+
? `${sessionId}:${runtimeAvailability.lastReadyAt ?? 0}`
|
|
95
|
+
: null;
|
|
96
|
+
|
|
97
|
+
useEffect(() => {
|
|
98
|
+
if (!readyRetrySignature) {
|
|
99
|
+
retriedReadySignatureRef.current = null;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (retriedReadySignatureRef.current === readyRetrySignature) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
retriedReadySignatureRef.current = readyRetrySignature;
|
|
106
|
+
setHydrationRetryNonce((current) => current + 1);
|
|
107
|
+
}, [readyRetrySignature]);
|
|
108
|
+
|
|
109
|
+
return agent;
|
|
72
110
|
}
|
|
@@ -4,6 +4,7 @@ import { NcpChatInputManager } from '@/components/chat/ncp/ncp-chat-input.manage
|
|
|
4
4
|
import { useChatInputStore } from '@/components/chat/stores/chat-input.store';
|
|
5
5
|
import { useChatSessionListStore } from '@/components/chat/stores/chat-session-list.store';
|
|
6
6
|
import { useChatThreadStore } from '@/components/chat/stores/chat-thread.store';
|
|
7
|
+
import { useSystemStatusStore } from '@/features/system-status';
|
|
7
8
|
|
|
8
9
|
describe('NcpChatInputManager', () => {
|
|
9
10
|
beforeEach(() => {
|
|
@@ -15,8 +16,23 @@ describe('NcpChatInputManager', () => {
|
|
|
15
16
|
attachments: [],
|
|
16
17
|
selectedSkills: [],
|
|
17
18
|
selectedSessionType: 'native',
|
|
18
|
-
selectedModel: '',
|
|
19
|
+
selectedModel: 'gpt-5',
|
|
19
20
|
selectedThinkingLevel: null,
|
|
21
|
+
isProviderStateResolved: true,
|
|
22
|
+
modelOptions: [
|
|
23
|
+
{
|
|
24
|
+
value: 'gpt-5',
|
|
25
|
+
modelLabel: 'GPT-5',
|
|
26
|
+
providerLabel: 'OpenAI',
|
|
27
|
+
thinkingCapability: null,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
useSystemStatusStore.setState({
|
|
33
|
+
state: {
|
|
34
|
+
...useSystemStatusStore.getState().state,
|
|
35
|
+
lifecyclePhase: 'ready',
|
|
20
36
|
},
|
|
21
37
|
});
|
|
22
38
|
useChatSessionListStore.setState({
|
|
@@ -96,4 +112,38 @@ describe('NcpChatInputManager', () => {
|
|
|
96
112
|
);
|
|
97
113
|
expect(sessionListManager.promoteRootDraftSessionRoute).toHaveBeenCalledWith('draft-root-session');
|
|
98
114
|
});
|
|
115
|
+
|
|
116
|
+
it('does not send while the runtime is still blocked during startup', async () => {
|
|
117
|
+
useChatInputStore.setState({
|
|
118
|
+
snapshot: {
|
|
119
|
+
...useChatInputStore.getState().snapshot,
|
|
120
|
+
isProviderStateResolved: false,
|
|
121
|
+
modelOptions: [],
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
useSystemStatusStore.setState({
|
|
125
|
+
state: {
|
|
126
|
+
...useSystemStatusStore.getState().state,
|
|
127
|
+
lifecyclePhase: 'cold-starting',
|
|
128
|
+
},
|
|
129
|
+
});
|
|
130
|
+
const streamActionsManager = {
|
|
131
|
+
sendMessage: vi.fn().mockResolvedValue(undefined),
|
|
132
|
+
stopCurrentRun: vi.fn().mockResolvedValue(undefined),
|
|
133
|
+
} as unknown as ConstructorParameters<typeof NcpChatInputManager>[1];
|
|
134
|
+
const sessionListManager = {
|
|
135
|
+
ensureDraftSession: vi.fn(() => 'draft-session'),
|
|
136
|
+
promoteRootDraftSessionRoute: vi.fn(),
|
|
137
|
+
} as unknown as ConstructorParameters<typeof NcpChatInputManager>[2];
|
|
138
|
+
const manager = new NcpChatInputManager(
|
|
139
|
+
{} as ConstructorParameters<typeof NcpChatInputManager>[0],
|
|
140
|
+
streamActionsManager,
|
|
141
|
+
sessionListManager,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
await manager.send();
|
|
145
|
+
|
|
146
|
+
expect(streamActionsManager.sendMessage).not.toHaveBeenCalled();
|
|
147
|
+
expect(sessionListManager.promoteRootDraftSessionRoute).not.toHaveBeenCalled();
|
|
148
|
+
});
|
|
99
149
|
});
|
|
@@ -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
|
);
|