Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions src/components/chat/permission-actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"use client"

import { useTranslations } from "next-intl"
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 = {
allow_once: "allowOnce",
allow_always: "allowAlways",
reject_once: "rejectOnce",
reject_always: "rejectAlways",
} as const

const KIND_VARIANTS: Record<string, PermissionActionVariant> = {
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 (
<div className="mt-3 flex flex-wrap gap-2">
{options.map((opt) => {
const variant: PermissionActionVariant =
KIND_VARIANTS[opt.kind] ??
(opt.kind.startsWith("reject") ? "outline" : "default")
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) {
return (
<Button
key={opt.option_id}
variant={variant}
className="h-auto min-h-9 max-w-full basis-full justify-start overflow-hidden text-left"
title={opt.name}
onClick={() => onRespond(opt.option_id)}
>
<span className="shrink-0">{label} ·</span>
<code className="truncate text-[0.85em] opacity-70">
{detail}
</code>
</Button>
)
}

return (
<Button
key={opt.option_id}
variant={variant}
className="h-auto min-h-9 whitespace-normal break-words"
title={opt.name}
onClick={() => onRespond(opt.option_id)}
>
{label}
</Button>
)
})}
</div>
)
}
24 changes: 7 additions & 17 deletions src/components/chat/permission-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -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]
Expand All @@ -56,7 +57,7 @@ export function PermissionDialog({
hasWeb

return (
<div className="mx-4 mb-3 rounded-xl border border-border/70 bg-card/95 p-3 shadow-sm">
<div className="mx-4 mb-3 overflow-hidden rounded-xl border border-border/70 bg-card/95 p-3 shadow-sm">
<div className="flex items-start justify-between gap-2">
<div className="min-w-0 space-y-1">
<div className="flex items-center gap-1.5 text-sm font-medium">
Expand Down Expand Up @@ -196,21 +197,10 @@ export function PermissionDialog({
)}
</div>

<div className="mt-3 flex flex-wrap gap-2">
{permission.options.map((opt) => {
const isReject = opt.kind.startsWith("reject")
return (
<Button
key={opt.option_id}
variant={isReject ? "outline" : "default"}
className="h-auto min-h-9 whitespace-normal break-words text-left"
onClick={() => onRespond(permission.request_id, opt.option_id)}
>
{opt.name}
</Button>
)
})}
</div>
<PermissionActions
options={permission.options}
onRespond={(optionId) => onRespond(permission.request_id, optionId)}
/>
</div>
)
}
8 changes: 7 additions & 1 deletion src/i18n/messages/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -1439,7 +1439,13 @@
"moreFiles": "+{count} ملف إضافي",
"plan": "الخطة",
"allowedActions": "الإجراءات المسموح بها",
"targetMode": "وضع الهدف: {mode}"
"targetMode": "وضع الهدف: {mode}",
"actions": {
"allowOnce": "السماح",
"allowAlways": "السماح دائمًا",
"rejectOnce": "رفض",
"rejectAlways": "رفض دائمًا"
}
},
"questionDialog": {
"title": "الوكيل يطرح سؤالاً",
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/messages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -1439,7 +1439,13 @@
"moreFiles": "+{count} 件の追加ファイル",
"plan": "計画",
"allowedActions": "許可されたアクション",
"targetMode": "対象モード: {mode}"
"targetMode": "対象モード: {mode}",
"actions": {
"allowOnce": "許可",
"allowAlways": "常に許可",
"rejectOnce": "拒否",
"rejectAlways": "常に拒否"
}
},
"questionDialog": {
"title": "エージェントが質問しています",
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/messages/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -1439,7 +1439,13 @@
"moreFiles": "+{count}개 파일 더",
"plan": "계획",
"allowedActions": "허용된 작업",
"targetMode": "대상 모드: {mode}"
"targetMode": "대상 모드: {mode}",
"actions": {
"allowOnce": "허용",
"allowAlways": "항상 허용",
"rejectOnce": "거부",
"rejectAlways": "항상 거부"
}
},
"questionDialog": {
"title": "에이전트가 질문하고 있습니다",
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/messages/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -1439,7 +1439,13 @@
"moreFiles": "+{count} 个更多文件",
"plan": "计划",
"allowedActions": "允许的操作",
"targetMode": "目标模式:{mode}"
"targetMode": "目标模式:{mode}",
"actions": {
"allowOnce": "允许",
"allowAlways": "始终允许",
"rejectOnce": "拒绝",
"rejectAlways": "始终拒绝"
}
},
"questionDialog": {
"title": "代理正在提问",
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/messages/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -1439,7 +1439,13 @@
"moreFiles": "+{count} 個更多檔案",
"plan": "計畫",
"allowedActions": "允許的操作",
"targetMode": "目標模式:{mode}"
"targetMode": "目標模式:{mode}",
"actions": {
"allowOnce": "允許",
"allowAlways": "始終允許",
"rejectOnce": "拒絕",
"rejectAlways": "始終拒絕"
}
},
"questionDialog": {
"title": "代理正在提問",
Expand Down