Commit 1f2e3288 by luoqi

fix(ai-script): 深度过程面板精简 — 去标题行/进行中默认展开可折叠/单色背景

按反馈调整 ScriptDeepProcess:
- 去掉'深度生成过程'标题行,更简洁。
- 步骤可折叠:进行中的默认展开、其余收缩;用户手动点过的步骤改由用户控制(in manual map → 不再受程序自动展开影响)。
- 背景改单色(slate-50 + slate 边框/图标),去掉 indigo 多色。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
parent 60ce7324
......@@ -419,7 +419,7 @@ export function PlanDetailApp({
</header>
<div className="flex-1 min-h-0 overflow-y-auto p-4">
{deepSteps && deepSteps.length > 0 && (
<ScriptDeepProcess steps={deepSteps} active={isStreaming} />
<ScriptDeepProcess steps={deepSteps} />
)}
{!isStreaming && !hasScriptContent ? (
<div className="h-full min-h-[160px] flex flex-col items-center justify-center text-center gap-2 text-slate-400">
......
'use client';
import { useState } from 'react';
import type { DeepStep } from './use-script-stream';
/**
* ScriptDeepProcess — 深度档"可见生成过程"时间线(规划→撰写→自检→[修订])。
* 让客服像看 GPT/Claude 一样看到多步推理:大纲拟定、正文撰写(正文本身在下方逐字流)、
* 接地+安全自检结果、按问题修订。仅深度档(后端 step 事件)出现。
* 展开规则:进行中的步骤默认展开、其余收缩;用户手动点过的步骤改由用户控制(不再受程序自动展开影响)。
*/
const STEP_LABEL: Record<DeepStep['step'], string> = {
......@@ -15,30 +15,60 @@ const STEP_LABEL: Record<DeepStep['step'], string> = {
repair: '按问题修订',
};
export function ScriptDeepProcess({ steps, active }: { steps: DeepStep[]; active: boolean }) {
function stepHasDetail(s: DeepStep): boolean {
const d = s.detail;
if (!d) return false;
if (s.step === 'plan') return !!d.outline?.length;
if (s.step === 'verify') return true;
if (s.step === 'repair') return typeof d.issuesCount === 'number';
return false;
}
export function ScriptDeepProcess({ steps }: { steps: DeepStep[] }) {
// 仅记录用户手动点过的步骤的开合状态;没点过的走程序默认(进行中=展开)
const [manual, setManual] = useState<Record<string, boolean>>({});
if (!steps.length) return null;
const runningStep = steps.find((s) => s.status === 'running')?.step ?? null;
return (
<div className="mb-3 rounded-lg border border-indigo-100 bg-indigo-50/40 p-3">
<div className="mb-2 flex items-center gap-1.5 text-[11px] font-semibold text-indigo-600">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" className="h-3.5 w-3.5">
<path d="M12 3l1.9 5.8L20 10l-5.1 1.8L12 18l-1.9-6.2L5 10l6.1-1.2z" strokeLinejoin="round" />
</svg>
深度生成过程
{active && <span className="text-indigo-400">· 进行中</span>}
</div>
<ol className="space-y-2.5">
{steps.map((s) => (
<li key={s.step} className="flex gap-2.5 text-[12px]">
<div className="mb-3 rounded-lg border border-slate-200 bg-slate-50 p-2.5">
<ol className="space-y-1.5">
{steps.map((s) => {
const hasDetail = stepHasDetail(s);
const autoOpen = s.step === runningStep; // 程序默认:进行中展开
const open = s.step in manual ? manual[s.step]! : autoOpen;
const toggle = () => setManual((m) => ({ ...m, [s.step]: !open }));
return (
<li key={s.step} className="text-[12px]">
<button
type="button"
onClick={hasDetail ? toggle : undefined}
className={`flex w-full items-center gap-2 text-left ${hasDetail ? 'cursor-pointer' : 'cursor-default'}`}
>
<StepIcon status={s.status} />
<div className="min-w-0 flex-1 leading-snug">
<div className="font-medium text-slate-700">
{STEP_LABEL[s.step]}
{s.status === 'running' && <span className="shimmer-text ml-1 text-slate-400"></span>}
</div>
<span className="font-medium text-slate-700">{STEP_LABEL[s.step]}</span>
{s.status === 'running' && <span className="shimmer-text text-slate-400"></span>}
{hasDetail && (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
className={`ml-auto h-3 w-3 flex-none text-slate-400 transition-transform ${open ? 'rotate-180' : ''}`}
>
<path d="M6 9l6 6 6-6" strokeLinecap="round" strokeLinejoin="round" />
</svg>
)}
</button>
{hasDetail && open && (
<div className="mt-1 pl-[22px]">
<StepDetail step={s} />
</div>
)}
</li>
))}
);
})}
</ol>
</div>
);
......@@ -47,14 +77,14 @@ export function ScriptDeepProcess({ steps, active }: { steps: DeepStep[]; active
function StepIcon({ status }: { status: DeepStep['status'] }) {
if (status === 'running') {
return (
<svg viewBox="0 0 24 24" fill="none" className="mt-0.5 h-3.5 w-3.5 flex-none animate-spin text-indigo-500">
<svg viewBox="0 0 24 24" fill="none" className="h-3.5 w-3.5 flex-none animate-spin text-slate-400">
<circle cx="12" cy="12" r="9" stroke="currentColor" strokeWidth="3" strokeOpacity="0.25" />
<path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" strokeWidth="3" strokeLinecap="round" />
</svg>
);
}
return (
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className="mt-0.5 h-3.5 w-3.5 flex-none text-emerald-500">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" className="h-3.5 w-3.5 flex-none text-slate-500">
<path d="M20 6L9 17l-5-5" strokeLinecap="round" strokeLinejoin="round" />
</svg>
);
......@@ -64,10 +94,9 @@ function StepDetail({ step: s }: { step: DeepStep }) {
const d = s.detail;
if (!d) return null;
// 规划:展示大纲(N 段:标题 — 意图)
if (s.step === 'plan' && d.outline?.length) {
return (
<ul className="mt-1 space-y-0.5 text-[11px] text-slate-500">
<ul className="space-y-0.5 text-[11px] text-slate-500">
{d.outline.map((o, i) => (
<li key={i} className="truncate">
<span className="text-slate-600">{o.title}</span>
......@@ -78,22 +107,18 @@ function StepDetail({ step: s }: { step: DeepStep }) {
);
}
// 自检:接地+安全 + 质量分 + 待修数
if (s.step === 'verify') {
const q = d.quality?.overall;
return (
<div className="mt-0.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-[11px]">
<span className={d.pass ? 'text-emerald-600' : 'text-amber-600'}>
接地 + 安全 {d.pass ? '通过' : `${d.issuesCount ?? 0} 处待修`}
</span>
{typeof q === 'number' && <span className="text-slate-400">质量 {q.toFixed(1)} / 5</span>}
<div className="flex flex-wrap items-center gap-x-2 gap-y-0.5 text-[11px] text-slate-500">
<span>接地 + 安全 {d.pass ? '通过' : `${d.issuesCount ?? 0} 处待修`}</span>
{typeof q === 'number' && <span>· 质量 {q.toFixed(1)} / 5</span>}
</div>
);
}
// 修订:针对 N 处问题重写
if (s.step === 'repair' && typeof d.issuesCount === 'number') {
return <div className="mt-0.5 text-[11px] text-slate-500">针对 {d.issuesCount} 处问题重写</div>;
return <div className="text-[11px] text-slate-500">针对 {d.issuesCount} 处问题重写</div>;
}
return null;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment