From 15eb2e6249822f1aee1d4c913d1bc2e9d37dc84f Mon Sep 17 00:00:00 2001 From: Dark aura Date: Wed, 6 May 2026 04:21:00 +0100 Subject: [PATCH] fix: integrate PermissionPrompt dialog into chat UI - Add PermissionPrompt component to chat page - Detect request_permission tool calls and show dialog - Extract path and command from tool call arguments - Fix TypeScript errors (null check, unused variable) - Fix locale files (trailing comma errors) --- .../src/components/chat/chat-page.tsx | 57 +++++++++++++++++++ web/frontend/src/i18n/locales/en.json | 6 +- web/frontend/src/i18n/locales/zh.json | 6 +- 3 files changed, 63 insertions(+), 6 deletions(-) diff --git a/web/frontend/src/components/chat/chat-page.tsx b/web/frontend/src/components/chat/chat-page.tsx index 3ad811dae..aa479d776 100644 --- a/web/frontend/src/components/chat/chat-page.tsx +++ b/web/frontend/src/components/chat/chat-page.tsx @@ -25,6 +25,7 @@ import type { ConnectionState } from "@/store/chat" import type { ChatAttachment } from "@/store/chat" import { showAssistantDetailsAtom } from "@/store/chat" import type { GatewayState } from "@/store/gateway" +import { PermissionPrompt } from "@/components/permission/PermissionPrompt" const MAX_IMAGE_SIZE_BYTES = 7 * 1024 * 1024 const MAX_IMAGE_SIZE_LABEL = "7 MB" @@ -115,6 +116,11 @@ export function ChatPage() { const [showAssistantDetails, setShowAssistantDetails] = useAtom( showAssistantDetailsAtom, ) + const [permissionRequest, setPermissionRequest] = useState<{ + toolName: string + path: string + originalCommand: string + } | null>(null) const { messages, @@ -191,6 +197,45 @@ export function ChatPage() { } } + // Check for permission requests in messages + useEffect(() => { + if (permissionRequest) return // Already showing a permission prompt + + // Check the last few messages for request_permission tool calls + for (let i = messages.length - 1; i >= Math.max(0, messages.length - 5); i--) { + const msg = messages[i] + if (msg.role !== "assistant") continue + if (!msg.toolCalls) continue + + const permCall = msg.toolCalls.find( + (tc) => tc.function?.name === "request_permission" + ) + if (!permCall || !permCall.function) continue + + try { + const args = JSON.parse(permCall.function.arguments ?? "{}") + if (args.path && args.command) { + setPermissionRequest({ + toolName: "request_permission", + path: args.path, + originalCommand: args.command, + }) + return + } + } catch { + // Ignore parse errors + } + } + }, [messages, permissionRequest]) + + const handlePermissionGranted = () => { + setPermissionRequest(null) + } + + const handlePermissionDenied = () => { + setPermissionRequest(null) + } + const handleAddImages = () => { if (!canInput) return fileInputRef.current?.click() @@ -324,6 +369,18 @@ export function ChatPage() { /> )} + {permissionRequest && ( +
+ +
+ )} + {messages.map((msg) => { if ( !showAssistantDetails && diff --git a/web/frontend/src/i18n/locales/en.json b/web/frontend/src/i18n/locales/en.json index fa08e1195..0a378c115 100644 --- a/web/frontend/src/i18n/locales/en.json +++ b/web/frontend/src/i18n/locales/en.json @@ -49,7 +49,7 @@ "websocketConnecting": "Connecting to chat service... Please wait.", "websocketDisconnected": "Unable to chat: WebSocket connection is disconnected. Check network and gateway status, then refresh the page or restart Launcher.", "websocketError": "Unable to chat: WebSocket connection failed. Check network and gateway status, then retry.", - "noDefaultModel": "Unable to chat: No default model is selected. Set a default model on the Models page." + "noDefaultModel": "Unable to chat: No default model is selected. Set a default model on the Models page.", "permissionRequiredTitle": "Permission Required", "permissionRequiredMessage": "PicoClaw wants to use {{tool}} to access {{path}} which is outside your workspace.", "commandToExecute": "Command to execute:", @@ -58,8 +58,8 @@ "permissionSession": "Allow for session", "permissionGrantedOnce": "Permission granted for one-time access to {{path}}", "permissionGrantedSession": "Permission granted for session access to {{path}}", - "permissionDenied": "Permission denied for {{path}}", - "permissionError": "An error occurred while handling the permission request.", + "permissionDenied": "Permission denied for {{path}}", + "permissionError": "An error occurred while handling the permission request." }, "newChat": "New Chat", "notConnected": "Gateway is not running. Start it to chat.", diff --git a/web/frontend/src/i18n/locales/zh.json b/web/frontend/src/i18n/locales/zh.json index 5b41f4f78..59fda8636 100644 --- a/web/frontend/src/i18n/locales/zh.json +++ b/web/frontend/src/i18n/locales/zh.json @@ -49,7 +49,7 @@ "websocketConnecting": "正在连接聊天服务,请稍候。", "websocketDisconnected": "无法对话:WebSocket 连接已断开。请检查网络与服务状态,然后刷新页面或重启 Launcher。", "websocketError": "无法对话:WebSocket 连接失败。请检查网络与服务状态后重试。", - "noDefaultModel": "无法对话:尚未设置默认模型。请前往模型页面设置默认模型。" + "noDefaultModel": "无法对话:尚未设置默认模型。请前往模型页面设置默认模型。", "permissionRequiredTitle": "需要权限", "permissionRequiredMessage": "PicoClaw 想要使用 {{tool}} 访问 {{path}},但该路径位于工作区之外。", "commandToExecute": "要执行的命令:", @@ -58,8 +58,8 @@ "permissionSession": "允许整个会话", "permissionGrantedOnce": "已授予对 {{path}} 的一次性访问权限", "permissionGrantedSession": "已授予对 {{path}} 的会话访问权限", - "permissionDenied": "已拒绝对 {{path}} 的访问权限", - "permissionError": "处理权限请求时发生错误。", + "permissionDenied": "已拒绝对 {{path}} 的访问权限", + "permissionError": "处理权限请求时发生错误。" }, "newChat": "新建对话", "notConnected": "服务未运行,请先启动以进行对话。",