返回 DeepSeek-Reasonix
useComposerProfileProjection.ts
根目录 / desktop / frontend / src / app-runtime / useComposerProfileProjection.ts
1 import { useMemo, type Dispatch, type SetStateAction } from "react";
2 import { useCommittedCommand } from "../lib/useCommittedCommand";
3 import { useRemoteComposerProfileSync } from "../lib/useRemoteComposerIntegration";
4 import {
5 composerProfileForOwner,
6 composerProfileFromMeta,
7 composerProfileFromTab,
8 composerProfileOwner,
9 composerProfileOwnersConflict,
10 composerProfileMode,
11 defaultComposerProfile,
12 displayedComposerProfileCollaborationMode,
13 patchComposerProfile,
14 updateUserPlanModeIntent,
15 type ComposerProfile,
16 type ComposerProfileField,
17 type UserPlanModeIntents,
18 } from "../lib/composerProfile";
19 import type { Meta, TabMeta } from "../lib/types";
20 import type { RemoteSessionApi } from "../lib/useRemoteSession";
21
22 export type ComposerProfileProjectionInput = {
23 activeTabId: string | undefined;
24 activeTab: TabMeta | undefined;
25 meta: Meta | null | undefined;
26 profilesByTab: Record<string, ComposerProfile>;
27 setProfilesByTab: Dispatch<SetStateAction<Record<string, ComposerProfile>>>;
28 tabMetas: readonly TabMeta[];
29 remote: boolean;
30 remoteSession: RemoteSessionApi;
31 planIntentsRef: { current: UserPlanModeIntents };
32 };
33
34 /**
35 * Owns the active composer profile projection (UI override over the backend
36 * profile, remote sync) and the profile patch commands: generic per-tab
37 * patches and goal activation patches. Mode axis
38 * changes stay in useComposerModeActions; this hook owns the profile record.
39 */
40 export function useComposerProfileProjection(input: ComposerProfileProjectionInput) {
41 const { activeTabId, activeTab, meta, profilesByTab, setProfilesByTab, tabMetas, remote, remoteSession } = input;
42 const activeOwner = composerProfileOwner(activeTab);
43 const activeComposerProfile = activeTabId ? composerProfileForOwner(profilesByTab[activeTabId], activeOwner) : undefined;
44 const backendActiveComposerProfile = useMemo(() => {
45 if (meta && !composerProfileOwnersConflict(composerProfileOwner(meta), activeOwner)) {
46 return composerProfileFromMeta(
47 meta,
48 activeTab ? composerProfileMode(composerProfileFromTab(activeTab, activeComposerProfile?.toolApprovalMode)) : undefined,
49 activeComposerProfile?.toolApprovalMode,
50 );
51 }
52 return composerProfileFromTab(activeTab, activeComposerProfile?.toolApprovalMode);
53 }, [activeComposerProfile?.toolApprovalMode, activeOwner, activeTab, meta]);
54 const composerProfile = activeTabId
55 ? activeComposerProfile ?? backendActiveComposerProfile
56 : defaultComposerProfile;
57 const goal = composerProfile.goal;
58 const collaborationMode = displayedComposerProfileCollaborationMode(composerProfile);
59 const toolApprovalMode = composerProfile.toolApprovalMode;
60 const remoteSessionRoute = activeTab?.remote
61 ? (activeTab.sessionId ? `session-id:${activeTab.sessionId}` : activeTab.sessionPath ?? "")
62 : "";
63 const remoteComposerProfileReady = useRemoteComposerProfileSync({ activeTabId, sessionRoute: remoteSessionRoute, remote,
64 remoteProfile: remoteSession.composerProfile, collaborationMode, toolApprovalMode, goal,
65 qualityFloor: composerProfile.qualityFloor, pending: composerProfile.pending, setProfiles: setProfilesByTab });
66
67 const patchActiveComposerProfile = useCommittedCommand((patch: Partial<Omit<ComposerProfile, "pending">>, pendingFields: ComposerProfileField[]) => {
68 if (!activeTabId) return;
69 setProfilesByTab((current) => patchComposerProfile(current, activeTabId, composerProfile, patch, pendingFields));
70 });
71 const patchComposerProfileForTab = useCommittedCommand((tabId: string, patch: Partial<Omit<ComposerProfile, "pending">>, pendingFields: ComposerProfileField[]) => {
72 if (!tabId) return;
73 setProfilesByTab((current) => {
74 const tab = tabMetas.find((value) => value.id === tabId);
75 const base = composerProfileForOwner(current[tabId], composerProfileOwner(tab)) ?? composerProfileFromTab(tab);
76 return patchComposerProfile(current, tabId, base, patch, pendingFields);
77 });
78 });
79
80 const patchActivatedGoalForTab = useCommittedCommand((tabId: string, nextGoal: string): void => {
81 const trimmed = nextGoal.trim();
82 patchComposerProfileForTab(tabId, {
83 collaborationMode: trimmed ? "goal" : "normal",
84 goalDraftMode: false,
85 goal: trimmed,
86 }, ["collaborationMode", "goal"]);
87 input.planIntentsRef.current = updateUserPlanModeIntent(input.planIntentsRef.current, tabId, false);
88 });
89
90 return {
91 composerProfile,
92 goal,
93 collaborationMode,
94 toolApprovalMode,
95 remoteComposerProfileReady,
96 patchActiveComposerProfile,
97 patchComposerProfileForTab,
98 patchActivatedGoalForTab,
99 };
100 }
101
101 lines TYPESCRIPT