返回 DeepSeek-Reasonix
useAppNavigationComposition.ts
根目录 / desktop / frontend / src / app-runtime / useAppNavigationComposition.ts
1 import { browserMockScenarioParam, GUIDANCE_QUEUE_MOCK_ITEMS, isGuidanceMockScenario } from "../lib/mockScenarios";
2 import { desktopHost } from "../lib/desktopHost";
3 import { formatShortcutCombo, resolvedShortcutCombo } from "../lib/keyboardShortcuts";
4 import { showWorktreeCleanupNotice } from "../lib/worktreeCleanupNotice";
5 import { desktopBridge } from "./desktopBridgeAdapter";
6 import { desktopProjectAdapter } from "./desktopProjectAdapter";
7 import { useHistoryCommands } from "./useHistoryCommands";
8 import { useBrowserFirstOpen } from "./useBrowserFirstOpen";
9 import { useSessionNavigationCommands } from "./useSessionNavigationCommands";
10 import { usePaletteCommands } from "./usePaletteCommands";
11 import { useTopicNavigationShortcuts } from "./useTopicNavigationShortcuts";
12 import { useProjectTopicCommands } from "./useProjectTopicCommands";
13 import { useAppChromeCommands } from "./useAppChromeCommands";
14 import { useOnboardingCommands } from "./useOnboardingCommands";
15 import { useWorktreeMergeCommands } from "./useWorktreeMergeCommands";
16 import type { HistoryViewState } from "./historyViewProjection";
17 import type { State } from "../lib/useController";
18 import type { TabMeta } from "../lib/types";
19 import type { Translator } from "../lib/i18n";
20 import type { useAppRuntimeAdapter } from "./useAppRuntimeAdapter";
21 import type { useAppShellStores } from "./useAppShellStores";
22 import type { useNavigationSurface } from "../lib/useNavigationSurface";
23 import type { useAppSessionComposition } from "./useAppSessionComposition";
24
25 type Runtime = ReturnType<typeof useAppRuntimeAdapter>;
26 type Shell = ReturnType<typeof useAppShellStores>;
27 type SessionComposition = ReturnType<typeof useAppSessionComposition>;
28
29 export type AppNavigationCompositionInput = {
30 runtime: Runtime;
31 t: Translator;
32 notice: Runtime["snapshot"]["notice"];
33 showToast: (message: string, level?: "info" | "warn" | "error", options?: { durationMs?: number }) => void;
34 shell: Shell;
35 state: State;
36 activeTab: TabMeta | undefined;
37 activeTabId: string | undefined;
38 activeSessionIdentity: string;
39 remoteSurfaceActive: boolean;
40 surface: Pick<ReturnType<typeof useNavigationSurface>, "begin" | "maskTarget">;
41 local: {
42 setHistView: React.Dispatch<React.SetStateAction<HistoryViewState | null>>;
43 setProjectRevision: React.Dispatch<React.SetStateAction<number>>;
44 setSidebarImDetailConnectionId: React.Dispatch<React.SetStateAction<string>>;
45 setTasksOpen: React.Dispatch<React.SetStateAction<false | "session" | "all">>;
46 };
47 session: SessionComposition;
48 };
49
50 /**
51 * Navigation/chrome composition: history, automation, desktop and session
52 * navigation, command palette, topic shortcuts, project/topic commands,
53 * window chrome and worktree merge. Runs after the session composition in
54 * the App body's original order; pure relocation.
55 */
56 export function useAppNavigationComposition(input: AppNavigationCompositionInput) {
57 const { runtime, t, notice, showToast, shell, state, activeTab, activeTabId, activeSessionIdentity, session } = input;
58 const { remoteSurfaceActive } = input;
59 const { begin: beginNavigationSurface, maskTarget: settleNavigationSurface } = input.surface;
60 const {
61 setHistView, setProjectRevision,
62 setSidebarImDetailConnectionId, setTasksOpen,
63 } = input.local;
64 const {
65 noteNavigationIntent, registeredNavigationIntent,
66 isNavigationIntentCurrent, syncActiveTab, ensureBlankSurface,
67 } = runtime.navigation;
68 const { listSessions, deleteSession, renameSession } = runtime.sessionActions;
69 const { refreshMeta, pickWorkspace, switchWorkspace } = runtime.workspace;
70 const {
71 managementActive, desktopPlatform, windowsFramelessChrome, sidebarCollapsed,
72 openPage, returnToWorkspace, enterConversation,
73 setSettingsTarget, setSettingsFocus, setSidebarSearchOpen, setSidebarSearchFocusSignal, setProviderSetupNeeded,
74 } = shell;
75 const { reload: reloadDesktopPreferences } = shell.preferences;
76 const {
77 closeTransientOverlays, refreshProviderSetupState,
78 tabBarCommands, terminalPanelCommands, remoteWorkspaceCommands, runtimeEventCommands,
79 desktopNavigation: desktopNavigationBag,
80 } = session;
81 const { refreshTabMetas, seedActiveTabMeta } = runtimeEventCommands;
82 const { handleTabClose } = tabBarCommands;
83 const { toggleTerminalPanel } = terminalPanelCommands;
84 const { openRemoteWorkspaceFromStatus, connectAndOpenRemoteWorkspace } = remoteWorkspaceCommands;
85 const { enqueueNavigation, enqueueNavigationWithIntent, openRemoteProject } = desktopNavigationBag;
86 const { toggleSidebar } = session.shellGeometry;
87 useBrowserFirstOpen(activeTabId, state.activeTurnId, () => session.workspacePanelCommands.openRightDockMode("browser"), state.meta?.sessionPath);
88
89 const historyCommands = useHistoryCommands({
90 running: state.running,
91 setHistView,
92 ports: {
93 listSessions,
94 deleteSession,
95 renameSession,
96 openPage: (page) => openPage(page),
97 },
98 });
99 const { openTrash, refreshHistoryView } = historyCommands;
100
101
102 const navigationCommands = useSessionNavigationCommands({
103 activeTab,
104 showToast,
105 closeTransientOverlays,
106 clearImDetail: () => setSidebarImDetailConnectionId(""),
107 prepareBlankWorkspace: (workspaceRoot) => {
108 session.workspacePanelCommands.prepareBlankWorkspace(workspaceRoot);
109 },
110 navigation: { enqueueNavigation, enqueueNavigationWithIntent, openRemoteProject },
111 noteNavigationIntent,
112 beginNavigationSurface,
113 settleNavigationSurface,
114 isNavigationIntentCurrent,
115 markProjectChanged: setProjectRevision,
116 refreshTabMetas,
117 refreshHistoryView,
118 enterConversation,
119 pickWorkspace,
120 switchWorkspace,
121 ports: {
122 openTaskSessionForTab: (tabId, taskId) => desktopBridge.openTaskSessionForTab(tabId, taskId),
123 listSessionsForTab: (tabId) => desktopBridge.listSessionsForTab(tabId),
124 },
125 });
126 const { openBlankSession, handleNewTab, onResumeSession, switchFolder, handleNavigateTopic } = navigationCommands;
127
128 // Command palette: ⌘K / Ctrl+K opens a fuzzy navigator over commands and
129 // recent sessions. Sessions are snapshotted on open so the list is stable
130 // while the palette is up; extension actions follow the same snapshot rule.
131 const { openPalette, paletteItems } = usePaletteCommands({
132 managementActive,
133 activeTabId,
134 remoteSurfaceActive,
135 t,
136 notice,
137 showToast,
138 ports: {
139 handleNewTab: () => void handleNewTab(),
140 listSessions,
141 openTrash: () => void openTrash(),
142 onResumeSession: (session) => onResumeSession(session),
143 openRemoteWorkspaceFromStatus: (host) => openRemoteWorkspaceFromStatus(host),
144 connectAndOpenRemoteWorkspace: (host) => connectAndOpenRemoteWorkspace(host),
145 toggleTerminalPanel,
146 setTasksOpen: (open) => setTasksOpen(open),
147 handleTabClose: (id) => void handleTabClose(id),
148 toggleSidebar,
149 returnToWorkspace,
150 },
151 });
152
153 // --- Topic shortcut navigation (Cmd/Ctrl+1-9) ---
154 const { showBadges: showTopicBadges, setVisibleTopics: handleVisibleTopicsChange } = useTopicNavigationShortcuts({
155 enabled: !sidebarCollapsed && !managementActive,
156 platform: desktopPlatform,
157 onNavigate: handleNavigateTopic,
158 });
159
160 // Delete / rename act on disk, then re-fetch so the panel reflects the change.
161 // Workspace: open the folder chooser and switch projects. The hook resets the
162 // transcript and refreshes meta on a pick. A cancel is a no-op.
163 const projectTopicCommands = useProjectTopicCommands({
164 visible: { tabId: activeTabId ?? "", sessionKey: activeSessionIdentity },
165 topic: activeTab?.remote ? {
166 id: activeTab.id, title: activeTab.topicTitle || "",
167 target: { kind: "remote", ...activeTab.remote, sessionPath: activeTab.sessionPath || "", sessionId: activeTab.session?.sessionId || activeTab.sessionId },
168 } : activeTab?.topicId ? {
169 id: activeTab.topicId, title: activeTab.topicTitle || "", target: { kind: "local", topicId: activeTab.topicId,
170 selector: { ref: activeTab.session ?? undefined, sessionPath: activeTab.sessionPath, topicId: activeTab.topicId } },
171 } : undefined,
172 ports: { ...desktopProjectAdapter, markChanged: setProjectRevision, refreshTabs: refreshTabMetas, syncActive: syncActiveTab },
173 navigation: { openBlank: openBlankSession, enqueue: enqueueNavigation, switchFolder },
174 reportError: error => showToast(error instanceof Error ? error.message : String(error), "error"),
175 });
176
177 const sidebarExpandBlocked = false;
178 const sidebarToggleTitle = sidebarCollapsed
179 ? t("sidebar.expand")
180 : t("sidebar.collapse");
181 const browserPreviewChrome = typeof window !== "undefined" && desktopHost().kind === "none";
182 const browserMockScenario = browserPreviewChrome ? browserMockScenarioParam() : "";
183 const guidanceQueueMockItems = isGuidanceMockScenario(browserMockScenario) ? GUIDANCE_QUEUE_MOCK_ITEMS : undefined;
184 // Command palette shortcut label (⌘K / Ctrl+K), platform-aware.
185 const commandPaletteShortcut = formatShortcutCombo(
186 resolvedShortcutCombo("commandPalette.open", desktopPlatform),
187 desktopPlatform,
188 );
189 const chromeCommands = useAppChromeCommands({
190 platform: desktopPlatform,
191 windowsFrameless: windowsFramelessChrome,
192 closeTransientOverlays,
193 clearImDetail: () => setSidebarImDetailConnectionId(""),
194 setSettingsFocus,
195 setSettingsTarget,
196 setSidebarSearchOpen,
197 setSidebarSearchFocusSignal,
198 refreshMeta,
199 refreshProviderSetupState,
200 reloadDesktopPreferences: (settings) => reloadDesktopPreferences(settings),
201 });
202 const onboardingCommands = useOnboardingCommands(() => setProviderSetupNeeded(false));
203 const worktreeMergeCommands = useWorktreeMergeCommands({
204 noteNavigationIntent,
205 registeredNavigationIntent, isNavigationIntentCurrent, ensureBlankSurface,
206 seedSource: seedActiveTabMeta, listTabs: desktopBridge.listTabs,
207 closeWorktree: desktopBridge.closeMergedWorktreeTab, finalize: desktopBridge.finalizeWorktreeMerge,
208 showToast, t, showCleanup: (cleanup, translate) => showWorktreeCleanupNotice(cleanup, translate, showToast),
209 });
210 return {
211 historyCommands,
212 navigationCommands,
213 paletteCommands: { openPalette, paletteItems },
214 topicShortcuts: { showTopicBadges, handleVisibleTopicsChange },
215 projectTopicCommands,
216 chromeCommands,
217 onboardingCommands,
218 worktreeMergeCommands,
219 sidebarExpandBlocked,
220 sidebarToggleTitle,
221 browserPreviewChrome,
222 commandPaletteShortcut,
223 guidanceQueueMockItems,
224 };
225 }
226
226 lines TYPESCRIPT