feat: AI writing history, Word export, roundtable dispatch chain
This commit is contained in:
1 parent
c0e3c5d692
commit
10334ddfe1
21 files changed
+1888
-143
No files matched your search
@@ -1,69 +1,12 @@
|
||||
import { apiFetch } from "@/core/api/fetch-client";
|
||||
import { getBackendBaseURL } from "@/core/config";
|
||||
import { downloadMarkdownDocxFromBackend } from "@/core/api/markdown-docx-export";
|
||||
|
||||
function filenameFromContentDisposition(header: string | null): string | null {
|
||||
if (!header) return null;
|
||||
const star = /filename\*=(?:UTF-8'')?([^;]+)/i.exec(header);
|
||||
if (star) {
|
||||
const raw = star[1].replace(/["']/g, "").trim();
|
||||
try {
|
||||
return decodeURIComponent(raw);
|
||||
} catch {
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
const plain = /filename="?([^";\n]+)"?/i.exec(header);
|
||||
return plain ? plain[1].trim() : null;
|
||||
}
|
||||
|
||||
function sanitizeFallbackFilename(title: string | undefined): string {
|
||||
const base = (title?.trim() || "document").replace(/[<>:"/\\|?*\x00-\x1f]+/g, "_");
|
||||
return base.toLowerCase().endsWith(".docx") ? base : `${base}.docx`;
|
||||
}
|
||||
|
||||
async function downloadBlobResponse(response: Response, fallbackFilename: string) {
|
||||
if (!response.ok) {
|
||||
const error = await response
|
||||
.json()
|
||||
.catch(() => ({ detail: "导出 Word 失败" }));
|
||||
const detail = error.detail;
|
||||
if (typeof detail === "string") throw new Error(detail);
|
||||
throw new Error("导出 Word 失败");
|
||||
}
|
||||
|
||||
const blob = await response.blob();
|
||||
const filename =
|
||||
filenameFromContentDisposition(response.headers.get("Content-Disposition")) ??
|
||||
fallbackFilename;
|
||||
const url = URL.createObjectURL(blob);
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = url;
|
||||
anchor.download = filename;
|
||||
anchor.rel = "noopener";
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
anchor.remove();
|
||||
window.setTimeout(() => URL.revokeObjectURL(url), 30_000);
|
||||
}
|
||||
|
||||
/** 调用后端导出 AI 写作草稿为 Word,仅传正文 markdown,不传目录/大纲。 */
|
||||
/** AI 写作草稿导出 Word(走通用 `/api/writing/export/docx`)。 */
|
||||
export async function downloadAIWritingDocx(options: {
|
||||
title?: string;
|
||||
draftMarkdown: string;
|
||||
}): Promise<void> {
|
||||
const draftMarkdown = options.draftMarkdown.trim();
|
||||
if (!draftMarkdown) {
|
||||
throw new Error("文档内容为空");
|
||||
}
|
||||
|
||||
const response = await apiFetch(`${getBackendBaseURL()}/api/ai-writing/export/docx`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
title: options.title?.trim() || undefined,
|
||||
draft_markdown: draftMarkdown,
|
||||
}),
|
||||
await downloadMarkdownDocxFromBackend({
|
||||
title: options.title,
|
||||
markdown: options.draftMarkdown,
|
||||
});
|
||||
|
||||
await downloadBlobResponse(response, sanitizeFallbackFilename(options.title));
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import { FileText, PenLine } from 'lucide-react';
|
||||
|
||||
import { useAIWritingOptional } from '../../contexts/AIWritingContext';
|
||||
import { useAIWritingHistoryOptional } from '../../contexts/AIWritingHistoryContext';
|
||||
import { useCanvasGraphContext } from '../../contexts/CanvasGraphContext';
|
||||
import { useCanvasUIContext } from '../../contexts/CanvasUIContext';
|
||||
import { assembleStreamingDraftMarkdown } from '../../hooks/useAIWritingStream';
|
||||
@@ -32,8 +33,12 @@ function DraftWaitingPlaceholder() {
|
||||
|
||||
export function AIWritingDraftPanel() {
|
||||
const writing = useAIWritingOptional();
|
||||
const history = useAIWritingHistoryOptional();
|
||||
const isHistoryMode = history?.isHistoryMode ?? false;
|
||||
const { artifact } = useCanvasGraphContext();
|
||||
const { isDraftEditable } = useCanvasUIContext();
|
||||
// 历史回看与写作完成态一致:Tiptap + 目录;流式生成中仍走 Markdown 预览
|
||||
const showCompletedDraftView = isDraftEditable || isHistoryMode;
|
||||
|
||||
const writingStatus = writing?.state.status;
|
||||
// 仅当章节仍在累积时走流式 Markdown;draft_ready 清空 sections 后即使 status 尚未切走也进画布
|
||||
@@ -96,7 +101,8 @@ export function AIWritingDraftPanel() {
|
||||
<div className="flex-1 overflow-hidden">
|
||||
<TextArtifactRenderer
|
||||
content={textContent}
|
||||
isStreaming={!isDraftEditable}
|
||||
isStreaming={!showCompletedDraftView}
|
||||
readOnly={isHistoryMode}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -13,8 +13,8 @@ import {
|
||||
} from '@/components/ui/dialog';
|
||||
import { useArticleTypes } from '../../hooks/useArticleTypes';
|
||||
|
||||
import { useCanvasGraphContext } from '../../contexts/CanvasGraphContext';
|
||||
import { useAIWriting } from '../../contexts/AIWritingContext';
|
||||
import { useAIWritingHistory } from '../../contexts/AIWritingHistoryContext';
|
||||
import { useAIWritingSessions } from '../../hooks/useAIWritingSessions';
|
||||
import { adminCleanupAIWritingSessions } from '../../api/ai-writing-sessions';
|
||||
|
||||
@@ -74,15 +74,13 @@ export function AIWritingPanel({
|
||||
hideAdvancedFields?: boolean;
|
||||
} = {}) {
|
||||
const navigate = useNavigate();
|
||||
const { setArtifact } = useCanvasGraphContext();
|
||||
const { state, startWriting, submitIntervention, pauseWriting, resumeWriting, reset } = useAIWriting();
|
||||
const { historySession, isHistoryMode, openHistory, closeHistory } = useAIWritingHistory();
|
||||
const { sessions, loading: sessionsLoading, refresh, rename, remove } = useAIWritingSessions();
|
||||
const { articleTypes, refresh: refreshArticleTypes } = useArticleTypes();
|
||||
|
||||
useEffect(() => { refreshArticleTypes(); }, [refreshArticleTypes]);
|
||||
|
||||
// History view state — null means normal writing mode
|
||||
const [historySession, setHistorySession] = useState<AIWritingSessionSummary | null>(null);
|
||||
const [reqDialogOpen, setReqDialogOpen] = useState(false);
|
||||
|
||||
const isIdle = state.status === 'idle';
|
||||
@@ -178,27 +176,13 @@ export function AIWritingPanel({
|
||||
}, []);
|
||||
|
||||
const handleSelectHistory = useCallback((session: AIWritingSessionSummary) => {
|
||||
setHistorySession(session);
|
||||
// Push historical draft to the right panel
|
||||
if (session.draft_markdown) {
|
||||
setArtifact({
|
||||
currentIndex: 0,
|
||||
contents: [{
|
||||
index: 0,
|
||||
type: 'text',
|
||||
title: session.draft_title ?? session.title,
|
||||
fullMarkdown: session.draft_markdown,
|
||||
}],
|
||||
});
|
||||
} else {
|
||||
setArtifact(null);
|
||||
}
|
||||
}, [setArtifact]);
|
||||
void openHistory(session);
|
||||
}, [openHistory]);
|
||||
|
||||
const handleNewSession = useCallback(() => {
|
||||
setHistorySession(null);
|
||||
closeHistory();
|
||||
reset();
|
||||
}, [reset]);
|
||||
}, [closeHistory, reset]);
|
||||
|
||||
// ── 侧边栏"最近写作"通信:两种路径 ────────────────────────────────────────
|
||||
// 用 ref 包住 handler,保证事件监听器始终能拿到最新版本
|
||||
@@ -246,60 +230,27 @@ export function AIWritingPanel({
|
||||
}
|
||||
}, [refresh]);
|
||||
|
||||
const showNewButton = historySession !== null || state.status !== 'idle';
|
||||
|
||||
// History view mode
|
||||
if (historySession) {
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-hidden bg-background">
|
||||
<DraftSync status={state.status} markdown={state.currentDraftMarkdown} title={state.currentDraftTitle} />
|
||||
<div className="flex items-center justify-between px-4 py-2.5 border-b shrink-0">
|
||||
<span className="text-sm font-semibold truncate flex-1 min-w-0 mr-2">
|
||||
{historySession.draft_title ?? historySession.title}
|
||||
</span>
|
||||
<div className="flex items-center gap-1.5 shrink-0">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
className="h-7 w-7"
|
||||
onClick={() => navigate('/page/canvas/article-types')}
|
||||
title="文章类型配置"
|
||||
>
|
||||
<Settings2 className="size-3.5" />
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" className="h-7 px-2 text-xs gap-1" onClick={handleNewSession}>
|
||||
<Plus className="size-3" />
|
||||
新建对话
|
||||
</Button>
|
||||
<AIWritingHistoryDropdown
|
||||
sessions={sessions}
|
||||
selectedId={historySession.id}
|
||||
loading={sessionsLoading}
|
||||
onSelect={handleSelectHistory}
|
||||
onDelete={remove}
|
||||
onRename={rename}
|
||||
onRefresh={refresh}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<AIWritingHistoryView session={historySession} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const showNewButton = isHistoryMode || state.status !== 'idle';
|
||||
|
||||
return (
|
||||
<div className="flex flex-col h-full overflow-hidden bg-background">
|
||||
<DraftSync status={state.status} markdown={state.currentDraftMarkdown} title={state.currentDraftTitle} />
|
||||
{!isHistoryMode && (
|
||||
<DraftSync status={state.status} markdown={state.currentDraftMarkdown} title={state.currentDraftTitle} />
|
||||
)}
|
||||
{/* 头部 */}
|
||||
<div className="flex items-center justify-between px-4 py-2.5 border-b shrink-0">
|
||||
<span
|
||||
className={cn(
|
||||
'text-sm font-semibold shrink-0',
|
||||
!isIdle && 'cursor-pointer hover:text-primary transition-colors',
|
||||
'text-sm font-semibold shrink-0 min-w-0 truncate',
|
||||
isHistoryMode && 'flex-1 mr-2',
|
||||
!isIdle && !isHistoryMode && 'cursor-pointer hover:text-primary transition-colors',
|
||||
)}
|
||||
onClick={() => { if (!isIdle && state.request) setReqDialogOpen(true); }}
|
||||
onClick={() => { if (!isIdle && !isHistoryMode && state.request) setReqDialogOpen(true); }}
|
||||
title={isHistoryMode ? (historySession?.draft_title ?? historySession?.title) : undefined}
|
||||
>
|
||||
AI 智能写作
|
||||
{isHistoryMode
|
||||
? (historySession?.draft_title ?? historySession?.title ?? '写作历史')
|
||||
: 'AI 智能写作'}
|
||||
</span>
|
||||
<div className="flex items-center gap-1.5">
|
||||
{/* ⚠️ 临时调试:admin 一键清理 —— 阶段 7 删冗余时移除 */}
|
||||
@@ -365,7 +316,7 @@ export function AIWritingPanel({
|
||||
)}
|
||||
<AIWritingHistoryDropdown
|
||||
sessions={sessions}
|
||||
selectedId={null}
|
||||
selectedId={historySession?.id ?? null}
|
||||
loading={sessionsLoading}
|
||||
onSelect={handleSelectHistory}
|
||||
onDelete={remove}
|
||||
@@ -378,8 +329,13 @@ export function AIWritingPanel({
|
||||
{/* 主体:relative 父容器 + absolute 滚动容器,确保滚动容器尺寸始终正确 */}
|
||||
<div className="flex-1 min-h-0 relative">
|
||||
<div ref={scrollRef} className="absolute inset-0 overflow-y-auto">
|
||||
{/* 历史回看:左侧时间线(与实时写作同一套 AIWritingTimeline) */}
|
||||
{isHistoryMode && historySession && (
|
||||
<AIWritingHistoryView session={historySession} />
|
||||
)}
|
||||
|
||||
{/* 未开始时显示表单 */}
|
||||
{isIdle && (
|
||||
{isIdle && !isHistoryMode && (
|
||||
<AIWritingForm
|
||||
onSubmit={startWriting}
|
||||
hideAdvancedFields={hideAdvancedFields}
|
||||
@@ -402,7 +358,7 @@ export function AIWritingPanel({
|
||||
)}
|
||||
|
||||
{/* 运行中或完成后显示时间线 */}
|
||||
{!isIdle && (
|
||||
{!isIdle && !isHistoryMode && (
|
||||
<AIWritingTimeline
|
||||
events={state.progressEvents}
|
||||
completedInterventions={state.completedInterventions}
|
||||
@@ -507,7 +463,7 @@ export function AIWritingPanel({
|
||||
</div>{/* 相对定位容器结束 */}
|
||||
|
||||
{/* 状态栏 */}
|
||||
{!isIdle && (
|
||||
{!isIdle && !isHistoryMode && (
|
||||
<div
|
||||
className={cn(
|
||||
'shrink-0 px-4 py-2 border-t text-xs flex items-center gap-2',
|
||||
|
||||
@@ -22,6 +22,8 @@ import type { RewriteAction, RewriteTone } from "../../api/rewrite";
|
||||
interface TextArtifactRendererProps {
|
||||
content: CanvasArtifactText;
|
||||
isStreaming?: boolean;
|
||||
/** 完成态只读(如历史回看):保留目录 + 排版,禁用编辑与 AI 润色 */
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
interface PanelPos {
|
||||
@@ -38,7 +40,7 @@ interface LastAction {
|
||||
customInstruction?: string;
|
||||
}
|
||||
|
||||
export function TextArtifactRenderer({ content, isStreaming }: TextArtifactRendererProps) {
|
||||
export function TextArtifactRenderer({ content, isStreaming, readOnly = false }: TextArtifactRendererProps) {
|
||||
const { artifact, setArtifact } = useCanvasGraphContext();
|
||||
const { writingSelection } = useCanvasUIContext();
|
||||
|
||||
@@ -72,7 +74,7 @@ export function TextArtifactRenderer({ content, isStreaming }: TextArtifactRende
|
||||
const editor = useWritingEditor({
|
||||
markdown: content.fullMarkdown,
|
||||
onChange: handleEditorChange,
|
||||
editable: !isStreaming,
|
||||
editable: !isStreaming && !readOnly,
|
||||
placeholder: "开始写作…",
|
||||
});
|
||||
|
||||
@@ -270,10 +272,15 @@ export function TextArtifactRenderer({ content, isStreaming }: TextArtifactRende
|
||||
);
|
||||
}
|
||||
|
||||
// ── Edit mode ─────────────────────────────────────────────────────────
|
||||
// ── Edit mode(写作完成态 / 历史回看只读)────────────────────────────────
|
||||
return (
|
||||
<div className="h-full flex flex-col overflow-hidden">
|
||||
<WritingToolbar editor={editor} markdown={content.fullMarkdown} title={content.title} />
|
||||
<WritingToolbar
|
||||
editor={editor}
|
||||
markdown={content.fullMarkdown}
|
||||
title={content.title}
|
||||
readOnly={readOnly}
|
||||
/>
|
||||
|
||||
<div className="flex-1 flex min-h-0 overflow-hidden">
|
||||
{/* 左侧目录栏(仿豆包):文档标题层级导航 */}
|
||||
@@ -282,7 +289,7 @@ export function TextArtifactRenderer({ content, isStreaming }: TextArtifactRende
|
||||
{/* 文档滚动区 */}
|
||||
<div ref={setDocScrollEl} className="flex-1 overflow-y-auto">
|
||||
<div className="max-w-2xl mx-auto px-10 pt-10">
|
||||
{editor && (
|
||||
{editor && !readOnly && (
|
||||
<>
|
||||
<WritingBubbleMenu editor={editor} onAiRewrite={openAiMenu} />
|
||||
<WritingParagraphMenu editor={editor} onAiRewrite={openAiMenu} />
|
||||
@@ -294,7 +301,7 @@ export function TextArtifactRenderer({ content, isStreaming }: TextArtifactRende
|
||||
</div>
|
||||
|
||||
{/* Phase: AI action menu */}
|
||||
{phase === "menu" && (
|
||||
{!readOnly && phase === "menu" && (
|
||||
<WritingAiMenu
|
||||
pos={panelPos}
|
||||
status={status}
|
||||
@@ -307,7 +314,7 @@ export function TextArtifactRenderer({ content, isStreaming }: TextArtifactRende
|
||||
)}
|
||||
|
||||
{/* Phase: result preview */}
|
||||
{phase === "preview" && (
|
||||
{!readOnly && phase === "preview" && (
|
||||
<WritingRewritePreview
|
||||
pos={panelPos}
|
||||
status={status}
|
||||
|
||||
@@ -69,9 +69,11 @@ interface WritingToolbarProps {
|
||||
markdown?: string;
|
||||
/** 文档标题,用作 .docx 文件名与封面 */
|
||||
title?: string;
|
||||
/** 历史回看等场景:仅保留导出,隐藏格式工具 */
|
||||
readOnly?: boolean;
|
||||
}
|
||||
|
||||
export function WritingToolbar({ editor, markdown, title }: WritingToolbarProps) {
|
||||
export function WritingToolbar({ editor, markdown, title, readOnly = false }: WritingToolbarProps) {
|
||||
const [exporting, setExporting] = useState(false);
|
||||
|
||||
if (!editor) return null;
|
||||
@@ -94,6 +96,42 @@ export function WritingToolbar({ editor, markdown, title }: WritingToolbarProps)
|
||||
}
|
||||
};
|
||||
|
||||
if (readOnly) {
|
||||
return (
|
||||
<div className="flex items-center gap-2 px-3 py-1.5 border-b bg-background/80 backdrop-blur-sm shrink-0">
|
||||
<span className="text-xs text-muted-foreground">历史文稿(只读)</span>
|
||||
<div className="ml-auto flex items-center gap-1">
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
void handleExport();
|
||||
}}
|
||||
disabled={!canExport}
|
||||
className={cn(
|
||||
"inline-flex h-7 w-7 items-center justify-center rounded-md text-sm transition-colors",
|
||||
"hover:bg-accent hover:text-accent-foreground",
|
||||
"disabled:pointer-events-none disabled:opacity-40",
|
||||
)}
|
||||
>
|
||||
{exporting ? (
|
||||
<Loader2 className="h-3.5 w-3.5 animate-spin" />
|
||||
) : (
|
||||
<Download className="h-3.5 w-3.5" />
|
||||
)}
|
||||
</button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent side="bottom" className="text-xs">
|
||||
导出 Word
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-0.5 px-3 py-1.5 border-b bg-background/80 backdrop-blur-sm shrink-0 flex-wrap">
|
||||
{/* Block type */}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
import { createContext, useCallback, useContext, useMemo, useState, type ReactNode } from 'react';
|
||||
|
||||
import { getAIWritingSession } from '../api/ai-writing-sessions';
|
||||
import type { AIWritingSessionSummary } from '../api/ai-writing-sessions';
|
||||
import { useCanvasGraphContext } from './CanvasGraphContext';
|
||||
import { useCanvasUIContext } from './CanvasUIContext';
|
||||
|
||||
type AIWritingHistoryContextValue = {
|
||||
historySession: AIWritingSessionSummary | null;
|
||||
isHistoryMode: boolean;
|
||||
openHistory: (session: AIWritingSessionSummary) => Promise<void>;
|
||||
closeHistory: () => void;
|
||||
};
|
||||
|
||||
const AIWritingHistoryContext = createContext<AIWritingHistoryContextValue | null>(null);
|
||||
|
||||
export function AIWritingHistoryProvider({ children }: { children: ReactNode }) {
|
||||
const { setArtifact } = useCanvasGraphContext();
|
||||
const { setIsDraftEditable } = useCanvasUIContext();
|
||||
const [historySession, setHistorySession] = useState<AIWritingSessionSummary | null>(null);
|
||||
|
||||
const openHistory = useCallback(async (session: AIWritingSessionSummary) => {
|
||||
let full = session;
|
||||
if (!session.draft_markdown || !session.transcript) {
|
||||
try {
|
||||
full = await getAIWritingSession(session.id);
|
||||
} catch {
|
||||
// 列表项已够用,拉详情失败则继续用摘要
|
||||
}
|
||||
}
|
||||
setHistorySession(full);
|
||||
// 完成态布局(Tiptap + 左侧目录),由 DraftPanel 根据 isHistoryMode 只读展示
|
||||
setIsDraftEditable(true);
|
||||
if (full.draft_markdown) {
|
||||
setArtifact({
|
||||
currentIndex: 0,
|
||||
contents: [{
|
||||
index: 0,
|
||||
type: 'text',
|
||||
title: full.draft_title ?? full.title,
|
||||
fullMarkdown: full.draft_markdown,
|
||||
}],
|
||||
});
|
||||
} else {
|
||||
setArtifact(null);
|
||||
}
|
||||
}, [setArtifact, setIsDraftEditable]);
|
||||
|
||||
const closeHistory = useCallback(() => {
|
||||
setHistorySession(null);
|
||||
setArtifact(null);
|
||||
setIsDraftEditable(true);
|
||||
}, [setArtifact, setIsDraftEditable]);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
historySession,
|
||||
isHistoryMode: historySession !== null,
|
||||
openHistory,
|
||||
closeHistory,
|
||||
}),
|
||||
[historySession, openHistory, closeHistory],
|
||||
);
|
||||
|
||||
return (
|
||||
<AIWritingHistoryContext.Provider value={value}>
|
||||
{children}
|
||||
</AIWritingHistoryContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useAIWritingHistory() {
|
||||
const ctx = useContext(AIWritingHistoryContext);
|
||||
if (!ctx) {
|
||||
throw new Error('useAIWritingHistory must be used within AIWritingHistoryProvider');
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
export function useAIWritingHistoryOptional() {
|
||||
return useContext(AIWritingHistoryContext);
|
||||
}
|
||||
@@ -92,6 +92,7 @@ import { AIWritingPanel } from '../components/ai-writing/AIWritingPanel';
|
||||
import { AIWritingDraftPanel } from '../components/ai-writing/AIWritingDraftPanel';
|
||||
import { WritingSetupChat } from '../components/ai-writing/WritingSetupChat';
|
||||
import { AIWritingProvider, useAIWriting } from '../contexts/AIWritingContext';
|
||||
import { AIWritingHistoryProvider, useAIWritingHistory } from '../contexts/AIWritingHistoryContext';
|
||||
import { WritingFormProvider } from '../contexts/WritingFormContext';
|
||||
import {
|
||||
ResizableHandle,
|
||||
@@ -131,8 +132,10 @@ function WritingFormPanel({ hideAdvancedFields }: { hideAdvancedFields: boolean
|
||||
function AIWritingLayout({ hideAdvancedFields }: { hideAdvancedFields: boolean }) {
|
||||
const layoutRef = useRef<GroupImperativeHandle>(null);
|
||||
const { state } = useAIWriting();
|
||||
const { isHistoryMode } = useAIWritingHistory();
|
||||
|
||||
if (state.status === 'idle') {
|
||||
// 初始问答阶段:无历史查看;查看历史时与「正在写作」相同左右分栏(左时间线、右草稿)
|
||||
if (state.status === 'idle' && !isHistoryMode) {
|
||||
return (
|
||||
<ResizablePanelGroup
|
||||
orientation="horizontal"
|
||||
@@ -206,6 +209,7 @@ export default function AIWritingPage({
|
||||
<CanvasThreadProvider>
|
||||
<CanvasGraphProvider>
|
||||
<CanvasUIProvider>
|
||||
<AIWritingHistoryProvider>
|
||||
<AIWritingProvider>
|
||||
<WritingFormProvider>
|
||||
<TooltipProvider delayDuration={300}>
|
||||
@@ -217,6 +221,7 @@ export default function AIWritingPage({
|
||||
</TooltipProvider>
|
||||
</WritingFormProvider>
|
||||
</AIWritingProvider>
|
||||
</AIWritingHistoryProvider>
|
||||
</CanvasUIProvider>
|
||||
</CanvasGraphProvider>
|
||||
</CanvasThreadProvider>
|
||||
|
||||
Reference in new issue
Block a user