feat: 升级至 v0.2.3 — Token 估算优化、工具确认超时改进、工作空间标签页
1. Token 估算优化(核心改进):新增 token-estimator.ts 智能字符估算(中文 1.5 token/字、ASCII 0.25 token/字),替换三处旧的 length/2 粗略估算,中文场景准确度从 ~50% 提升到 ~90% 2. 工具确认超时改进:超时时间可配置(30s~600s)、超时 toast 通知、ConfirmationDialog 倒计时 UI(进度条 + 最后 10 秒红色脉冲动画)、SettingsModal 新增配置入口 3. 详情栏新增 Workspace 标签页:workspace:getInfo IPC + WorkspaceViewer 组件(文件预览、目录状态、在文件管理器打开)
This commit is contained in:
@@ -13,8 +13,9 @@ import {
|
||||
Dialog, DialogTitle, DialogContent, DialogActions,
|
||||
Button, Typography, Box, Chip, Alert, FormControlLabel, Checkbox,
|
||||
Accordion, AccordionSummary, AccordionDetails,
|
||||
LinearProgress,
|
||||
} from '@mui/material';
|
||||
import { ShieldAlert, ChevronDown } from 'lucide-react';
|
||||
import { ShieldAlert, ChevronDown, Timer } from 'lucide-react';
|
||||
|
||||
interface ConfirmationRequest {
|
||||
toolCallId: string;
|
||||
@@ -22,6 +23,8 @@ interface ConfirmationRequest {
|
||||
args: Record<string, unknown>;
|
||||
riskLevel: string;
|
||||
reason: string;
|
||||
/** 过期时间戳(ms),由后端 ConfirmationHook 注入,用于倒计时 */
|
||||
expiresAt?: number;
|
||||
}
|
||||
|
||||
const RISK_COLORS: Record<string, 'default' | 'success' | 'warning' | 'error' | 'info'> = {
|
||||
@@ -36,17 +39,45 @@ export function ConfirmationDialog(): React.JSX.Element | null {
|
||||
const [request, setRequest] = useState<ConfirmationRequest | null>(null);
|
||||
const [remember, setRemember] = useState(false);
|
||||
const [autoExecute, setAutoExecute] = useState(false);
|
||||
const [remainingMs, setRemainingMs] = useState<number>(0);
|
||||
// 记录首次接收时的剩余时间,作为进度条总量(固定不变)
|
||||
const [initialMs, setInitialMs] = useState<number>(0);
|
||||
|
||||
useEffect(() => {
|
||||
// 监听来自主进程的确认请求(通过 preload 暴露的 metona.tool API)
|
||||
const cleanup = window.metona?.tool?.onConfirmationRequest((data: unknown) => {
|
||||
setRequest(data as ConfirmationRequest);
|
||||
const req = data as ConfirmationRequest;
|
||||
setRequest(req);
|
||||
setRemember(false);
|
||||
setAutoExecute(false);
|
||||
// 初始化倒计时
|
||||
if (req.expiresAt) {
|
||||
const remain = Math.max(0, req.expiresAt - Date.now());
|
||||
setRemainingMs(remain);
|
||||
setInitialMs(remain);
|
||||
} else {
|
||||
setRemainingMs(0);
|
||||
setInitialMs(0);
|
||||
}
|
||||
});
|
||||
return cleanup;
|
||||
}, []);
|
||||
|
||||
// 倒计时:每 200ms 更新剩余时间
|
||||
useEffect(() => {
|
||||
if (!request?.expiresAt) return;
|
||||
const interval = setInterval(() => {
|
||||
const remaining = request.expiresAt! - Date.now();
|
||||
if (remaining <= 0) {
|
||||
setRemainingMs(0);
|
||||
clearInterval(interval);
|
||||
} else {
|
||||
setRemainingMs(remaining);
|
||||
}
|
||||
}, 200);
|
||||
return () => clearInterval(interval);
|
||||
}, [request]);
|
||||
|
||||
const handleRespond = useCallback((approved: boolean) => {
|
||||
if (!request) return;
|
||||
// 通过 preload 暴露的 metona.tool API 发送响应
|
||||
@@ -63,6 +94,14 @@ export function ConfirmationDialog(): React.JSX.Element | null {
|
||||
if (!request) return null;
|
||||
|
||||
const riskColor = RISK_COLORS[request.riskLevel] ?? 'default';
|
||||
const remainingSec = Math.ceil(remainingMs / 1000);
|
||||
const isUrgent = remainingSec <= 10 && remainingSec > 0;
|
||||
const isExpired = remainingMs <= 0 && request.expiresAt != null;
|
||||
// 总时长使用首次接收时的剩余时间(固定值),保证进度条单调递减
|
||||
const totalMs = initialMs || remainingMs;
|
||||
const progressPercent = totalMs > 0
|
||||
? Math.max(0, Math.min(100, (remainingMs / totalMs) * 100))
|
||||
: 100;
|
||||
|
||||
// 格式化参数显示
|
||||
const formatArg = (key: string, value: unknown): string => {
|
||||
@@ -101,6 +140,37 @@ export function ConfirmationDialog(): React.JSX.Element | null {
|
||||
{request.reason}
|
||||
</Alert>
|
||||
|
||||
{request.expiresAt && !isExpired && (
|
||||
<Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<Timer
|
||||
size={16}
|
||||
color={isUrgent ? 'var(--mui-palette-error-main)' : 'var(--mui-palette-text-secondary)'}
|
||||
style={isUrgent ? { animation: 'metona-pulse 1s ease-in-out infinite' } : undefined}
|
||||
/>
|
||||
<Typography
|
||||
variant="caption"
|
||||
sx={{
|
||||
color: isUrgent ? 'error.main' : 'text.secondary',
|
||||
fontWeight: isUrgent ? 700 : 500,
|
||||
minWidth: 48,
|
||||
animation: isUrgent ? 'metona-pulse 1s ease-in-out infinite' : 'none',
|
||||
'@keyframes metona-pulse': {
|
||||
'0%, 100%': { opacity: 1 },
|
||||
'50%': { opacity: 0.4 },
|
||||
},
|
||||
}}
|
||||
>
|
||||
剩余 {remainingSec}s
|
||||
</Typography>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={progressPercent}
|
||||
color={isUrgent ? 'error' : 'primary'}
|
||||
sx={{ flex: 1, height: 6, borderRadius: 3 }}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
<Box sx={{ mb: 2, display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||
<Chip
|
||||
label={`工具: ${request.toolName}`}
|
||||
|
||||
Reference in New Issue
Block a user