From 574c50a70e262990ee7448ae2e645dae853af071 Mon Sep 17 00:00:00 2001 From: isYangs <110588441+isYangs@users.noreply.github.com> Date: Thu, 2 Apr 2026 12:54:42 +0800 Subject: [PATCH 1/3] refactor(chat): extract permission action buttons --- src/components/chat/permission-actions.tsx | 80 ++++++++++++++++++++++ src/i18n/messages/ar.json | 8 ++- src/i18n/messages/de.json | 8 ++- src/i18n/messages/en.json | 8 ++- src/i18n/messages/es.json | 8 ++- src/i18n/messages/fr.json | 8 ++- src/i18n/messages/ja.json | 8 ++- src/i18n/messages/ko.json | 8 ++- src/i18n/messages/pt.json | 8 ++- src/i18n/messages/zh-CN.json | 8 ++- src/i18n/messages/zh-TW.json | 8 ++- 11 files changed, 150 insertions(+), 10 deletions(-) create mode 100644 src/components/chat/permission-actions.tsx diff --git a/src/components/chat/permission-actions.tsx b/src/components/chat/permission-actions.tsx new file mode 100644 index 00000000..cd24dede --- /dev/null +++ b/src/components/chat/permission-actions.tsx @@ -0,0 +1,80 @@ +"use client" + +import { useTranslations } from "next-intl" +import { Button } from "@/components/ui/button" +import type { PermissionOptionInfo } from "@/lib/types" + +type PermissionActionVariant = "default" | "outline" + +interface PermissionActionsProps { + options: PermissionOptionInfo[] + onRespond: (optionId: string) => void +} + +const KIND_LABEL_KEYS: Record = { + allow_once: "allowOnce", + allow_always: "allowAlways", + reject_once: "rejectOnce", + reject_always: "rejectAlways", +} + +const KIND_VARIANTS: Record = { + allow_once: "default", + allow_always: "default", + reject_once: "outline", + reject_always: "outline", +} + +function extractDetail(name: string): string | undefined { + const match = name.match(/`([^`]+)`/) + return match?.[1] +} + +export function PermissionActions({ + options, + onRespond, +}: PermissionActionsProps) { + const t = useTranslations("Folder.chat.permissionDialog.actions") + + return ( +
+ {options.map((opt) => { + const variant: PermissionActionVariant = + KIND_VARIANTS[opt.kind] ?? + (opt.kind.startsWith("reject") ? "outline" : "default") + const labelKey = KIND_LABEL_KEYS[opt.kind] + const label = labelKey ? t(labelKey as never) : opt.name + const detail = labelKey ? extractDetail(opt.name) : undefined + + if (detail) { + return ( + + ) + } + + return ( + + ) + })} +
+ ) +} diff --git a/src/i18n/messages/ar.json b/src/i18n/messages/ar.json index 41ddcd82..6efab57a 100644 --- a/src/i18n/messages/ar.json +++ b/src/i18n/messages/ar.json @@ -1439,7 +1439,13 @@ "moreFiles": "+{count} ملف إضافي", "plan": "الخطة", "allowedActions": "الإجراءات المسموح بها", - "targetMode": "وضع الهدف: {mode}" + "targetMode": "وضع الهدف: {mode}", + "actions": { + "allowOnce": "السماح", + "allowAlways": "السماح دائمًا", + "rejectOnce": "رفض", + "rejectAlways": "رفض دائمًا" + } }, "questionDialog": { "title": "الوكيل يطرح سؤالاً", diff --git a/src/i18n/messages/de.json b/src/i18n/messages/de.json index 573e24b6..9ce4a918 100644 --- a/src/i18n/messages/de.json +++ b/src/i18n/messages/de.json @@ -1439,7 +1439,13 @@ "moreFiles": "+{count} weitere Dateien", "plan": "Arbeitsplan", "allowedActions": "Erlaubte Aktionen", - "targetMode": "Zielmodus: {mode}" + "targetMode": "Zielmodus: {mode}", + "actions": { + "allowOnce": "Erlauben", + "allowAlways": "Immer erlauben", + "rejectOnce": "Ablehnen", + "rejectAlways": "Immer ablehnen" + } }, "questionDialog": { "title": "Agent stellt eine Frage", diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index 846d68a1..52d31a4b 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -1439,7 +1439,13 @@ "moreFiles": "+{count} more files", "plan": "Plan", "allowedActions": "Allowed actions", - "targetMode": "Target mode: {mode}" + "targetMode": "Target mode: {mode}", + "actions": { + "allowOnce": "Allow", + "allowAlways": "Always allow", + "rejectOnce": "Reject", + "rejectAlways": "Always reject" + } }, "questionDialog": { "title": "Agent is asking a question", diff --git a/src/i18n/messages/es.json b/src/i18n/messages/es.json index 83e58175..aeb3642c 100644 --- a/src/i18n/messages/es.json +++ b/src/i18n/messages/es.json @@ -1439,7 +1439,13 @@ "moreFiles": "+{count} archivos más", "plan": "Plan de trabajo", "allowedActions": "Acciones permitidas", - "targetMode": "Modo objetivo: {mode}" + "targetMode": "Modo objetivo: {mode}", + "actions": { + "allowOnce": "Permitir", + "allowAlways": "Permitir siempre", + "rejectOnce": "Rechazar", + "rejectAlways": "Rechazar siempre" + } }, "questionDialog": { "title": "El agente está haciendo una pregunta", diff --git a/src/i18n/messages/fr.json b/src/i18n/messages/fr.json index aba2feaf..f2dafbc0 100644 --- a/src/i18n/messages/fr.json +++ b/src/i18n/messages/fr.json @@ -1439,7 +1439,13 @@ "moreFiles": "+{count} fichiers supplémentaires", "plan": "Plan de travail", "allowedActions": "Actions autorisées", - "targetMode": "Mode cible : {mode}" + "targetMode": "Mode cible : {mode}", + "actions": { + "allowOnce": "Autoriser", + "allowAlways": "Toujours autoriser", + "rejectOnce": "Refuser", + "rejectAlways": "Toujours refuser" + } }, "questionDialog": { "title": "L'agent pose une question", diff --git a/src/i18n/messages/ja.json b/src/i18n/messages/ja.json index 10ac49c5..05d5321b 100644 --- a/src/i18n/messages/ja.json +++ b/src/i18n/messages/ja.json @@ -1439,7 +1439,13 @@ "moreFiles": "+{count} 件の追加ファイル", "plan": "計画", "allowedActions": "許可されたアクション", - "targetMode": "対象モード: {mode}" + "targetMode": "対象モード: {mode}", + "actions": { + "allowOnce": "許可", + "allowAlways": "常に許可", + "rejectOnce": "拒否", + "rejectAlways": "常に拒否" + } }, "questionDialog": { "title": "エージェントが質問しています", diff --git a/src/i18n/messages/ko.json b/src/i18n/messages/ko.json index 3df750b8..5c3c670e 100644 --- a/src/i18n/messages/ko.json +++ b/src/i18n/messages/ko.json @@ -1439,7 +1439,13 @@ "moreFiles": "+{count}개 파일 더", "plan": "계획", "allowedActions": "허용된 작업", - "targetMode": "대상 모드: {mode}" + "targetMode": "대상 모드: {mode}", + "actions": { + "allowOnce": "허용", + "allowAlways": "항상 허용", + "rejectOnce": "거부", + "rejectAlways": "항상 거부" + } }, "questionDialog": { "title": "에이전트가 질문하고 있습니다", diff --git a/src/i18n/messages/pt.json b/src/i18n/messages/pt.json index 923655df..8fafad69 100644 --- a/src/i18n/messages/pt.json +++ b/src/i18n/messages/pt.json @@ -1439,7 +1439,13 @@ "moreFiles": "+{count} arquivos a mais", "plan": "Plano", "allowedActions": "Ações permitidas", - "targetMode": "Modo de destino: {mode}" + "targetMode": "Modo de destino: {mode}", + "actions": { + "allowOnce": "Permitir", + "allowAlways": "Permitir sempre", + "rejectOnce": "Rejeitar", + "rejectAlways": "Rejeitar sempre" + } }, "questionDialog": { "title": "O agente está fazendo uma pergunta", diff --git a/src/i18n/messages/zh-CN.json b/src/i18n/messages/zh-CN.json index 40b0d4c1..ab6816cc 100644 --- a/src/i18n/messages/zh-CN.json +++ b/src/i18n/messages/zh-CN.json @@ -1439,7 +1439,13 @@ "moreFiles": "+{count} 个更多文件", "plan": "计划", "allowedActions": "允许的操作", - "targetMode": "目标模式:{mode}" + "targetMode": "目标模式:{mode}", + "actions": { + "allowOnce": "允许", + "allowAlways": "始终允许", + "rejectOnce": "拒绝", + "rejectAlways": "始终拒绝" + } }, "questionDialog": { "title": "代理正在提问", diff --git a/src/i18n/messages/zh-TW.json b/src/i18n/messages/zh-TW.json index 52cbf7b3..212fc00b 100644 --- a/src/i18n/messages/zh-TW.json +++ b/src/i18n/messages/zh-TW.json @@ -1439,7 +1439,13 @@ "moreFiles": "+{count} 個更多檔案", "plan": "計畫", "allowedActions": "允許的操作", - "targetMode": "目標模式:{mode}" + "targetMode": "目標模式:{mode}", + "actions": { + "allowOnce": "允許", + "allowAlways": "始終允許", + "rejectOnce": "拒絕", + "rejectAlways": "始終拒絕" + } }, "questionDialog": { "title": "代理正在提問", From f652a5825f04b0900856d646e93c6e6087bdbcde Mon Sep 17 00:00:00 2001 From: isYangs <110588441+isYangs@users.noreply.github.com> Date: Thu, 2 Apr 2026 12:55:00 +0800 Subject: [PATCH 2/3] refactor(chat): use extracted permission actions --- src/components/chat/permission-dialog.tsx | 24 +++++++---------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/src/components/chat/permission-dialog.tsx b/src/components/chat/permission-dialog.tsx index f2047804..b2bce8ba 100644 --- a/src/components/chat/permission-dialog.tsx +++ b/src/components/chat/permission-dialog.tsx @@ -11,11 +11,11 @@ import { Globe, Search, } from "lucide-react" -import { Button } from "@/components/ui/button" import { Badge } from "@/components/ui/badge" import { CodeBlock } from "@/components/ai-elements/code-block" import { UnifiedDiffPreview } from "@/components/diff/unified-diff-preview" import { MessageResponse } from "@/components/ai-elements/message" +import { PermissionActions } from "@/components/chat/permission-actions" import type { PendingPermission } from "@/contexts/acp-connections-context" import { parsePermissionToolCall } from "@/lib/permission-request" @@ -34,6 +34,7 @@ export function PermissionDialog({ onRespond, }: PermissionDialogProps) { const t = useTranslations("Folder.chat.permissionDialog") + const parsed = useMemo( () => parsePermissionToolCall(permission?.tool_call), [permission?.tool_call] @@ -56,7 +57,7 @@ export function PermissionDialog({ hasWeb return ( -
+
@@ -196,21 +197,10 @@ export function PermissionDialog({ )}
-
- {permission.options.map((opt) => { - const isReject = opt.kind.startsWith("reject") - return ( - - ) - })} -
+ onRespond(permission.request_id, optionId)} + />
) } From d06d8decdc4c2357064fd481bcfc0153ee00a511 Mon Sep 17 00:00:00 2001 From: isYangs <110588441+isYangs@users.noreply.github.com> Date: Fri, 3 Apr 2026 20:50:18 +0800 Subject: [PATCH 3/3] fix(chat): restore permission action button wrapping and type label keys --- src/components/chat/permission-actions.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/chat/permission-actions.tsx b/src/components/chat/permission-actions.tsx index cd24dede..bfbd84f7 100644 --- a/src/components/chat/permission-actions.tsx +++ b/src/components/chat/permission-actions.tsx @@ -5,18 +5,19 @@ import { Button } from "@/components/ui/button" import type { PermissionOptionInfo } from "@/lib/types" type PermissionActionVariant = "default" | "outline" +type ActionLabelKey = (typeof KIND_LABEL_KEYS)[keyof typeof KIND_LABEL_KEYS] interface PermissionActionsProps { options: PermissionOptionInfo[] onRespond: (optionId: string) => void } -const KIND_LABEL_KEYS: Record = { +const KIND_LABEL_KEYS = { allow_once: "allowOnce", allow_always: "allowAlways", reject_once: "rejectOnce", reject_always: "rejectAlways", -} +} as const const KIND_VARIANTS: Record = { allow_once: "default", @@ -42,8 +43,11 @@ export function PermissionActions({ const variant: PermissionActionVariant = KIND_VARIANTS[opt.kind] ?? (opt.kind.startsWith("reject") ? "outline" : "default") - const labelKey = KIND_LABEL_KEYS[opt.kind] - const label = labelKey ? t(labelKey as never) : opt.name + const labelKey = + KIND_LABEL_KEYS[opt.kind as keyof typeof KIND_LABEL_KEYS] + const label = labelKey ? t(labelKey as ActionLabelKey) : opt.name + // Only split label/detail for known kinds; unrecognized kinds + // render opt.name as-is since we have no translation key for them. const detail = labelKey ? extractDetail(opt.name) : undefined if (detail) { @@ -51,7 +55,7 @@ export function PermissionActions({