支持显示权限请求里面的plan markdown

This commit is contained in:
xintaofei
2026-03-11 23:59:22 +08:00
parent ccd821bacb
commit b452cdda45
12 changed files with 90 additions and 0 deletions

View File

@@ -8,10 +8,12 @@ import {
FilePenLine,
ListTodo,
Compass,
FileText,
} from "lucide-react"
import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { CodeBlock } from "@/components/ai-elements/code-block"
import { MessageResponse } from "@/components/ai-elements/message"
import type { PendingPermission } from "@/contexts/acp-connections-context"
import { parsePermissionToolCall } from "@/lib/permission-request"
@@ -39,10 +41,14 @@ export function PermissionDialog({
const hasFileChanges = parsed.fileChanges.length > 0
const hasPlan =
parsed.planEntries.length > 0 || Boolean(parsed.planExplanation)
const hasPlanMarkdown = Boolean(parsed.planMarkdown)
const hasAllowedPrompts = parsed.allowedPrompts.length > 0
const hasStructured =
Boolean(parsed.command) ||
hasFileChanges ||
hasPlan ||
hasPlanMarkdown ||
hasAllowedPrompts ||
Boolean(parsed.modeTarget)
return (
@@ -138,6 +144,42 @@ export function PermissionDialog({
</div>
)}
{hasPlanMarkdown && (
<div className="space-y-1.5 rounded-md border border-border/60 bg-muted/20 p-2">
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<FileText className="h-3.5 w-3.5" />
<span>{t("plan")}</span>
</div>
<div className="text-sm prose prose-sm dark:prose-invert max-w-none [&_ul]:list-inside [&_ol]:list-inside">
<MessageResponse>{parsed.planMarkdown!}</MessageResponse>
</div>
</div>
)}
{hasAllowedPrompts && (
<div className="space-y-1.5 rounded-md border border-border/60 bg-muted/20 p-2">
<div className="flex items-center gap-1 text-xs text-muted-foreground">
<Terminal className="h-3.5 w-3.5" />
<span>{t("allowedActions")}</span>
</div>
<div className="space-y-1 rounded-md bg-muted/40 p-2">
{parsed.allowedPrompts.map((item, index) => (
<div
key={`${item.prompt}-${index}`}
className="flex items-center gap-2 text-xs"
>
{item.tool && (
<Badge variant="outline" className="shrink-0 text-[10px]">
{item.tool}
</Badge>
)}
<span className="text-foreground/90">{item.prompt}</span>
</div>
))}
</div>
</div>
)}
{parsed.modeTarget && (
<div className="rounded-md border border-border/60 bg-muted/20 p-2 text-xs">
<div className="flex items-center gap-1 text-muted-foreground">

View File

@@ -1173,6 +1173,7 @@
"filesSummary": "الملفات: {count}",
"moreFiles": "+{count} ملف إضافي",
"plan": "الخطة",
"allowedActions": "الإجراءات المسموح بها",
"targetMode": "وضع الهدف: {mode}"
},
"questionDialog": {

View File

@@ -1173,6 +1173,7 @@
"filesSummary": "Dateien: {count}",
"moreFiles": "+{count} weitere Dateien",
"plan": "Arbeitsplan",
"allowedActions": "Erlaubte Aktionen",
"targetMode": "Zielmodus: {mode}"
},
"questionDialog": {

View File

@@ -1173,6 +1173,7 @@
"filesSummary": "Files: {count}",
"moreFiles": "+{count} more files",
"plan": "Plan",
"allowedActions": "Allowed actions",
"targetMode": "Target mode: {mode}"
},
"questionDialog": {

View File

@@ -1173,6 +1173,7 @@
"filesSummary": "Archivos: {count}",
"moreFiles": "+{count} archivos más",
"plan": "Plan de trabajo",
"allowedActions": "Acciones permitidas",
"targetMode": "Modo objetivo: {mode}"
},
"questionDialog": {

View File

@@ -1173,6 +1173,7 @@
"filesSummary": "Fichiers : {count}",
"moreFiles": "+{count} fichiers supplémentaires",
"plan": "Plan de travail",
"allowedActions": "Actions autorisées",
"targetMode": "Mode cible : {mode}"
},
"questionDialog": {

View File

@@ -1173,6 +1173,7 @@
"filesSummary": "ファイル: {count}",
"moreFiles": "+{count} 件の追加ファイル",
"plan": "計画",
"allowedActions": "許可されたアクション",
"targetMode": "対象モード: {mode}"
},
"questionDialog": {

View File

@@ -1173,6 +1173,7 @@
"filesSummary": "파일: {count}",
"moreFiles": "+{count}개 파일 더",
"plan": "계획",
"allowedActions": "허용된 작업",
"targetMode": "대상 모드: {mode}"
},
"questionDialog": {

View File

@@ -1173,6 +1173,7 @@
"filesSummary": "Arquivos: {count}",
"moreFiles": "+{count} arquivos a mais",
"plan": "Plano",
"allowedActions": "Ações permitidas",
"targetMode": "Modo de destino: {mode}"
},
"questionDialog": {

View File

@@ -1173,6 +1173,7 @@
"filesSummary": "文件:{count}",
"moreFiles": "+{count} 个更多文件",
"plan": "计划",
"allowedActions": "允许的操作",
"targetMode": "目标模式:{mode}"
},
"questionDialog": {

View File

@@ -1173,6 +1173,7 @@
"filesSummary": "檔案:{count}",
"moreFiles": "+{count} 個更多檔案",
"plan": "計畫",
"allowedActions": "允許的操作",
"targetMode": "目標模式:{mode}"
},
"questionDialog": {

View File

@@ -19,6 +19,11 @@ export interface PermissionPlanEntry {
status: string | null
}
export interface PermissionAllowedPrompt {
prompt: string
tool: string
}
export interface ParsedPermissionToolCall {
title: string
normalizedKind: string
@@ -30,6 +35,8 @@ export interface ParsedPermissionToolCall {
diffPreview: string | null
planEntries: PermissionPlanEntry[]
planExplanation: string | null
planMarkdown: string | null
allowedPrompts: PermissionAllowedPrompt[]
modeTarget: string | null
jsonPreview: string
}
@@ -529,6 +536,28 @@ function parsePlanEntries(
return []
}
function parseAllowedPrompts(
rawInputObj: ObjectLike | null
): PermissionAllowedPrompt[] {
if (!rawInputObj) return []
const list = asArray(
pickValue(rawInputObj, ["allowedPrompts", "allowed_prompts"])
)
if (!list || list.length === 0) return []
const prompts: PermissionAllowedPrompt[] = []
for (const item of list) {
const record = asObject(item)
if (!record) continue
const prompt = pickString(record, ["prompt", "description", "text"])
const tool = pickString(record, ["tool", "toolName", "tool_name"])
if (prompt) {
prompts.push({ prompt, tool: tool ?? "" })
}
}
return prompts
}
function formatFallbackTitle(kind: string): string {
const normalized = kind.replace(/_/g, " ").trim()
if (!normalized) return "Permission Request"
@@ -631,6 +660,13 @@ export function parsePermissionToolCall(
const planEntries = parsePlanEntries(rawInputObj)
const planExplanation = pickString(rawInputObj, ["explanation"])
const rawPlan = rawInputObj ? pickValue(rawInputObj, ["plan"]) : null
const planMarkdown =
typeof rawPlan === "string" && rawPlan.trim().length > 0 ? rawPlan : null
const allowedPrompts = parseAllowedPrompts(rawInputObj)
const modeTarget =
pickString(rawInputObj, [
"mode_id",
@@ -654,6 +690,8 @@ export function parsePermissionToolCall(
diffPreview,
planEntries,
planExplanation,
planMarkdown,
allowedPrompts,
modeTarget,
jsonPreview: stringifyJson(toolCallObj ?? toolCall),
}