返回 DeepSeek-Reasonix
PermissionPresetChoice.tsx
根目录 / desktop / frontend / src / components / PermissionPresetChoice.tsx
1 import { useCallback, useEffect, useState } from "react";
2 import { Eye, ShieldAlert, ShieldCheck } from "lucide-react";
3 import { useT } from "../lib/i18n";
4 import { normalizeToolApprovalMode, type ToolApprovalMode } from "../lib/types";
5 import { hasConfirmedFullAccessForProject, rememberFullAccessConfirmationForProject } from "../lib/fullAccessConfirmation";
6 import { useWindowChromeStore } from "../store/windowChrome";
7 import { ComposerChoice } from "./ComposerChoice";
8 import { RiskConfirmation } from "./RiskConfirmation";
9
10 export function PermissionPresetChoice({
11 value,
12 disabled,
13 dismissSignal,
14 scopeKey,
15 projectConfirmationKey,
16 onPick,
17 }: {
18 value: ToolApprovalMode;
19 disabled: boolean;
20 dismissSignal?: number;
21 scopeKey: string;
22 projectConfirmationKey: string;
23 onPick: (value: ToolApprovalMode) => void;
24 }) {
25 const t = useT();
26 // Windows has no OS-level shell sandbox: the workspace preset still confines
27 // Reasonix file tools, but shell commands run unconfined, so say so here.
28 const windows = useWindowChromeStore((state) => state.platform) === "windows";
29 const preset = normalizeToolApprovalMode(value);
30 const [confirmingFullAccess, setConfirmingFullAccess] = useState(false);
31 const [acknowledged, setAcknowledged] = useState(false);
32
33 const closeConfirmation = useCallback(() => {
34 setAcknowledged(false);
35 setConfirmingFullAccess(false);
36 }, []);
37
38 useEffect(() => {
39 closeConfirmation();
40 }, [closeConfirmation, disabled, dismissSignal, projectConfirmationKey, scopeKey]);
41
42 const choose = (next: ToolApprovalMode) => {
43 const normalized = normalizeToolApprovalMode(next);
44 if (normalized === preset) return;
45 if (normalized === "danger-full-access") {
46 if (hasConfirmedFullAccessForProject(projectConfirmationKey)) {
47 onPick(normalized);
48 return;
49 }
50 setAcknowledged(false);
51 setConfirmingFullAccess(true);
52 return;
53 }
54 onPick(normalized);
55 };
56
57 const confirm = () => {
58 if (disabled || !acknowledged) return;
59 rememberFullAccessConfirmationForProject(projectConfirmationKey);
60 closeConfirmation();
61 onPick("danger-full-access");
62 };
63
64 return <>
65 <ComposerChoice
66 label={t(preset === "read-only" ? "composer.permissionReadOnly" : preset === "workspace-write" ? "composer.permissionWorkspaceWrite" : "composer.permissionFullAccess")}
67 showChevron
68 icon={preset === "danger-full-access" ? <ShieldAlert size={16} /> : preset === "workspace-write" ? <ShieldCheck size={16} /> : <Eye size={16} />}
69 tone={`composer-choice--permission-${preset}`}
70 value={preset}
71 disabled={disabled || confirmingFullAccess}
72 dismissSignal={dismissSignal}
73 onPick={(next) => choose(next as ToolApprovalMode)}
74 options={[
75 { value: "read-only", label: t("composer.permissionReadOnly"), icon: <Eye size={18} />, description: t("composer.permissionReadOnlyDesc") },
76 { value: "workspace-write", label: t("composer.permissionWorkspaceWrite"), badge: t("composer.permissionRecommended"), icon: <ShieldCheck size={18} />, description: t(windows ? "composer.permissionWorkspaceWriteDescWindows" : "composer.permissionWorkspaceWriteDesc") },
77 { value: "danger-full-access", label: t("composer.permissionFullAccess"), icon: <ShieldAlert size={18} />, description: t("composer.permissionFullAccessDesc") },
78 ]}
79 />
80 <RiskConfirmation
81 open={confirmingFullAccess}
82 title={t("permission.fullAccessConfirm.title")}
83 description={t("permission.fullAccessConfirm.currentDescription")}
84 acknowledgeLabel={t("permission.fullAccessConfirm.currentAcknowledge")}
85 cancelLabel={t("common.cancel")}
86 closeLabel={t("common.close")}
87 confirmLabel={t("permission.fullAccessConfirm.enable")}
88 acknowledged={acknowledged}
89 disabled={disabled}
90 onAcknowledgedChange={setAcknowledged}
91 onCancel={closeConfirmation}
92 onConfirm={confirm}
93 />
94 </>;
95 }
96
96 lines Plain Text