feat(ai写作): 大纲整理可见化 + 检索词结合大纲 + 素材/大纲阶段关思考
- 草稿确认卡:「给出修改意见」「让编辑继续审核」移到「确认定稿」上方。 - 大纲整理阶段文案:用户自带大纲时显示「整理你的大纲」而非「规划大纲」 (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>
This commit is contained in:
1 parent
8678ceb422
commit
49173d9c28
13 files changed
+485
-36
No files matched your search
@@ -2,6 +2,7 @@
|
||||
|
||||
import { Check, LayoutIcon, PanelLeftIcon, Shirt } from "lucide-react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
@@ -140,6 +141,7 @@ export function LayoutModeSwitcher({
|
||||
fallback?: "full" | "compact";
|
||||
}) {
|
||||
const [mode, setLayoutMode] = useLayoutMode();
|
||||
const navigate = useNavigate();
|
||||
const current = mode ?? fallback;
|
||||
const TriggerIcon = current === "full" ? PanelLeftIcon : LayoutIcon;
|
||||
|
||||
@@ -166,7 +168,10 @@ export function LayoutModeSwitcher({
|
||||
return (
|
||||
<DropdownMenuItem
|
||||
key={option.id}
|
||||
onClick={() => setLayoutMode(option.id)}
|
||||
onClick={() => {
|
||||
setLayoutMode(option.id);
|
||||
navigate("/page/workspace/chats/new");
|
||||
}}
|
||||
className="gap-2"
|
||||
>
|
||||
<Icon className="size-4 shrink-0" />
|
||||
|
||||
@@ -496,9 +496,13 @@ export function AIWritingPanel({
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 大纲流式思考框 */}
|
||||
{/* 大纲流式思考框(用户自带大纲时文案显示「整理」) */}
|
||||
{state.status === 'writing_outline' && (state.outlineStreamingText || state.outlineThinkingText) && (
|
||||
<OutlineStreamingView text={state.outlineStreamingText} thinkingText={state.outlineThinkingText} />
|
||||
<OutlineStreamingView
|
||||
text={state.outlineStreamingText}
|
||||
thinkingText={state.outlineThinkingText}
|
||||
organizing={!!state.request?.userOutline}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 编辑审核流式结果 */}
|
||||
@@ -560,7 +564,11 @@ export function AIWritingPanel({
|
||||
/* 意图解析流式过程中(LLM 还在吐 JSON)不要显示 skeleton */
|
||||
!state.intentStreamingText && !state.intentThinkingText && (
|
||||
<div className="px-4 pb-4">
|
||||
<WritingSkeletonCard status={state.status} researchProgress={state.researchProgress} />
|
||||
<WritingSkeletonCard
|
||||
status={state.status}
|
||||
researchProgress={state.researchProgress}
|
||||
organizing={!!state.request?.userOutline}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -605,7 +613,9 @@ export function AIWritingPanel({
|
||||
)}
|
||||
{state.status === 'researching' && state.researchProgress
|
||||
? state.researchProgress
|
||||
: STATUS_LABEL[state.status] ?? state.status}
|
||||
: state.status === 'writing_outline' && state.request?.userOutline
|
||||
? '作家整理你的大纲中…'
|
||||
: STATUS_LABEL[state.status] ?? state.status}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -455,13 +455,6 @@ function DraftConfirmCard({ draftTitle, draftWordCount, wordCountTarget, revisio
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-1.5 pt-1">
|
||||
<Button
|
||||
type="button" size="sm" className="w-full h-8 text-xs gap-1.5"
|
||||
onClick={() => submit('finalize')} disabled={disabled || submitting}
|
||||
>
|
||||
<CheckCircle2 className="size-3.5" />确认定稿
|
||||
</Button>
|
||||
|
||||
{/* 按编辑审核意见修改(有审核结果时始终显示) */}
|
||||
{hasReview && (
|
||||
<Button
|
||||
@@ -504,6 +497,13 @@ function DraftConfirmCard({ draftTitle, draftWordCount, wordCountTarget, revisio
|
||||
<ArrowRight className="size-3.5" />让编辑继续审核
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="button" size="sm" className="w-full h-8 text-xs gap-1.5"
|
||||
onClick={() => submit('finalize')} disabled={disabled || submitting}
|
||||
>
|
||||
<CheckCircle2 className="size-3.5" />确认定稿
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,9 +4,11 @@ import { FileText, ChevronDown, ChevronRight, Brain } from 'lucide-react';
|
||||
interface OutlineStreamingViewProps {
|
||||
text: string;
|
||||
thinkingText?: string;
|
||||
/** 用户自带大纲时为 true:文案显示「整理你的大纲」而非「规划大纲」。 */
|
||||
organizing?: boolean;
|
||||
}
|
||||
|
||||
export function OutlineStreamingView({ text, thinkingText }: OutlineStreamingViewProps) {
|
||||
export function OutlineStreamingView({ text, thinkingText, organizing }: OutlineStreamingViewProps) {
|
||||
const preRef = useRef<HTMLPreElement>(null);
|
||||
const thinkRef = useRef<HTMLPreElement>(null);
|
||||
const [thinkOpen, setThinkOpen] = useState(true);
|
||||
@@ -66,7 +68,7 @@ export function OutlineStreamingView({ text, thinkingText }: OutlineStreamingVie
|
||||
<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) => (
|
||||
|
||||
@@ -5,6 +5,8 @@ interface WritingSkeletonCardProps {
|
||||
status: WritingStatus;
|
||||
/** 素材收集阶段的实时进度文案(仅 researching 阶段使用) */
|
||||
researchProgress?: string;
|
||||
/** 用户自带大纲时为 true:大纲阶段文案显示「整理你的大纲」而非「规划大纲」。 */
|
||||
organizing?: boolean;
|
||||
}
|
||||
|
||||
// ── 基础骨架元素 ──────────────────────────────────────────────────────────────
|
||||
@@ -80,12 +82,12 @@ function MaterialsSkeleton({ researchProgress }: { researchProgress?: string })
|
||||
|
||||
// ── 大纲规划骨架屏 ────────────────────────────────────────────────────────────
|
||||
|
||||
function OutlineSkeleton() {
|
||||
function OutlineSkeleton({ organizing }: { organizing?: boolean } = {}) {
|
||||
return (
|
||||
<div className="space-y-3 p-4">
|
||||
<div className="flex items-center gap-2 text-xs text-purple-500 font-medium">
|
||||
<ThinkingDots color="bg-purple-400" />
|
||||
<span>作家规划大纲中…</span>
|
||||
<span>{organizing ? '作家整理你的大纲中…' : '作家规划大纲中…'}</span>
|
||||
</div>
|
||||
|
||||
{/* 文章标题占位 */}
|
||||
@@ -213,7 +215,7 @@ const SKELETON_MAP: Partial<Record<WritingStatus, React.FC>> = {
|
||||
awaiting_review_confirm: RevisingSkeleton, // 采纳编辑意见后
|
||||
};
|
||||
|
||||
export function WritingSkeletonCard({ status, researchProgress }: WritingSkeletonCardProps) {
|
||||
export function WritingSkeletonCard({ status, researchProgress, organizing }: WritingSkeletonCardProps) {
|
||||
// 素材收集阶段单独渲染,以便透传实时进度文案
|
||||
if (status === 'researching' || status === 'awaiting_material_confirm') {
|
||||
return (
|
||||
@@ -222,6 +224,14 @@ export function WritingSkeletonCard({ status, researchProgress }: WritingSkeleto
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// 大纲阶段单独渲染,以便透传「整理 / 规划」文案(用户自带大纲走「整理」)
|
||||
if (status === 'writing_outline' || status === 'awaiting_outline_confirm') {
|
||||
return (
|
||||
<div className="rounded-xl border bg-card shadow-sm overflow-hidden">
|
||||
<OutlineSkeleton organizing={organizing} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const Skeleton = SKELETON_MAP[status];
|
||||
if (!Skeleton) return null;
|
||||
return (
|
||||
|
||||
Reference in new issue
Block a user