返回 DeepSeek-Reasonix
DecisionFooterRegion.tsx
根目录 / desktop / frontend / src / app-shell / DecisionFooterRegion.tsx
1 import { lazy, Suspense, type ComponentProps, type CSSProperties, type ReactNode } from "react";
2
3 import { Composer } from "../components/Composer";
4 import { useT } from "../lib/i18n";
5
6 const TodoPanel = lazy(() => import("../components/TodoPanel").then((module) => ({ default: module.TodoPanel })));
7 const UndoRewindBanner = lazy(() => import("../components/UndoRewindBanner").then((module) => ({ default: module.UndoRewindBanner })));
8 const ApprovalModal = lazy(() => import("../components/ApprovalModal").then((module) => ({ default: module.ApprovalModal })));
9 const AskCard = lazy(() => import("../components/AskCard").then((module) => ({ default: module.AskCard })));
10 const MCPInteractionCard = lazy(() => import("../components/MCPInteractionCard").then((module) => ({ default: module.MCPInteractionCard })));
11 const ExtensionFormDialog = lazy(() => import("../components/ExtensionFormDialog").then((module) => ({ default: module.ExtensionFormDialog })));
12 const RuntimeDecisionCard = lazy(() => import("../components/RuntimeDecisionCard").then((module) => ({ default: module.RuntimeDecisionCard })));
13 const ClearContextCard = lazy(() => import("../components/ClearContextCard").then((module) => ({ default: module.ClearContextCard })));
14
15 export type ComposerProps = ComponentProps<(typeof import("../components/Composer"))["Composer"]>;
16 export type TodoProps = ComponentProps<(typeof import("../components/TodoPanel"))["TodoPanel"]>;
17 export type UndoProps = ComponentProps<(typeof import("../components/UndoRewindBanner"))["UndoRewindBanner"]>;
18 export type ApprovalProps = ComponentProps<(typeof import("../components/ApprovalModal"))["ApprovalModal"]>;
19 export type AskProps = ComponentProps<(typeof import("../components/AskCard"))["AskCard"]>;
20 export type McpProps = ComponentProps<(typeof import("../components/MCPInteractionCard"))["MCPInteractionCard"]>;
21 export type ExtensionProps = ComponentProps<(typeof import("../components/ExtensionFormDialog"))["ExtensionFormDialog"]>;
22 export type RuntimeDecisionProps = ComponentProps<(typeof import("../components/RuntimeDecisionCard"))["RuntimeDecisionCard"]>;
23 export type ClearContextProps = ComponentProps<(typeof import("../components/ClearContextCard"))["ClearContextCard"]>;
24
25 export type DecisionFooterSurface =
26 | { kind: "approval"; identity: string; props: ApprovalProps }
27 | { kind: "ask"; identity: string; props: AskProps }
28 | { kind: "mcp"; identity: string; props: McpProps }
29 | { kind: "extension"; identity: string; props: ExtensionProps }
30 | { kind: "runtime"; identity: string; props: RuntimeDecisionProps }
31 | { kind: "clear-context"; identity: string; props: ClearContextProps };
32
33 /** Loading one decision cannot hide an already available sibling or its focus. */
34 export function DecisionFooterSlots({ todo, undo, decision }: { todo: ReactNode; undo: ReactNode; decision: ReactNode }) {
35 return <>
36 <Suspense fallback={null}>{todo}</Suspense>
37 <Suspense fallback={null}>{undo}</Suspense>
38 <Suspense fallback={null}>{decision}</Suspense>
39 </>;
40 }
41
42 export type DecisionFooterRegionProps = {
43 hidden: boolean;
44 className: string;
45 style?: CSSProperties;
46 footerRef: ComponentProps<"footer">["ref"];
47 creationNotice?: ReactNode;
48 todo?: { identity: string; props: TodoProps };
49 undo?: { identity: string; props: UndoProps };
50 decision?: DecisionFooterSurface;
51 composer: {
52 empty?: { onCreate: () => void; onChooseProject: () => void };
53 hidden: boolean;
54 inert: boolean;
55 hero: boolean;
56 headline?: string;
57 hint?: string;
58 props: ComposerProps;
59 };
60 };
61
62 function DecisionSurface({ surface }: { surface: DecisionFooterSurface }) {
63 switch (surface.kind) {
64 case "approval":
65 return <ApprovalModal key={surface.identity} {...surface.props} />;
66 case "ask":
67 return <AskCard key={surface.identity} {...surface.props} />;
68 case "mcp":
69 return <MCPInteractionCard key={surface.identity} {...surface.props} />;
70 case "extension":
71 return <ExtensionFormDialog key={surface.identity} {...surface.props} />;
72 case "runtime":
73 return <RuntimeDecisionCard key={surface.identity} {...surface.props} />;
74 case "clear-context":
75 return <ClearContextCard key={surface.identity} {...surface.props} />;
76 }
77 }
78
79 export function DecisionFooterRegion({
80 hidden,
81 className,
82 style,
83 footerRef,
84 creationNotice,
85 todo,
86 undo,
87 decision,
88 composer,
89 }: DecisionFooterRegionProps) {
90 const t = useT();
91 if (hidden) return null;
92
93 return (
94 <footer className={className} ref={footerRef} style={style} inert={composer.inert || undefined} aria-hidden={composer.inert || undefined}>
95 <DecisionFooterSlots
96 todo={todo ? <TodoPanel key={todo.identity} {...todo.props} /> : null}
97 undo={undo ? <UndoRewindBanner key={undo.identity} {...undo.props} /> : null}
98 decision={decision ? <DecisionSurface surface={decision} /> : null}
99 />
100 {/* Composer remains mounted while decisions are visible so session-scoped drafts survive. */}
101 <div
102 className={[
103 "composer-decision-host",
104 composer.inert ? "composer-decision-host--footprint-hidden" : composer.hidden ? "composer-decision-host--hidden" : "",
105 composer.hero ? "composer-decision-host--creation-hero" : "",
106 ].filter(Boolean).join(" ")}
107 hidden={Boolean(decision) || undefined}
108 inert={composer.hidden ? true : undefined}
109 aria-hidden={composer.hidden ? true : undefined}
110 >
111 {composer.hero && composer.headline ? <h2 className="welcome-creation__headline">{composer.headline}</h2> : null}
112 {creationNotice}
113 {composer.empty ? <div className="welcome-creation">
114 <button className="btn btn--primary" onClick={composer.empty.onCreate}>{t("topbar.newSession")}</button>
115 <button className="btn" onClick={composer.empty.onChooseProject}>{t("projectTree.addProjectTooltip")}</button>
116 </div> : <Composer {...composer.props} />}
117 {composer.hero && composer.hint ? <p className="composer-decision-host__hint">{composer.hint}</p> : null}
118 </div>
119 </footer>
120 );
121 }
122
122 lines Plain Text