Files
tkmind_go/ui/desktop/src/components/ToolApprovalButtons.tsx
T

148 lines
4.1 KiB
TypeScript

import { useState, useEffect } from 'react';
import { Button } from './ui/button';
import type { Permission } from '../types/permissions';
import { resolveAcpPermissionRequest } from '../acp/permissionRequests';
import { defineMessages, useIntl } from '../i18n';
const i18n = defineMessages({
allowOnce: {
id: 'toolApprovalButtons.allowOnce',
defaultMessage: 'Allow Once',
},
alwaysAllow: {
id: 'toolApprovalButtons.alwaysAllow',
defaultMessage: 'Always Allow',
},
deny: {
id: 'toolApprovalButtons.deny',
defaultMessage: 'Deny',
},
allowedOnce: {
id: 'toolApprovalButtons.allowedOnce',
defaultMessage: 'Allowed once',
},
alwaysAllowed: {
id: 'toolApprovalButtons.alwaysAllowed',
defaultMessage: 'Always allowed',
},
denied: {
id: 'toolApprovalButtons.denied',
defaultMessage: 'Denied',
},
deniedOnce: {
id: 'toolApprovalButtons.deniedOnce',
defaultMessage: 'Denied once',
},
cancelled: {
id: 'toolApprovalButtons.cancelled',
defaultMessage: 'Cancelled',
},
staleApprovalRequest: {
id: 'toolApprovalButtons.staleApprovalRequest',
defaultMessage: 'This approval request is no longer active.',
},
});
const globalApprovalState = new Map<
string,
{
decision: Permission | null;
isClicked: boolean;
}
>();
export interface ToolApprovalData {
id: string;
toolName: string;
prompt?: string;
sessionId: string;
isClicked?: boolean;
}
export default function ToolApprovalButtons({ data }: { data: ToolApprovalData }) {
const intl = useIntl();
const { id, toolName, prompt, sessionId, isClicked: initialIsClicked } = data;
const storedState = globalApprovalState.get(id);
const [decision, setDecision] = useState<Permission | null>(storedState?.decision ?? null);
const [isClicked, setIsClicked] = useState(storedState?.isClicked ?? initialIsClicked ?? false);
const [approvalError, setApprovalError] = useState<string | null>(null);
const setResolvedDecision = (action: Permission) => {
setDecision(action);
setIsClicked(true);
setApprovalError(null);
};
useEffect(() => {
const currentState = globalApprovalState.get(id);
if (currentState) {
setDecision(currentState.decision);
setIsClicked(currentState.isClicked);
}
setApprovalError(null);
}, [id]);
useEffect(() => {
globalApprovalState.set(id, { decision, isClicked });
}, [id, decision, isClicked]);
const handleAction = async (action: Permission) => {
try {
if (resolveAcpPermissionRequest(sessionId, id, action)) {
setResolvedDecision(action);
} else {
setApprovalError(intl.formatMessage(i18n.staleApprovalRequest));
}
} catch (err) {
console.error('Error confirming tool action:', err);
}
};
if (isClicked && decision) {
const statusMessages: Record<Permission, string> = {
allow_once: intl.formatMessage(i18n.allowedOnce),
always_allow: intl.formatMessage(i18n.alwaysAllowed),
always_deny: intl.formatMessage(i18n.denied),
deny_once: intl.formatMessage(i18n.deniedOnce),
cancel: intl.formatMessage(i18n.cancelled),
};
return (
<p className="text-sm text-muted-foreground mt-2">
{toolName} - {statusMessages[decision]}
</p>
);
}
return (
<>
<div className="flex items-center gap-2 mt-2">
<Button
className="rounded-full"
variant="secondary"
onClick={() => handleAction('allow_once')}
>
{intl.formatMessage(i18n.allowOnce)}
</Button>
{!prompt && (
<Button
className="rounded-full"
variant="secondary"
onClick={() => handleAction('always_allow')}
>
{intl.formatMessage(i18n.alwaysAllow)}
</Button>
)}
<Button className="rounded-full" variant="outline" onClick={() => handleAction('deny_once')}>
{intl.formatMessage(i18n.deny)}
</Button>
</div>
{approvalError && (
<p className="text-sm text-red-500 mt-2" role="alert">
{approvalError}
</p>
)}
</>
);
}