- 草稿确认卡:「给出修改意见」「让编辑继续审核」移到「确认定稿」上方。 - 大纲整理阶段文案:用户自带大纲时显示「整理你的大纲」而非「规划大纲」 (OutlineStreamingView / 骨架屏 / 状态栏,按 request.userOutline 切换)。 - 素材收集专家:用户自带大纲时把大纲原文带入检索词生成,让关键词结合写作 意图覆盖各章节主题(_keyword_outline_section,agent + legacy 两路)。 - 素材收集(检索词/排序)与大纲整理/生成阶段强制关闭思考 (create_chat_model force_disable_thinking:GLM thinking.disabled + Qwen/vLLM enable_thinking=false),关不掉的模型仍由 ThinkTagParser 把 <think> 分流到思考框正常展示。disable_thinking 串入 _utils.get_model/llm_json 与 _agent_runner.stream/run_writing_agent。 - 含草稿「局部修改」WIP(writer_draft 只重写命中章节)及外观控件改动。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
102 lines
4.0 KiB
TypeScript
102 lines
4.0 KiB
TypeScript
import { useEffect, useRef, useState } from 'react';
|
|
import { FileText, ChevronDown, ChevronRight, Brain } from 'lucide-react';
|
|
|
|
interface OutlineStreamingViewProps {
|
|
text: string;
|
|
thinkingText?: string;
|
|
/** 用户自带大纲时为 true:文案显示「整理你的大纲」而非「规划大纲」。 */
|
|
organizing?: boolean;
|
|
}
|
|
|
|
export function OutlineStreamingView({ text, thinkingText, organizing }: OutlineStreamingViewProps) {
|
|
const preRef = useRef<HTMLPreElement>(null);
|
|
const thinkRef = useRef<HTMLPreElement>(null);
|
|
const [thinkOpen, setThinkOpen] = useState(true);
|
|
|
|
useEffect(() => {
|
|
if (preRef.current) {
|
|
preRef.current.scrollTop = preRef.current.scrollHeight;
|
|
}
|
|
}, [text]);
|
|
|
|
useEffect(() => {
|
|
if (thinkOpen && thinkRef.current) {
|
|
thinkRef.current.scrollTop = thinkRef.current.scrollHeight;
|
|
}
|
|
}, [thinkingText, thinkOpen]);
|
|
|
|
if (!text && !thinkingText) return null;
|
|
|
|
return (
|
|
<div className="px-4 pb-2 space-y-2">
|
|
{/* 思考过程区(仅在有思考内容时展示) */}
|
|
{thinkingText && (
|
|
<div className="rounded-xl border border-amber-200 dark:border-amber-900 bg-amber-50/50 dark:bg-amber-950/20 overflow-hidden shadow-sm">
|
|
<button
|
|
type="button"
|
|
onClick={() => setThinkOpen((v) => !v)}
|
|
className="w-full flex items-center gap-2 px-3 py-2 border-b border-amber-200/60 dark:border-amber-900/60 bg-amber-100/40 dark:bg-amber-900/30 hover:bg-amber-100/70 dark:hover:bg-amber-900/50 transition-colors"
|
|
>
|
|
<Brain className="size-3.5 text-amber-500 shrink-0" />
|
|
<span className="text-xs font-medium text-amber-600 dark:text-amber-400">
|
|
思考过程
|
|
</span>
|
|
<span className="ml-auto text-amber-400">
|
|
{thinkOpen
|
|
? <ChevronDown className="size-3.5" />
|
|
: <ChevronRight className="size-3.5" />}
|
|
</span>
|
|
</button>
|
|
{thinkOpen && (
|
|
<pre
|
|
ref={thinkRef}
|
|
className="text-[11px] font-mono leading-relaxed p-3 text-amber-700/70 dark:text-amber-300/60 whitespace-pre-wrap max-h-40 overflow-y-auto italic"
|
|
>
|
|
{thinkingText}
|
|
{/* 思考仍在进行时显示光标 */}
|
|
{!text && (
|
|
<span className="inline-block w-0.5 h-3.5 bg-amber-400 ml-0.5 align-text-bottom animate-[blink_1s_step-end_infinite]" />
|
|
)}
|
|
</pre>
|
|
)}
|
|
</div>
|
|
)}
|
|
|
|
{/* 正文流式区 */}
|
|
{text && (
|
|
<div className="rounded-xl border border-purple-200 dark:border-purple-900 bg-purple-50/50 dark:bg-purple-950/20 overflow-hidden shadow-sm">
|
|
<div className="flex items-center gap-2 px-3 py-2 border-b border-purple-200/60 dark:border-purple-900/60 bg-purple-100/40 dark:bg-purple-900/30">
|
|
<FileText className="size-3.5 text-purple-500 shrink-0" />
|
|
<span className="text-xs font-medium text-purple-600 dark:text-purple-400">
|
|
{organizing ? '作家正在整理你的大纲…' : '作家正在规划大纲…'}
|
|
</span>
|
|
<span className="ml-auto inline-flex gap-1">
|
|
{[0, 1, 2].map((i) => (
|
|
<span
|
|
key={i}
|
|
className="size-1 rounded-full bg-purple-400 animate-bounce"
|
|
style={{ animationDelay: `${i * 150}ms` }}
|
|
/>
|
|
))}
|
|
</span>
|
|
</div>
|
|
<pre
|
|
ref={preRef}
|
|
className="text-[11px] font-mono leading-relaxed p-3 text-muted-foreground whitespace-pre-wrap max-h-52 overflow-y-auto"
|
|
>
|
|
{text}
|
|
<span className="inline-block w-0.5 h-3.5 bg-purple-400 ml-0.5 align-text-bottom animate-[blink_1s_step-end_infinite]" />
|
|
</pre>
|
|
</div>
|
|
)}
|
|
|
|
<style>{`
|
|
@keyframes blink {
|
|
0%, 100% { opacity: 1; }
|
|
50% { opacity: 0; }
|
|
}
|
|
`}</style>
|
|
</div>
|
|
);
|
|
}
|