返回 DeepSeek-Reasonix
RuntimeDecisionCard.tsx
根目录 / desktop / frontend / src / components / RuntimeDecisionCard.tsx
1 import { useEffect, useRef } from "react";
2 import { PromptAction, PromptBadge, PromptShelf } from "./PromptShelf";
3
4 export type RuntimeDecisionAction = {
5 key: string;
6 label: string;
7 description: string;
8 onClick: () => void;
9 danger?: boolean;
10 disabled?: boolean;
11 };
12
13 export function RuntimeDecisionCard({
14 id,
15 title,
16 badge,
17 meta,
18 note,
19 actions,
20 secondaryAction,
21 onCancel,
22 }: {
23 id: string;
24 title: string;
25 badge: string;
26 meta: string;
27 note?: string;
28 actions: RuntimeDecisionAction[];
29 secondaryAction?: RuntimeDecisionAction;
30 onCancel: () => void;
31 }) {
32 const shelfRef = useRef<HTMLDivElement | null>(null);
33 const actionsRef = useRef(actions);
34 const onCancelRef = useRef(onCancel);
35 actionsRef.current = actions;
36 onCancelRef.current = onCancel;
37
38 useEffect(() => {
39 shelfRef.current?.focus();
40 const onKeyDown = (event: globalThis.KeyboardEvent) => {
41 const target = event.target instanceof Element ? event.target : null;
42 const tag = target?.tagName.toLowerCase();
43 if (tag === "input" || tag === "textarea" || (target instanceof HTMLElement && target.isContentEditable)) return;
44
45 if (event.key === "Escape") {
46 event.preventDefault();
47 onCancelRef.current();
48 return;
49 }
50 if (event.repeat || event.metaKey || event.ctrlKey || event.altKey) return;
51 const action = actionsRef.current.find((candidate) => candidate.key === event.key);
52 if (!action || action.disabled) return;
53 event.preventDefault();
54 action.onClick();
55 };
56 document.addEventListener("keydown", onKeyDown);
57 return () => document.removeEventListener("keydown", onKeyDown);
58 }, []);
59
60 return (
61 <PromptShelf
62 decision
63 className="prompt-shelf--runtime-decision"
64 barRef={shelfRef}
65 titleId={`${id}-title`}
66 title={title}
67 badges={<PromptBadge tone="amber">{badge}</PromptBadge>}
68 meta={meta}
69 actionsRole="group"
70 actions={actions.map((action) => (
71 <PromptAction
72 key={action.key}
73 keyLabel={action.key}
74 label={action.label}
75 description={action.description}
76 descriptionDisclosure
77 descriptionAlwaysVisible={Boolean(action.danger)}
78 onClick={action.onClick}
79 disabled={action.disabled}
80 tone={action.danger ? "danger" : "default"}
81 role="button"
82 />
83 ))}
84 footer={secondaryAction ? (
85 <div className="runtime-decision-footer">
86 <button
87 type="button"
88 className="btn btn--small runtime-decision-footer__secondary"
89 onClick={secondaryAction.onClick}
90 disabled={secondaryAction.disabled}
91 >
92 {secondaryAction.label}
93 </button>
94 </div>
95 ) : undefined}
96 >
97 {note && <p className="prompt-shelf__note">{note}</p>}
98 </PromptShelf>
99 );
100 }
101
101 lines Plain Text