返回 DeepSeek-Reasonix
GoalLifecycleActions.tsx
根目录 / desktop / frontend / src / components / GoalLifecycleActions.tsx
1 import { useEffect, useRef, useState } from "react";
2 import { useI18n } from "../lib/i18n";
3 import type { GoalLifecycleView } from "../lib/types";
4
5 export function GoalLifecycleActions({
6 goalView,
7 goalStatus,
8 disabled,
9 running,
10 onEditGoal,
11 onPauseGoal,
12 onResumeGoal,
13 onStopGoal,
14 }: {
15 goalView?: GoalLifecycleView;
16 goalStatus?: string;
17 disabled?: boolean;
18 running: boolean;
19 onEditGoal: (objective: string, maxGoalRounds: number | null) => void;
20 onPauseGoal: () => void;
21 onResumeGoal: () => void;
22 onStopGoal: () => void;
23 }) {
24 const { t } = useI18n();
25 const resumable = goalView?.phase === "paused"
26 || goalView?.phase === "blocked"
27 || (goalView?.phase === "active" && goalView.activation === "disarmed")
28 || (!goalView && goalStatus === "blocked");
29
30 const [draft, setDraft] = useState<{ objective: string; rounds: string; invalid: boolean } | null>(null);
31
32 const editButtonRef = useRef<HTMLButtonElement>(null);
33 const wasOpen = useRef(false);
34 useEffect(() => {
35 if (wasOpen.current && !draft) editButtonRef.current?.focus();
36 wasOpen.current = draft !== null;
37 }, [draft]);
38
39 const openEditor = () => {
40 if (!goalView) return;
41 setDraft({ objective: goalView.objective, rounds: goalView.maxGoalRounds?.toString() ?? "", invalid: false });
42 };
43
44 const submitEdit = (event: React.FormEvent) => {
45 event.preventDefault();
46 if (!draft) return;
47 const trimmed = draft.rounds.trim();
48 const limit = trimmed === "" ? null : Number(trimmed);
49 if (limit !== null && (!Number.isSafeInteger(limit) || limit <= 0)) {
50 setDraft({ ...draft, invalid: true });
51 return;
52 }
53 setDraft(null);
54 onEditGoal(draft.objective, limit);
55 };
56
57 return <>
58 {draft && (
59 <form className="goal-edit-form" data-goal-edit-form="" onSubmit={submitEdit} onKeyDown={(e) => { if (e.key === "Escape") { e.preventDefault(); e.stopPropagation(); setDraft(null); } }}>
60 <label>
61 {t("composer.goalEditObjective")}
62 <textarea
63 className="remote-secret-dialog__input"
64 rows={2}
65 data-goal-edit-objective=""
66 autoFocus
67 value={draft.objective}
68 onChange={(e) => setDraft({ ...draft, objective: e.target.value })}
69 />
70 </label>
71 <label>
72 {t("composer.goalEditMaxRounds")}
73 <input
74 className="remote-secret-dialog__input"
75 data-goal-edit-rounds=""
76 inputMode="numeric"
77 value={draft.rounds}
78 onChange={(e) => setDraft({ ...draft, rounds: e.target.value, invalid: false })}
79 />
80 </label>
81 {draft.invalid && <p className="goal-edit-form__error" role="alert" data-goal-edit-error="">{t("composer.goalEditInvalidRounds")}</p>}
82 <div className="goal-edit-form__actions">
83 <button type="button" className="btn" onClick={() => setDraft(null)}>{t("common.cancel")}</button>
84 <button type="submit" className="btn btn--primary" disabled={draft.objective.trim() === ""}>{t("common.save")}</button>
85 </div>
86 </form>
87 )}
88 {goalView && <button ref={editButtonRef} type="button" className="composer-intent-menu__stop" onClick={openEditor} disabled={disabled || draft !== null}>
89 {t("composer.taskModeEditGoal")}
90 </button>}
91 {resumable ? (
92 <button type="button" className="composer-intent-menu__stop" onClick={onResumeGoal} disabled={disabled}>
93 {t("composer.taskModeResumeGoal")}
94 </button>
95 ) : (
96 <button type="button" className="composer-intent-menu__stop" onClick={onPauseGoal} disabled={disabled}>
97 {t("composer.taskModePauseGoal")}
98 </button>
99 )}
100 <button type="button" className="composer-intent-menu__stop" onClick={onStopGoal} disabled={disabled || running}>
101 {t("composer.taskModeStopGoal")}
102 </button>
103 </>;
104 }
105
105 lines Plain Text