支持显示权限请求里面的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, FilePenLine,
ListTodo, ListTodo,
Compass, Compass,
FileText,
} from "lucide-react" } from "lucide-react"
import { Button } from "@/components/ui/button" import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { CodeBlock } from "@/components/ai-elements/code-block" import { CodeBlock } from "@/components/ai-elements/code-block"
import { MessageResponse } from "@/components/ai-elements/message"
import type { PendingPermission } from "@/contexts/acp-connections-context" import type { PendingPermission } from "@/contexts/acp-connections-context"
import { parsePermissionToolCall } from "@/lib/permission-request" import { parsePermissionToolCall } from "@/lib/permission-request"
@@ -39,10 +41,14 @@ export function PermissionDialog({
const hasFileChanges = parsed.fileChanges.length > 0 const hasFileChanges = parsed.fileChanges.length > 0
const hasPlan = const hasPlan =
parsed.planEntries.length > 0 || Boolean(parsed.planExplanation) parsed.planEntries.length > 0 || Boolean(parsed.planExplanation)
const hasPlanMarkdown = Boolean(parsed.planMarkdown)
const hasAllowedPrompts = parsed.allowedPrompts.length > 0
const hasStructured = const hasStructured =
Boolean(parsed.command) || Boolean(parsed.command) ||
hasFileChanges || hasFileChanges ||
hasPlan || hasPlan ||
hasPlanMarkdown ||
hasAllowedPrompts ||
Boolean(parsed.modeTarget) Boolean(parsed.modeTarget)
return ( return (
@@ -138,6 +144,42 @@ export function PermissionDialog({
</div> </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 && ( {parsed.modeTarget && (
<div className="rounded-md border border-border/60 bg-muted/20 p-2 text-xs"> <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"> <div className="flex items-center gap-1 text-muted-foreground">

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -19,6 +19,11 @@ export interface PermissionPlanEntry {
status: string | null status: string | null
} }
export interface PermissionAllowedPrompt {
prompt: string
tool: string
}
export interface ParsedPermissionToolCall { export interface ParsedPermissionToolCall {
title: string title: string
normalizedKind: string normalizedKind: string
@@ -30,6 +35,8 @@ export interface ParsedPermissionToolCall {
diffPreview: string | null diffPreview: string | null
planEntries: PermissionPlanEntry[] planEntries: PermissionPlanEntry[]
planExplanation: string | null planExplanation: string | null
planMarkdown: string | null
allowedPrompts: PermissionAllowedPrompt[]
modeTarget: string | null modeTarget: string | null
jsonPreview: string jsonPreview: string
} }
@@ -529,6 +536,28 @@ function parsePlanEntries(
return [] 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 { function formatFallbackTitle(kind: string): string {
const normalized = kind.replace(/_/g, " ").trim() const normalized = kind.replace(/_/g, " ").trim()
if (!normalized) return "Permission Request" if (!normalized) return "Permission Request"
@@ -631,6 +660,13 @@ export function parsePermissionToolCall(
const planEntries = parsePlanEntries(rawInputObj) const planEntries = parsePlanEntries(rawInputObj)
const planExplanation = pickString(rawInputObj, ["explanation"]) 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 = const modeTarget =
pickString(rawInputObj, [ pickString(rawInputObj, [
"mode_id", "mode_id",
@@ -654,6 +690,8 @@ export function parsePermissionToolCall(
diffPreview, diffPreview,
planEntries, planEntries,
planExplanation, planExplanation,
planMarkdown,
allowedPrompts,
modeTarget, modeTarget,
jsonPreview: stringifyJson(toolCallObj ?? toolCall), jsonPreview: stringifyJson(toolCallObj ?? toolCall),
} }