@nextclaw/ui 0.14.1 → 0.14.2
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 +29 -0
- package/dist/assets/{api-DPcvCLfa.js → api-a14tR_z1.js} +7 -7
- package/dist/assets/channels-list-page-CFGkm4DT.js +8 -0
- package/dist/assets/chat-page-CEdavJXc.js +105 -0
- package/dist/assets/{config-split-page-BCJhqdK9.js → config-split-page-B5b9B0uE.js} +1 -1
- package/dist/assets/{confirm-dialog-CWh5FfK2.js → confirm-dialog-ClH_Ec7n.js} +1 -1
- package/dist/assets/{desktop-update-config-DsRhBizi.js → desktop-update-config-DPNDnVpk.js} +1 -1
- package/dist/assets/{dist-DPrgcEC0.js → dist-BtCS2Ucq.js} +1 -1
- package/dist/assets/{dist-DsE4OsSS.js → dist-CLGGLm8b.js} +1 -1
- package/dist/assets/doc-browser-5nMWYKBD.js +1 -0
- package/dist/assets/doc-browser-BJRLhztv.js +1 -0
- package/dist/assets/doc-browser-context-D4JwMMxK.js +1 -0
- package/dist/assets/{ellipsis-B81FStLm.js → ellipsis-CtJXjIol.js} +1 -1
- package/dist/assets/{external-link-C_dmG-WJ.js → external-link-CdJiG13p.js} +1 -1
- package/dist/assets/index-BDN1zzMQ.js +105 -0
- package/dist/assets/index-g3uSZXoc.css +1 -0
- package/dist/assets/mcp-marketplace-page-BU0kIjhR.js +40 -0
- package/dist/assets/mcp-marketplace-page-sMNFSq_T.js +1 -0
- package/dist/assets/middleware-CkFT3R4v.js +1 -0
- package/dist/assets/model-config-page-D6hYXROd.js +1 -0
- package/dist/assets/plus-DPjb0piO.js +1 -0
- package/dist/assets/provider-scoped-model-input-DAX2HgmR.js +1 -0
- package/dist/assets/providers-config-page-BzAeaACA.js +1 -0
- package/dist/assets/{react-sZz5HSCm.js → react-C6cZgP44.js} +1 -1
- package/dist/assets/remote-B0G80uPr.js +1 -0
- package/dist/assets/rotate-cw-DnEB1Rju.js +1 -0
- package/dist/assets/runtime-config-page-C5xKaV3i.js +1 -0
- package/dist/assets/{save-MzjoiHri.js → save-DKA0x4zk.js} +1 -1
- package/dist/assets/{search-B29dE4GN.js → search-BNlcoj5c.js} +1 -1
- package/dist/assets/{search-config-page-Dl5sZzob.js → search-config-page-Cq9uJ-N8.js} +1 -1
- package/dist/assets/{secrets-config-page-C_l-IFCK.js → secrets-config-page-CxSbZLBo.js} +2 -2
- package/dist/assets/{select-BKmh05hd.js → select-CvTlDZTQ.js} +1 -1
- package/dist/assets/{tag-chip-BS_7Ueom.js → tag-chip-8ffF2y2C.js} +1 -1
- package/dist/assets/use-config-DmT0o1gv.js +1 -0
- package/dist/assets/x-BiSSpgqB.js +1 -0
- package/dist/index.html +15 -14
- package/package.json +9 -9
- package/src/app/components/app-presenter-provider.tsx +11 -2
- package/src/app/components/layout/__tests__/app-layout.test.tsx +11 -2
- package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +110 -79
- package/src/app/components/layout/app-layout.tsx +18 -4
- package/src/app/components/layout/sidebar-items.tsx +194 -71
- package/src/app/components/layout/sidebar-rail.styles.ts +15 -0
- package/src/app/components/layout/sidebar.tsx +340 -83
- package/src/app/managers/viewport-layout.manager.ts +10 -0
- package/src/app/stores/viewport-layout.store.ts +39 -3
- package/src/features/agents/components/__tests__/agents-page.test.tsx +62 -39
- package/src/features/agents/components/agents-page.tsx +10 -6
- package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +145 -0
- package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +69 -762
- package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +132 -0
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +22 -15
- package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +14 -15
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +18 -18
- package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +10 -4
- package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +52 -11
- package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +16 -14
- package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +247 -0
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +80 -7
- package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +82 -25
- package/src/features/chat/components/layout/chat-sidebar.tsx +59 -92
- package/src/features/chat/components/providers/chat-presenter.provider.tsx +0 -6
- package/src/features/chat/features/conversation/components/session-conversation-area.tsx +289 -0
- package/src/features/chat/features/conversation/components/session-conversation-input.tsx +427 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +204 -0
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +66 -0
- package/src/features/chat/features/{input/hooks/use-chat-input-bar-query-state.ts → conversation/hooks/use-session-conversation-input-query.ts} +32 -17
- package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +281 -0
- package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +158 -0
- package/src/features/chat/features/input/hooks/__tests__/use-chat-input-surface-state.test.tsx +56 -0
- package/src/features/chat/features/input/hooks/use-chat-input-surface-state.ts +117 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/panel-app-reference-plugin.test.ts +77 -0
- package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/chat-input-product-plugin-adapters.types.ts +10 -0
- package/src/features/chat/features/input/input-surface-plugins/input-surface-search.utils.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/panel-app-reference-plugin.utils.ts +108 -0
- package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +161 -0
- package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +22 -1
- package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +26 -13
- package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +7 -28
- package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +6 -0
- package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +26 -51
- package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +2 -0
- package/src/features/chat/features/input/utils/ncp-chat-input-availability.utils.ts +5 -6
- package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +47 -12
- package/src/features/chat/features/message/components/chat-message-list.container.tsx +93 -12
- package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +31 -12
- package/src/features/chat/features/message/utils/chat-message-markdown-part.utils.ts +38 -0
- package/src/features/chat/features/message/utils/chat-message-part.utils.ts +1 -1
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +2 -2
- package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +2 -2
- package/src/features/chat/features/session/hooks/__tests__/use-chat-session-project.test.tsx +3 -30
- package/src/features/chat/features/session/hooks/use-chat-session-project.ts +0 -5
- package/src/features/chat/features/session/utils/chat-run-metadata.utils.ts +8 -0
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx +2 -2
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +2 -0
- package/src/features/chat/features/session-type/components/chat-session-type-menu.tsx +2 -2
- package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +2 -2
- package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +49 -31
- package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +2 -2
- package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +21 -10
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +2 -2
- package/src/features/chat/features/welcome/components/chat-welcome.tsx +2 -2
- package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +54 -0
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +44 -76
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +5 -1
- package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +7 -1
- package/src/features/chat/features/workspace/hooks/use-chat-conversation-workspace-state.ts +40 -31
- package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +59 -1
- package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +109 -0
- package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +44 -4
- package/src/features/chat/index.ts +0 -1
- package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +51 -99
- package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +135 -21
- package/src/features/chat/managers/chat-session-list.manager.ts +12 -43
- package/src/features/chat/managers/chat-thread.manager.ts +108 -34
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.ts +48 -1
- package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +72 -0
- package/src/features/chat/pages/ncp-chat-page.tsx +0 -122
- package/src/features/chat/presenters/chat.presenter.ts +2 -19
- package/src/features/chat/stores/__tests__/chat-thread.store.test.ts +31 -0
- package/src/features/chat/stores/chat-thread.store.ts +42 -26
- package/src/features/panel-apps/hooks/use-panel-apps.ts +2 -1
- package/src/platforms/desktop/components/__tests__/desktop-app-shell.test.tsx +50 -10
- package/src/platforms/desktop/components/desktop-app-shell.tsx +34 -10
- package/src/platforms/desktop/components/desktop-window-chrome.tsx +41 -16
- package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +24 -1
- package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +8 -4
- package/src/shared/lib/i18n/locales/en-US/chat.json +20 -2
- package/src/shared/lib/i18n/locales/en-US/core.json +2 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +20 -2
- package/src/shared/lib/i18n/locales/zh-CN/core.json +2 -0
- package/src/shared/lib/ui-document-title/__tests__/ui-document-title.test.ts +49 -0
- package/src/shared/lib/ui-document-title/index.ts +50 -21
- package/dist/assets/channels-list-page-D6jtziu4.js +0 -8
- package/dist/assets/chat-page-BBnNJoKG.js +0 -105
- package/dist/assets/doc-browser-C8xOF_9F.js +0 -1
- package/dist/assets/doc-browser-DgQedeAn.js +0 -1
- package/dist/assets/doc-browser-context-B46tQGmO.js +0 -1
- package/dist/assets/index-7o1WdUbU.js +0 -103
- package/dist/assets/index-DQWmEWzk.css +0 -1
- package/dist/assets/mcp-marketplace-page-CWZN7ohK.js +0 -1
- package/dist/assets/mcp-marketplace-page-Dzull_4-.js +0 -40
- package/dist/assets/model-config-page-Cv0O3BIj.js +0 -1
- package/dist/assets/plus-Bnt56oqG.js +0 -1
- package/dist/assets/provider-scoped-model-input-Duuh7i3h.js +0 -1
- package/dist/assets/providers-config-page-CPDz0Lxc.js +0 -1
- package/dist/assets/remote-B0LMs8u7.js +0 -1
- package/dist/assets/rotate-cw-BfyG_KEF.js +0 -1
- package/dist/assets/runtime-config-page-q0fvv8ME.js +0 -1
- package/dist/assets/use-config-DZkBWIdL.js +0 -1
- package/dist/assets/x-CW2KO-Hz.js +0 -1
- package/src/features/chat/components/conversation/chat-conversation-alerts.tsx +0 -37
- package/src/features/chat/features/input/components/__tests__/chat-attachment-upload-limit.test.ts +0 -38
- package/src/features/chat/features/input/components/chat-input-bar.container.tsx +0 -496
- package/src/features/chat/features/message/utils/chat-message-inline-content.utils.ts +0 -95
- package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +0 -448
- package/src/features/chat/managers/__tests__/chat-run-snapshot.manager.test.ts +0 -129
- package/src/features/chat/managers/__tests__/chat-run.manager.test.ts +0 -236
- package/src/features/chat/managers/__tests__/chat-session-preference-sync.manager.test.ts +0 -219
- package/src/features/chat/managers/chat-input.manager.ts +0 -430
- package/src/features/chat/managers/chat-run.manager.ts +0 -110
- package/src/features/chat/managers/chat-session-preference-sync.manager.ts +0 -142
- package/src/features/chat/stores/chat-input.store.ts +0 -104
- /package/dist/assets/{config-hints-CTqBnCDp.js → config-hints-BkYBc7z0.js} +0 -0
|
@@ -11,41 +11,14 @@ import {
|
|
|
11
11
|
} from "@/features/chat/components/layout/chat-page-shell";
|
|
12
12
|
import { parseSessionKeyFromRoute } from "@/features/chat/features/session/utils/chat-session-route.utils";
|
|
13
13
|
import { ChatPresenter } from "@/features/chat/presenters/chat.presenter";
|
|
14
|
-
import {
|
|
15
|
-
isNcpAgentStartupUnavailableErrorMessage,
|
|
16
|
-
useNcpSessionConversation,
|
|
17
|
-
} from "@/features/chat/features/ncp/hooks/use-ncp-session-conversation";
|
|
18
|
-
import {
|
|
19
|
-
useNcpChatSelectedSession,
|
|
20
|
-
} from "@/features/chat/features/ncp/hooks/use-ncp-chat-derived-state";
|
|
21
14
|
import { useChatQueryStoreSync } from "@/features/chat/features/ncp/hooks/use-ncp-chat-query-store-sync";
|
|
22
15
|
import {
|
|
23
16
|
ChatPresenterProvider,
|
|
24
17
|
usePresenter,
|
|
25
18
|
} from "@/features/chat/components/providers/chat-presenter.provider";
|
|
26
19
|
import { useConfirmDialog } from "@/shared/hooks/use-confirm-dialog";
|
|
27
|
-
import { useSystemStatus } from "@/features/system-status";
|
|
28
20
|
import { useAppPresenter } from "@/app/components/app-presenter-provider";
|
|
29
|
-
import { isNcpChatRuntimeBlocked, resolveNcpChatSendErrorMessage } from "@/features/chat/features/runtime/utils/ncp-chat-runtime-availability.utils";
|
|
30
21
|
import { useUiShowContentEvent } from "@/features/chat/features/ncp/hooks/use-ui-show-content-event";
|
|
31
|
-
import { readNcpContextWindowValue } from "@/features/chat/features/session/utils/ncp-session-context-metadata.utils";
|
|
32
|
-
|
|
33
|
-
function useChatDraftIntentConsumer() {
|
|
34
|
-
const appPresenter = useAppPresenter();
|
|
35
|
-
const presenter = usePresenter();
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
const applyIntent = (intent: { id: number; prompt: string }) => {
|
|
38
|
-
presenter.startAgentCreationDraft(intent.prompt);
|
|
39
|
-
appPresenter.chatDraftIntentManager.markConsumed(intent.id);
|
|
40
|
-
};
|
|
41
|
-
const unsubscribe = appPresenter.chatDraftIntentManager.subscribe(applyIntent);
|
|
42
|
-
const pendingIntent = appPresenter.chatDraftIntentManager.consumePending();
|
|
43
|
-
if (pendingIntent) {
|
|
44
|
-
applyIntent(pendingIntent);
|
|
45
|
-
}
|
|
46
|
-
return unsubscribe;
|
|
47
|
-
}, [appPresenter, presenter]);
|
|
48
|
-
}
|
|
49
22
|
|
|
50
23
|
function useNcpChatRouteSelection() {
|
|
51
24
|
const { sessionId: routeSessionIdParam } = useParams<{ sessionId?: string }>();
|
|
@@ -59,31 +32,6 @@ function useNcpChatRouteSelection() {
|
|
|
59
32
|
};
|
|
60
33
|
}
|
|
61
34
|
|
|
62
|
-
type NcpChatConversation = ReturnType<typeof useNcpSessionConversation>;
|
|
63
|
-
|
|
64
|
-
function useChatRunRuntimeConnection(params: {
|
|
65
|
-
agent: NcpChatConversation;
|
|
66
|
-
sessionKey: string | undefined;
|
|
67
|
-
}) {
|
|
68
|
-
const presenter = usePresenter();
|
|
69
|
-
const { agent, sessionKey } = params;
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
presenter.chatRunManager.setActiveRuntime({
|
|
72
|
-
sessionKey: sessionKey ?? null,
|
|
73
|
-
sendEnvelope: (envelope) => agent.send(envelope),
|
|
74
|
-
abortCurrentRun: () => agent.abort(),
|
|
75
|
-
resumeCurrentSessionRun: () => agent.streamRun(),
|
|
76
|
-
});
|
|
77
|
-
return () => {
|
|
78
|
-
presenter.chatRunManager.setActiveRuntime(null);
|
|
79
|
-
};
|
|
80
|
-
}, [
|
|
81
|
-
agent,
|
|
82
|
-
presenter,
|
|
83
|
-
sessionKey,
|
|
84
|
-
]);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
35
|
function useNcpChatUiBindings() {
|
|
88
36
|
const presenter = usePresenter();
|
|
89
37
|
const { confirm, ConfirmDialog } = useConfirmDialog();
|
|
@@ -101,42 +49,6 @@ function useNcpChatUiBindings() {
|
|
|
101
49
|
return <ConfirmDialog />;
|
|
102
50
|
}
|
|
103
51
|
|
|
104
|
-
function useChatRunSnapshotSync(params: {
|
|
105
|
-
agent: NcpChatConversation;
|
|
106
|
-
isSending: boolean;
|
|
107
|
-
isRunning: boolean;
|
|
108
|
-
lastSendError: string | null;
|
|
109
|
-
routeSessionKey: string | null;
|
|
110
|
-
}) {
|
|
111
|
-
const presenter = usePresenter();
|
|
112
|
-
const { agent, isRunning, isSending, lastSendError, routeSessionKey } = params;
|
|
113
|
-
useEffect(() => {
|
|
114
|
-
presenter.chatRunManager.applyRunSnapshot({
|
|
115
|
-
routeSessionKey,
|
|
116
|
-
isHydrating: agent.isHydrating,
|
|
117
|
-
isSending,
|
|
118
|
-
isRunning,
|
|
119
|
-
visibleMessages: agent.visibleMessages,
|
|
120
|
-
contextWindow: readNcpContextWindowValue(agent.snapshot.contextWindow),
|
|
121
|
-
sendErrorMessage: lastSendError,
|
|
122
|
-
materializedSessionKey:
|
|
123
|
-
agent.snapshot.activeRun?.sessionId ??
|
|
124
|
-
agent.visibleMessages.find((message) => message.sessionId.trim())?.sessionId ??
|
|
125
|
-
null,
|
|
126
|
-
});
|
|
127
|
-
}, [
|
|
128
|
-
agent.isHydrating,
|
|
129
|
-
agent.snapshot.activeRun?.sessionId,
|
|
130
|
-
agent.snapshot.contextWindow,
|
|
131
|
-
agent.visibleMessages,
|
|
132
|
-
isRunning,
|
|
133
|
-
isSending,
|
|
134
|
-
lastSendError,
|
|
135
|
-
presenter,
|
|
136
|
-
routeSessionKey,
|
|
137
|
-
]);
|
|
138
|
-
}
|
|
139
|
-
|
|
140
52
|
export function NcpChatPage({ view }: ChatPageProps) {
|
|
141
53
|
const appPresenter = useAppPresenter();
|
|
142
54
|
const [presenter] = useState(() => new ChatPresenter(appPresenter));
|
|
@@ -149,52 +61,18 @@ export function NcpChatPage({ view }: ChatPageProps) {
|
|
|
149
61
|
|
|
150
62
|
function NcpChatPageContent({ view }: ChatPageProps) {
|
|
151
63
|
const presenter = usePresenter();
|
|
152
|
-
const systemStatus = useSystemStatus();
|
|
153
|
-
const isRuntimeBlocked = isNcpChatRuntimeBlocked(systemStatus);
|
|
154
64
|
const confirmDialog = useNcpChatUiBindings();
|
|
155
65
|
const routeSelection = useNcpChatRouteSelection();
|
|
156
66
|
const { routeSessionKey, sessionKey } = routeSelection;
|
|
157
67
|
useChatQueryStoreSync({
|
|
158
68
|
sessionKey: sessionKey ?? null,
|
|
159
69
|
});
|
|
160
|
-
const selectedSession = useNcpChatSelectedSession(sessionKey ?? null);
|
|
161
|
-
const agent = useNcpSessionConversation(sessionKey);
|
|
162
|
-
const rawLastSendError =
|
|
163
|
-
agent.hydrateError?.message ?? agent.snapshot.error?.message ?? null;
|
|
164
|
-
const filteredLastSendError =
|
|
165
|
-
systemStatus.phase === "ready" &&
|
|
166
|
-
isNcpAgentStartupUnavailableErrorMessage(rawLastSendError)
|
|
167
|
-
? null
|
|
168
|
-
: rawLastSendError;
|
|
169
|
-
const currentSessionRunning = agent.isRunning || selectedSession?.status === "running";
|
|
170
|
-
const isSending = agent.isSending || currentSessionRunning;
|
|
171
|
-
const lastSendError =
|
|
172
|
-
isRuntimeBlocked
|
|
173
|
-
? null
|
|
174
|
-
: systemStatus.phase === "ready"
|
|
175
|
-
? filteredLastSendError
|
|
176
|
-
: resolveNcpChatSendErrorMessage({
|
|
177
|
-
message: filteredLastSendError,
|
|
178
|
-
status: systemStatus,
|
|
179
|
-
});
|
|
180
|
-
useChatRunRuntimeConnection({
|
|
181
|
-
agent,
|
|
182
|
-
sessionKey,
|
|
183
|
-
});
|
|
184
70
|
useChatSessionSync({
|
|
185
71
|
view,
|
|
186
72
|
routeSessionKey,
|
|
187
73
|
syncRouteSessionSelection:
|
|
188
74
|
presenter.chatSessionListManager.syncRouteSessionSelection,
|
|
189
75
|
});
|
|
190
|
-
useChatDraftIntentConsumer();
|
|
191
76
|
useUiShowContentEvent();
|
|
192
|
-
useChatRunSnapshotSync({
|
|
193
|
-
agent,
|
|
194
|
-
isRunning: currentSessionRunning,
|
|
195
|
-
routeSessionKey,
|
|
196
|
-
lastSendError,
|
|
197
|
-
isSending,
|
|
198
|
-
});
|
|
199
77
|
return <ChatPageLayout view={view} confirmDialog={confirmDialog} />;
|
|
200
78
|
}
|
|
@@ -1,39 +1,22 @@
|
|
|
1
1
|
import { ChatSessionListManager } from '@/features/chat/managers/chat-session-list.manager';
|
|
2
|
-
import { ChatRunManager } from '@/features/chat/managers/chat-run.manager';
|
|
3
2
|
import { ChatUiManager } from '@/features/chat/managers/chat-ui.manager';
|
|
4
|
-
import { ChatInputManager } from '@/features/chat/managers/chat-input.manager';
|
|
5
3
|
import { ChatQueryManager } from '@/features/chat/managers/chat-query.manager';
|
|
6
4
|
import { ChatThreadManager } from '@/features/chat/managers/chat-thread.manager';
|
|
7
5
|
import type { AppPresenter } from '@/app/presenters/app.presenter';
|
|
8
6
|
|
|
9
7
|
export class ChatPresenter {
|
|
10
8
|
readonly chatUiManager: ChatUiManager;
|
|
11
|
-
readonly chatRunManager: ChatRunManager;
|
|
12
9
|
readonly chatSessionListManager: ChatSessionListManager;
|
|
13
|
-
readonly chatInputManager: ChatInputManager;
|
|
14
10
|
readonly chatQueryManager: ChatQueryManager;
|
|
15
11
|
readonly chatThreadManager: ChatThreadManager;
|
|
16
12
|
|
|
17
13
|
constructor(appPresenter: AppPresenter) {
|
|
18
14
|
this.chatUiManager = new ChatUiManager(appPresenter.docBrowserManager);
|
|
19
|
-
this.
|
|
20
|
-
this.chatSessionListManager = new ChatSessionListManager(this.chatUiManager, this.chatRunManager);
|
|
15
|
+
this.chatSessionListManager = new ChatSessionListManager(this.chatUiManager);
|
|
21
16
|
this.chatQueryManager = new ChatQueryManager();
|
|
22
|
-
this.chatInputManager = new ChatInputManager(
|
|
23
|
-
this.chatRunManager,
|
|
24
|
-
this.chatSessionListManager
|
|
25
|
-
);
|
|
26
17
|
this.chatThreadManager = new ChatThreadManager(
|
|
27
18
|
this.chatUiManager,
|
|
28
|
-
this.chatSessionListManager
|
|
29
|
-
this.chatRunManager
|
|
19
|
+
this.chatSessionListManager
|
|
30
20
|
);
|
|
31
21
|
}
|
|
32
|
-
|
|
33
|
-
startAgentCreationDraft = (prompt: string) => {
|
|
34
|
-
this.chatSessionListManager.createSession();
|
|
35
|
-
this.chatSessionListManager.setSelectedAgentId('main');
|
|
36
|
-
this.chatInputManager.setDraft(prompt);
|
|
37
|
-
this.chatInputManager.requestComposerFocusAtEnd();
|
|
38
|
-
};
|
|
39
22
|
}
|
|
@@ -22,6 +22,7 @@ function resetChatThreadStore() {
|
|
|
22
22
|
activeWorkspacePanelKind: null,
|
|
23
23
|
childSessionTabs: [],
|
|
24
24
|
activeChildSessionKey: null,
|
|
25
|
+
activeSideChatDraft: null,
|
|
25
26
|
workspaceFileTabs: [],
|
|
26
27
|
activeWorkspaceFileKey: null,
|
|
27
28
|
workspaceNavigationHistory: [],
|
|
@@ -85,6 +86,36 @@ describe('chat thread workspace panel persistence', () => {
|
|
|
85
86
|
expect(persisted.state.snapshot.workspaceFileTabs[0].fullLines).toBeUndefined();
|
|
86
87
|
});
|
|
87
88
|
|
|
89
|
+
it('does not persist an unmaterialized side chat draft', () => {
|
|
90
|
+
useChatThreadStore.getState().setSnapshot({
|
|
91
|
+
workspacePanelParentKey: 'session-1',
|
|
92
|
+
activeWorkspacePanelKind: 'side-chat-draft',
|
|
93
|
+
activeSideChatDraft: {
|
|
94
|
+
draftKey: 'draft-1',
|
|
95
|
+
parentSessionKey: 'session-1',
|
|
96
|
+
},
|
|
97
|
+
workspaceNavigationHistory: [
|
|
98
|
+
{ kind: 'child-session', key: 'child-session-1' },
|
|
99
|
+
{ kind: 'side-chat-draft', key: 'draft-1' },
|
|
100
|
+
],
|
|
101
|
+
workspaceNavigationHistoryIndex: 1,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
const persisted = JSON.parse(
|
|
105
|
+
window.localStorage.getItem(chatThreadWorkspaceStorageKey) ?? '{}',
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(persisted.state.snapshot).toMatchObject({
|
|
109
|
+
workspacePanelParentKey: 'session-1',
|
|
110
|
+
activeWorkspacePanelKind: null,
|
|
111
|
+
workspaceNavigationHistory: [
|
|
112
|
+
{ kind: 'child-session', key: 'child-session-1' },
|
|
113
|
+
],
|
|
114
|
+
workspaceNavigationHistoryIndex: 0,
|
|
115
|
+
});
|
|
116
|
+
expect(persisted.state.snapshot.activeSideChatDraft).toBeUndefined();
|
|
117
|
+
});
|
|
118
|
+
|
|
88
119
|
it('hydrates the workspace panel state and repairs stale active file keys', async () => {
|
|
89
120
|
window.localStorage.setItem(
|
|
90
121
|
chatThreadWorkspaceStorageKey,
|
|
@@ -14,6 +14,11 @@ export type ChatChildSessionTab = {
|
|
|
14
14
|
agentId?: string | null;
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
export type ChatWorkspaceSideChatDraft = {
|
|
18
|
+
draftKey: string;
|
|
19
|
+
parentSessionKey: string;
|
|
20
|
+
};
|
|
21
|
+
|
|
17
22
|
export type ChatWorkspaceFileTab = {
|
|
18
23
|
key: string;
|
|
19
24
|
parentSessionKey: string | null;
|
|
@@ -32,17 +37,10 @@ export type ChatWorkspaceFileTab = {
|
|
|
32
37
|
};
|
|
33
38
|
|
|
34
39
|
export type ChatWorkspaceNavigationEntry =
|
|
35
|
-
| {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
| {
|
|
40
|
-
kind: 'file';
|
|
41
|
-
key: string;
|
|
42
|
-
}
|
|
43
|
-
| {
|
|
44
|
-
kind: 'cron';
|
|
45
|
-
};
|
|
40
|
+
| { kind: 'child-session'; key: string }
|
|
41
|
+
| { kind: 'side-chat-draft'; key: string }
|
|
42
|
+
| { kind: 'file'; key: string }
|
|
43
|
+
| { kind: 'cron' };
|
|
46
44
|
|
|
47
45
|
export type ChatThreadSnapshot = {
|
|
48
46
|
sessionTypeLabel?: string | null;
|
|
@@ -63,9 +61,10 @@ export type ChatThreadSnapshot = {
|
|
|
63
61
|
parentSessionKey?: string | null;
|
|
64
62
|
parentSessionLabel?: string | null;
|
|
65
63
|
workspacePanelParentKey?: string | null;
|
|
66
|
-
activeWorkspacePanelKind?: "child-session" | "file" | "cron" | null;
|
|
64
|
+
activeWorkspacePanelKind?: "child-session" | "side-chat-draft" | "file" | "cron" | null;
|
|
67
65
|
childSessionTabs: ChatChildSessionTab[];
|
|
68
66
|
activeChildSessionKey?: string | null;
|
|
67
|
+
activeSideChatDraft?: ChatWorkspaceSideChatDraft | null;
|
|
69
68
|
workspaceFileTabs: ChatWorkspaceFileTab[];
|
|
70
69
|
activeWorkspaceFileKey?: string | null;
|
|
71
70
|
workspaceNavigationHistory: ChatWorkspaceNavigationEntry[];
|
|
@@ -105,6 +104,8 @@ type PersistedChatWorkspaceSnapshot = Pick<
|
|
|
105
104
|
| 'workspaceNavigationHistoryIndex'
|
|
106
105
|
>;
|
|
107
106
|
|
|
107
|
+
type PersistedWorkspacePanelKind = 'child-session' | 'file' | 'cron';
|
|
108
|
+
|
|
108
109
|
const initialSnapshot: ChatThreadSnapshot = {
|
|
109
110
|
sessionTypeLabel: null,
|
|
110
111
|
sessionTypeIcon: null,
|
|
@@ -127,6 +128,7 @@ const initialSnapshot: ChatThreadSnapshot = {
|
|
|
127
128
|
activeWorkspacePanelKind: null,
|
|
128
129
|
childSessionTabs: [],
|
|
129
130
|
activeChildSessionKey: null,
|
|
131
|
+
activeSideChatDraft: null,
|
|
130
132
|
workspaceFileTabs: [],
|
|
131
133
|
activeWorkspaceFileKey: null,
|
|
132
134
|
workspaceNavigationHistory: [],
|
|
@@ -158,7 +160,7 @@ function normalizeHistoryIndex(value: unknown, maxIndex: number): number {
|
|
|
158
160
|
|
|
159
161
|
function isWorkspacePanelKind(
|
|
160
162
|
value: unknown,
|
|
161
|
-
): value is
|
|
163
|
+
): value is PersistedWorkspacePanelKind {
|
|
162
164
|
return value === 'child-session' || value === 'file' || value === 'cron';
|
|
163
165
|
}
|
|
164
166
|
|
|
@@ -344,19 +346,33 @@ export const useChatThreadStore = create<ChatThreadStore>()(
|
|
|
344
346
|
name: CHAT_THREAD_WORKSPACE_STORAGE_KEY,
|
|
345
347
|
version: CHAT_THREAD_WORKSPACE_STORAGE_VERSION,
|
|
346
348
|
storage: createJSONStorage(() => window.localStorage),
|
|
347
|
-
partialize: (state): PersistedChatThreadStore =>
|
|
348
|
-
snapshot
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
349
|
+
partialize: (state): PersistedChatThreadStore => {
|
|
350
|
+
const workspaceNavigationHistory = state.snapshot.workspaceNavigationHistory
|
|
351
|
+
.filter((entry) => entry.kind !== 'side-chat-draft');
|
|
352
|
+
const workspaceNavigationHistoryIndex =
|
|
353
|
+
workspaceNavigationHistory.length > 0
|
|
354
|
+
? Math.min(
|
|
355
|
+
state.snapshot.workspaceNavigationHistoryIndex,
|
|
356
|
+
workspaceNavigationHistory.length - 1,
|
|
357
|
+
)
|
|
358
|
+
: 0;
|
|
359
|
+
return {
|
|
360
|
+
snapshot: {
|
|
361
|
+
workspacePanelParentKey: state.snapshot.workspacePanelParentKey,
|
|
362
|
+
activeWorkspacePanelKind:
|
|
363
|
+
state.snapshot.activeWorkspacePanelKind === 'side-chat-draft'
|
|
364
|
+
? null
|
|
365
|
+
: state.snapshot.activeWorkspacePanelKind,
|
|
366
|
+
activeChildSessionKey: state.snapshot.activeChildSessionKey,
|
|
367
|
+
workspaceFileTabs: state.snapshot.workspaceFileTabs
|
|
368
|
+
.slice(-CHAT_THREAD_MAX_PERSISTED_WORKSPACE_FILE_TABS)
|
|
369
|
+
.map(toPersistedWorkspaceFileTab),
|
|
370
|
+
activeWorkspaceFileKey: state.snapshot.activeWorkspaceFileKey,
|
|
371
|
+
workspaceNavigationHistory,
|
|
372
|
+
workspaceNavigationHistoryIndex,
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
},
|
|
360
376
|
merge: (persistedState, currentState) => {
|
|
361
377
|
const persistedSnapshot = isRecord(persistedState)
|
|
362
378
|
? persistedState.snapshot
|
|
@@ -4,10 +4,11 @@ import type { PanelAppPreferencesUpdateView } from '@/shared/lib/api';
|
|
|
4
4
|
|
|
5
5
|
const PANEL_APPS_QUERY_KEY = ['panel-apps'] as const;
|
|
6
6
|
|
|
7
|
-
export function usePanelApps() {
|
|
7
|
+
export function usePanelApps(options: { enabled?: boolean } = {}) {
|
|
8
8
|
return useQuery({
|
|
9
9
|
queryKey: PANEL_APPS_QUERY_KEY,
|
|
10
10
|
queryFn: () => nextclawClient.panelApps.listPanelApps(),
|
|
11
|
+
enabled: options.enabled ?? true,
|
|
11
12
|
staleTime: 0,
|
|
12
13
|
});
|
|
13
14
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { act, render, screen } from "@testing-library/react";
|
|
2
2
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
3
3
|
import { MemoryRouter } from "react-router-dom";
|
|
4
|
-
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
5
5
|
import { DesktopAppShell } from "@/platforms/desktop/components/desktop-app-shell";
|
|
6
|
+
import { viewportLayoutManager } from "@/app/managers/viewport-layout.manager";
|
|
6
7
|
|
|
7
8
|
vi.mock("@/app/components/layout/sidebar", () => ({
|
|
8
9
|
Sidebar: () => <aside data-testid="settings-sidebar">Settings Sidebar</aside>,
|
|
@@ -16,7 +17,10 @@ type WindowStateListener = (snapshot: { isMaximized: boolean }) => void;
|
|
|
16
17
|
|
|
17
18
|
let windowStateListener: WindowStateListener | null = null;
|
|
18
19
|
|
|
19
|
-
function setDesktopPlatform(
|
|
20
|
+
function setDesktopPlatform(
|
|
21
|
+
platform: string | null,
|
|
22
|
+
isMaximized = false,
|
|
23
|
+
): void {
|
|
20
24
|
windowStateListener = null;
|
|
21
25
|
window.nextclawDesktop = platform
|
|
22
26
|
? ({
|
|
@@ -59,6 +63,11 @@ function renderDesktopShell(platform: string | null, isMobileLayout = false) {
|
|
|
59
63
|
}
|
|
60
64
|
|
|
61
65
|
describe("DesktopAppShell", () => {
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
window.localStorage.clear();
|
|
68
|
+
viewportLayoutManager.resetForTests();
|
|
69
|
+
});
|
|
70
|
+
|
|
62
71
|
afterEach(() => {
|
|
63
72
|
setDesktopPlatform(null);
|
|
64
73
|
});
|
|
@@ -68,29 +77,58 @@ describe("DesktopAppShell", () => {
|
|
|
68
77
|
|
|
69
78
|
const chrome = screen.getByTestId("desktop-window-chrome");
|
|
70
79
|
const sidebarChrome = screen.getByTestId("desktop-window-chrome-sidebar");
|
|
71
|
-
const resizeStrip = screen.getByTestId(
|
|
80
|
+
const resizeStrip = screen.getByTestId(
|
|
81
|
+
"desktop-window-chrome-resize-strip",
|
|
82
|
+
);
|
|
72
83
|
const controls = screen.getByTestId("desktop-window-controls");
|
|
73
84
|
|
|
74
85
|
expect(chrome).toBeTruthy();
|
|
75
|
-
expect(
|
|
86
|
+
expect(
|
|
87
|
+
chrome.parentElement?.style.getPropertyValue("--desktop-titlebar-height"),
|
|
88
|
+
).toBe("40px");
|
|
76
89
|
expect(chrome.className).toContain("bg-secondary");
|
|
77
90
|
expect(chrome.className).not.toContain(" shrink-0 border-b ");
|
|
78
|
-
expect(chrome.className).toContain(
|
|
91
|
+
expect(chrome.className).toContain(
|
|
92
|
+
"after:left-[var(--desktop-sidebar-width)]",
|
|
93
|
+
);
|
|
79
94
|
expect(chrome.className).toContain("after:border-b");
|
|
80
95
|
expect(chrome.className).toContain("desktop-window-drag");
|
|
81
|
-
expect(sidebarChrome.className).toContain(
|
|
96
|
+
expect(sidebarChrome.className).toContain(
|
|
97
|
+
"w-[var(--desktop-sidebar-width)]",
|
|
98
|
+
);
|
|
82
99
|
expect(sidebarChrome.className).not.toContain("border-b");
|
|
83
100
|
expect(sidebarChrome.className).toContain("desktop-window-drag");
|
|
84
101
|
expect(resizeStrip.className).toContain("desktop-window-no-drag");
|
|
85
102
|
expect(resizeStrip.className).toContain("top-0");
|
|
86
103
|
expect(resizeStrip.className).toContain("h-1");
|
|
87
104
|
expect(controls.className).toContain("desktop-window-no-drag");
|
|
88
|
-
expect(screen.getByLabelText("Minimize").className).toContain(
|
|
89
|
-
|
|
90
|
-
|
|
105
|
+
expect(screen.getByLabelText("Minimize").className).toContain(
|
|
106
|
+
"desktop-window-no-drag",
|
|
107
|
+
);
|
|
108
|
+
expect(screen.getByLabelText("Maximize").className).toContain(
|
|
109
|
+
"desktop-window-no-drag",
|
|
110
|
+
);
|
|
111
|
+
expect(screen.getByLabelText("Close").className).toContain(
|
|
112
|
+
"desktop-window-no-drag",
|
|
113
|
+
);
|
|
91
114
|
expect(screen.getByTestId("app-content")).toBeTruthy();
|
|
92
115
|
});
|
|
93
116
|
|
|
117
|
+
it("uses the collapsed sidebar width in Windows chrome from the shared layout store", () => {
|
|
118
|
+
viewportLayoutManager.setSidebarCollapsed(true);
|
|
119
|
+
|
|
120
|
+
renderDesktopShell("win32");
|
|
121
|
+
|
|
122
|
+
const chromeRoot = screen.getByTestId(
|
|
123
|
+
"desktop-window-chrome",
|
|
124
|
+
).parentElement;
|
|
125
|
+
|
|
126
|
+
expect(chromeRoot?.style.getPropertyValue("--desktop-sidebar-width")).toBe(
|
|
127
|
+
"56px",
|
|
128
|
+
);
|
|
129
|
+
expect(screen.getByAltText("NextClaw")).toBeTruthy();
|
|
130
|
+
});
|
|
131
|
+
|
|
94
132
|
it("switches the Windows maximize button to restore while maximized", async () => {
|
|
95
133
|
renderDesktopShell("win32");
|
|
96
134
|
|
|
@@ -100,7 +138,9 @@ describe("DesktopAppShell", () => {
|
|
|
100
138
|
windowStateListener?.({ isMaximized: true });
|
|
101
139
|
});
|
|
102
140
|
|
|
103
|
-
expect(screen.getByLabelText("Restore").className).toContain(
|
|
141
|
+
expect(screen.getByLabelText("Restore").className).toContain(
|
|
142
|
+
"desktop-window-no-drag",
|
|
143
|
+
);
|
|
104
144
|
expect(screen.queryByLabelText("Maximize")).toBeNull();
|
|
105
145
|
});
|
|
106
146
|
|
|
@@ -11,9 +11,12 @@ import { MobileBottomNav } from "@/platforms/mobile";
|
|
|
11
11
|
import type { DocBrowserCustomTabRenderers } from "@/shared/components/doc-browser/doc-browser-renderer.types";
|
|
12
12
|
import type { DocBrowserDockControls } from "@/shared/components/doc-browser/doc-browser-context";
|
|
13
13
|
import { cn } from "@/shared/lib/utils";
|
|
14
|
+
import { useViewportLayoutStore } from "@/app/stores/viewport-layout.store";
|
|
15
|
+
import { SIDEBAR_RAIL_WIDTH_PX } from "@/app/components/layout/sidebar-rail.styles";
|
|
14
16
|
|
|
15
17
|
const DocBrowser = lazy(async () => ({
|
|
16
|
-
default: (await import("@/shared/components/doc-browser/doc-browser"))
|
|
18
|
+
default: (await import("@/shared/components/doc-browser/doc-browser"))
|
|
19
|
+
.DocBrowser,
|
|
17
20
|
}));
|
|
18
21
|
|
|
19
22
|
type DesktopAppShellProps = {
|
|
@@ -38,8 +41,17 @@ export function DesktopAppShell({
|
|
|
38
41
|
children,
|
|
39
42
|
}: DesktopAppShellProps) {
|
|
40
43
|
const isMainRoute = isMainWorkspaceRoute(pathname);
|
|
41
|
-
const
|
|
44
|
+
const isSidebarCollapsed = useViewportLayoutStore(
|
|
45
|
+
(state) => state.isSidebarCollapsed,
|
|
46
|
+
);
|
|
47
|
+
const showMobileBottomNav =
|
|
48
|
+
isMobileLayout && !isChatSessionDetailRoute(pathname);
|
|
42
49
|
const shouldUseWindowsChrome = isWindowsDesktopHost();
|
|
50
|
+
const desktopSidebarWidth = isSidebarCollapsed
|
|
51
|
+
? `${SIDEBAR_RAIL_WIDTH_PX}px`
|
|
52
|
+
: isMainRoute
|
|
53
|
+
? "280px"
|
|
54
|
+
: "240px";
|
|
43
55
|
|
|
44
56
|
return (
|
|
45
57
|
<div
|
|
@@ -47,13 +59,19 @@ export function DesktopAppShell({
|
|
|
47
59
|
"h-screen flex flex-col overflow-hidden bg-background font-sans text-foreground",
|
|
48
60
|
shouldUseWindowsChrome ? "rounded-[10px]" : null,
|
|
49
61
|
)}
|
|
50
|
-
style={
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
62
|
+
style={
|
|
63
|
+
shouldUseWindowsChrome
|
|
64
|
+
? ({
|
|
65
|
+
"--desktop-titlebar-height": "40px",
|
|
66
|
+
"--desktop-caption-safe-right": "140px",
|
|
67
|
+
"--desktop-sidebar-width": desktopSidebarWidth,
|
|
68
|
+
} as CSSProperties)
|
|
69
|
+
: undefined
|
|
70
|
+
}
|
|
55
71
|
>
|
|
56
|
-
{shouldUseWindowsChrome ?
|
|
72
|
+
{shouldUseWindowsChrome ? (
|
|
73
|
+
<DesktopWindowChrome sidebarCollapsed={isSidebarCollapsed} />
|
|
74
|
+
) : null}
|
|
57
75
|
<div className="flex min-h-0 flex-1 overflow-hidden">
|
|
58
76
|
{!isMainRoute && <Sidebar mode="settings" />}
|
|
59
77
|
<div className="flex-1 flex min-w-0 overflow-hidden relative">
|
|
@@ -70,7 +88,10 @@ export function DesktopAppShell({
|
|
|
70
88
|
</div>
|
|
71
89
|
{isDocBrowserOpen && docBrowserMode === "docked" ? (
|
|
72
90
|
<Suspense fallback={null}>
|
|
73
|
-
<DocBrowser
|
|
91
|
+
<DocBrowser
|
|
92
|
+
customTabRenderers={docBrowserRenderers}
|
|
93
|
+
dockControls={docBrowserDockControls}
|
|
94
|
+
/>
|
|
74
95
|
</Suspense>
|
|
75
96
|
) : null}
|
|
76
97
|
{sideDock}
|
|
@@ -79,7 +100,10 @@ export function DesktopAppShell({
|
|
|
79
100
|
{showMobileBottomNav ? <MobileBottomNav /> : null}
|
|
80
101
|
{isDocBrowserOpen && docBrowserMode === "floating" ? (
|
|
81
102
|
<Suspense fallback={null}>
|
|
82
|
-
<DocBrowser
|
|
103
|
+
<DocBrowser
|
|
104
|
+
customTabRenderers={docBrowserRenderers}
|
|
105
|
+
dockControls={docBrowserDockControls}
|
|
106
|
+
/>
|
|
83
107
|
</Suspense>
|
|
84
108
|
) : null}
|
|
85
109
|
</div>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
2
|
import { Copy, Minus, Square, X } from "lucide-react";
|
|
3
3
|
import { BrandHeader } from "@/shared/components/common/brand-header";
|
|
4
|
+
import { cn } from "@/shared/lib/utils";
|
|
4
5
|
import type { DesktopWindowStateSnapshot } from "@/platforms/desktop/types/desktop-update.types";
|
|
5
6
|
|
|
6
7
|
type DesktopWindowControlAction = "minimize" | "toggle-maximize" | "close";
|
|
@@ -17,7 +18,11 @@ const windowControls: WindowControlDefinition[] = [
|
|
|
17
18
|
{ action: "close", label: "Close", icon: X, variant: "danger" },
|
|
18
19
|
];
|
|
19
20
|
|
|
20
|
-
export function DesktopWindowChrome(
|
|
21
|
+
export function DesktopWindowChrome({
|
|
22
|
+
sidebarCollapsed = false,
|
|
23
|
+
}: {
|
|
24
|
+
sidebarCollapsed?: boolean;
|
|
25
|
+
}) {
|
|
21
26
|
const isMaximized = useDesktopWindowMaximizedState();
|
|
22
27
|
const maximizeControl: WindowControlDefinition = isMaximized
|
|
23
28
|
? { action: "toggle-maximize", label: "Restore", icon: Copy }
|
|
@@ -33,21 +38,37 @@ export function DesktopWindowChrome() {
|
|
|
33
38
|
data-testid="desktop-window-chrome-resize-strip"
|
|
34
39
|
/>
|
|
35
40
|
<div
|
|
36
|
-
className=
|
|
41
|
+
className={cn(
|
|
42
|
+
"desktop-window-drag relative z-10 flex h-full w-[var(--desktop-sidebar-width)] shrink-0 items-center bg-secondary text-secondary-foreground",
|
|
43
|
+
sidebarCollapsed ? "justify-center px-0" : "pl-4 pr-3",
|
|
44
|
+
)}
|
|
37
45
|
data-testid="desktop-window-chrome-sidebar"
|
|
38
46
|
>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
{sidebarCollapsed ? (
|
|
48
|
+
<img
|
|
49
|
+
src="/logo.svg"
|
|
50
|
+
alt="NextClaw"
|
|
51
|
+
className="h-6 w-6 shrink-0 object-contain"
|
|
52
|
+
/>
|
|
53
|
+
) : (
|
|
54
|
+
<BrandHeader
|
|
55
|
+
className="flex min-w-0 shrink-0 items-center gap-2.5"
|
|
56
|
+
density="chrome"
|
|
57
|
+
/>
|
|
58
|
+
)}
|
|
43
59
|
</div>
|
|
44
60
|
<div
|
|
45
61
|
className="desktop-window-no-drag absolute right-0 top-0 z-20 flex h-full w-[var(--desktop-caption-safe-right)] items-start justify-end"
|
|
46
62
|
data-testid="desktop-window-controls"
|
|
47
63
|
>
|
|
48
|
-
{[windowControls[0], maximizeControl, windowControls[1]].map(
|
|
49
|
-
|
|
50
|
-
|
|
64
|
+
{[windowControls[0], maximizeControl, windowControls[1]].map(
|
|
65
|
+
(control) => (
|
|
66
|
+
<DesktopWindowControlButton
|
|
67
|
+
key={control.action}
|
|
68
|
+
control={control}
|
|
69
|
+
/>
|
|
70
|
+
),
|
|
71
|
+
)}
|
|
51
72
|
</div>
|
|
52
73
|
</header>
|
|
53
74
|
);
|
|
@@ -86,14 +107,18 @@ function useDesktopWindowMaximizedState(): boolean {
|
|
|
86
107
|
let isSubscribed = true;
|
|
87
108
|
const desktopApi = window.nextclawDesktop;
|
|
88
109
|
|
|
89
|
-
void desktopApi
|
|
90
|
-
|
|
110
|
+
void desktopApi
|
|
111
|
+
?.getWindowState?.()
|
|
112
|
+
.then((snapshot: DesktopWindowStateSnapshot) => {
|
|
113
|
+
if (isSubscribed) {
|
|
114
|
+
setIsMaximized(snapshot.isMaximized);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
const unsubscribe = desktopApi?.onWindowStateChanged?.(
|
|
118
|
+
(snapshot: DesktopWindowStateSnapshot) => {
|
|
91
119
|
setIsMaximized(snapshot.isMaximized);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
const unsubscribe = desktopApi?.onWindowStateChanged?.((snapshot: DesktopWindowStateSnapshot) => {
|
|
95
|
-
setIsMaximized(snapshot.isMaximized);
|
|
96
|
-
});
|
|
120
|
+
},
|
|
121
|
+
);
|
|
97
122
|
|
|
98
123
|
return () => {
|
|
99
124
|
isSubscribed = false;
|