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