返回 DeepSeek-Reasonix
useComposerInsertCommands.ts
根目录 / desktop / frontend / src / app-runtime / useComposerInsertCommands.ts
1 import { useRef, useState } from "react";
2 import { useCommittedCommand } from "../lib/useCommittedCommand";
3 import { formatTerminalOutputForComposer } from "../lib/terminalOutput";
4 import { formatSelectionReference, type SelectedTextInsertRequest } from "../lib/selectedTextContext";
5 import type { ComposerInsertRequest } from "../lib/types";
6 import type { Translator } from "../lib/i18n";
7 import type { useSessionOperations } from "./useSessionOperations";
8
9 export type WorkspaceInsertTarget = "composer" | "planRevision";
10
11 export type ComposerInsertCommandsInput = {
12 activeTabId: string | undefined;
13 sessionKey: string;
14 approval: { id: string; tool: string } | undefined | null;
15 operations: ReturnType<typeof useSessionOperations>;
16 t: Translator;
17 showToast: (message: string, kind: "info" | "warn" | "error") => void;
18 ports: {
19 terminalOutput(tabId: string, sessionId: string): Promise<string>;
20 };
21 };
22
23 /**
24 * Owns every composer-bound insertion channel: per-tab composer insert
25 * requests, selected-text/code requests, the plan-revision insert and the
26 * workspace insert target that routes between them, plus terminal-output
27 * insertion through the session operations authority. The plan-revision
28 * input is plain text and only consumes request.text, so structured
29 * references land there in their fenced rendering.
30 */
31 export function useComposerInsertCommands(input: ComposerInsertCommandsInput) {
32 const { activeTabId, approval, t, showToast, ports } = input;
33 const [composerInsertRequestsByTab, setComposerInsertRequestsByTab] = useState<Record<string, ComposerInsertRequest>>({});
34 const [selectedTextRequestsByTab, setSelectedTextRequestsByTab] = useState<Record<string, SelectedTextInsertRequest>>({});
35 const selectedTextRequestIdRef = useRef(0);
36 const [planRevisionInsertRequest, setPlanRevisionInsertRequest] = useState<{
37 tabId: string;
38 approvalId: string;
39 request: ComposerInsertRequest;
40 } | null>(null);
41 const [workspaceInsertTarget, setWorkspaceInsertTarget] = useState<WorkspaceInsertTarget>("composer");
42
43 const activePlanRevisionInsertRequest =
44 planRevisionInsertRequest &&
45 planRevisionInsertRequest.tabId === activeTabId &&
46 planRevisionInsertRequest.approvalId === approval?.id
47 ? planRevisionInsertRequest.request
48 : null;
49 const composerInsertRequest = activeTabId ? composerInsertRequestsByTab[activeTabId] ?? null : null;
50 const selectedTextRequest = activeTabId ? selectedTextRequestsByTab[activeTabId] ?? null : null;
51
52 const setInsertTarget = useCommittedCommand((target: WorkspaceInsertTarget) => setWorkspaceInsertTarget(target));
53 const handleRevisionActiveChange = useCommittedCommand((active: boolean) => {
54 setWorkspaceInsertTarget(active ? "planRevision" : "composer");
55 });
56
57 const replaceComposerInsert = useCommittedCommand((tabId: string, text: string) => {
58 setComposerInsertRequestsByTab((current) => ({ ...current, [tabId]: { id: Date.now(), text, mode: "replace" } }));
59 });
60 const prefillSubagentCommand = useCommittedCommand((command: string) => {
61 if (!activeTabId) return;
62 setComposerInsertRequestsByTab((current) => ({
63 ...current,
64 [activeTabId]: { id: Date.now(), text: command, mode: "prefix" },
65 }));
66 });
67
68 const addWorkspaceTextToComposer = useCommittedCommand((text: string) => {
69 if (activeTabId && workspaceInsertTarget === "planRevision" && approval?.tool === "exit_plan_mode") {
70 setPlanRevisionInsertRequest({
71 tabId: activeTabId,
72 approvalId: approval.id,
73 request: { id: Date.now(), text },
74 });
75 return;
76 }
77 if (activeTabId) {
78 setComposerInsertRequestsByTab((current) => ({
79 ...current,
80 [activeTabId]: { id: Date.now(), text },
81 }));
82 }
83 });
84
85 const addTerminalOutputToComposer = useCommittedCommand(async (sessionId: string) => {
86 if (!activeTabId) return;
87 const target = { tabId: activeTabId, sessionKey: input.sessionKey };
88 const outcome = await input.operations(target, `terminal-output:${sessionId}`, {}, async (_operationInput, authority) =>
89 (await import("./sessionRuntimeOwner")).executeTerminalOutputInsertion(target, sessionId, {
90 read: (tabId, terminalSessionId) => ports.terminalOutput(tabId, terminalSessionId),
91 apply: addWorkspaceTextToComposer,
92 }, formatTerminalOutputForComposer, authority),
93 );
94 if (outcome.status === "completed" && !outcome.value) showToast(t("terminal.noOutput"), "info");
95 if (outcome.status === "failed") showToast(outcome.error instanceof Error ? outcome.error.message : String(outcome.error), "error");
96 });
97
98 const addSelectedTextToComposer = useCommittedCommand((text: string, source?: SelectedTextInsertRequest["source"]) => {
99 const selected = text.trim();
100 if (!activeTabId || !selected) return;
101 selectedTextRequestIdRef.current += 1;
102 setSelectedTextRequestsByTab((current) => ({
103 ...current,
104 [activeTabId]: { id: selectedTextRequestIdRef.current, text: selected, ...(source ? { source } : {}) },
105 }));
106 });
107
108 const addTerminalSelectionToComposer = useCommittedCommand((text: string) => addSelectedTextToComposer(text, "terminal"));
109 const addWorkspaceCodeToComposer = useCommittedCommand((path: string, code: string) => {
110 if (!activeTabId || !code.trim()) return;
111 if (workspaceInsertTarget === "planRevision" && approval?.tool === "exit_plan_mode") {
112 setPlanRevisionInsertRequest({
113 tabId: activeTabId,
114 approvalId: approval.id,
115 request: { id: Date.now(), text: formatSelectionReference(path, code) },
116 });
117 return;
118 }
119 selectedTextRequestIdRef.current += 1;
120 setSelectedTextRequestsByTab((current) => ({
121 ...current,
122 [activeTabId]: { id: selectedTextRequestIdRef.current, text: code, path },
123 }));
124 });
125
126 return {
127 composerInsertRequest,
128 selectedTextRequest,
129 activePlanRevisionInsertRequest,
130 setInsertTarget,
131 handleRevisionActiveChange,
132 replaceComposerInsert,
133 prefillSubagentCommand,
134 addWorkspaceTextToComposer,
135 addTerminalOutputToComposer,
136 addSelectedTextToComposer,
137 addTerminalSelectionToComposer,
138 addWorkspaceCodeToComposer,
139 };
140 }
141
141 lines TYPESCRIPT