Merge branch 'ji' into dhk-1
Resolve column_additions_mysql.sql conflict: keep square_id columns from master and roundtable_drafts.task_id from dhk-1. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
commit
bcc3d617a0
142 files changed
+14953
-2383
No files matched your search
@@ -22,6 +22,12 @@ interface AIWritingFormProps {
|
||||
disabled?: boolean;
|
||||
/** workspace 入口:隐藏严格模式、目标读者 */
|
||||
hideAdvancedFields?: boolean;
|
||||
/** 预选空间 ID(传入时自动切换素材来源为「我的空间」) */
|
||||
initialNotebookId?: number;
|
||||
/** 预选文档 ID 列表 */
|
||||
initialDocumentIds?: number[];
|
||||
/** 外部直接提供的 studio bearer token,跳过 getTokenByEmail */
|
||||
studioToken?: string;
|
||||
}
|
||||
|
||||
const INPUT_CLASS =
|
||||
@@ -39,7 +45,7 @@ const MAX_INTENT_LENGTH = 500;
|
||||
const MATERIAL_SOURCE_OPTIONS: Array<{ value: MaterialSource; label: string; desc: string }> = [
|
||||
{ value: 'knowledge_base', label: '知识库', desc: '默认 web/intranet 检索' },
|
||||
{ value: 'skill', label: '技能检索', desc: '用已启用的技能编排检索' },
|
||||
{ value: 'notebook', label: '我的笔记', desc: '从笔记本中检索素材' },
|
||||
{ value: 'notebook', label: '我的空间', desc: '从空间中检索素材' },
|
||||
];
|
||||
|
||||
const FIELD_LABEL_CLASS = 'text-[16px] font-semibold leading-none text-[#222]';
|
||||
@@ -50,6 +56,9 @@ export function AIWritingForm({
|
||||
onSubmit,
|
||||
disabled,
|
||||
hideAdvancedFields = false,
|
||||
initialNotebookId,
|
||||
initialDocumentIds,
|
||||
studioToken,
|
||||
}: AIWritingFormProps) {
|
||||
const {
|
||||
intent, setIntent,
|
||||
@@ -62,13 +71,15 @@ export function AIWritingForm({
|
||||
materialSource, setMaterialSource,
|
||||
modelName, setModelName,
|
||||
} = useWritingForm();
|
||||
// 笔记来源:选中的 search_space_id 和多个 document_id(master 引入,useWritingForm 未含)
|
||||
const [selectedNotebookId, setSelectedNotebookId] = useState<number | null>(null);
|
||||
const [selectedDocumentIds, setSelectedDocumentIds] = useState<number[]>([]);
|
||||
// 笔记来源:选中的 search_space_id 和多个 document_id
|
||||
const [selectedNotebookId, setSelectedNotebookId] = useState<number | null>(
|
||||
initialNotebookId ?? null,
|
||||
);
|
||||
const [selectedDocumentIds, setSelectedDocumentIds] = useState<number[]>(initialDocumentIds ?? []);
|
||||
|
||||
const { models, isLoading: modelsLoading } = useModels();
|
||||
const { notebooks, isLoading: notebooksLoading } = useStudioNotebooks();
|
||||
const { sources, isLoading: sourcesLoading } = useNotebookSources(selectedNotebookId);
|
||||
const { notebooks, isLoading: notebooksLoading } = useStudioNotebooks(!studioToken);
|
||||
const { sources, isLoading: sourcesLoading } = useNotebookSources(selectedNotebookId, studioToken);
|
||||
|
||||
const { articleTypes, loading: typesLoading, refresh: refreshTypes } = useArticleTypes();
|
||||
|
||||
@@ -86,6 +97,7 @@ export function AIWritingForm({
|
||||
[models, modelName],
|
||||
);
|
||||
|
||||
|
||||
// 初始化时加载文章类型
|
||||
useEffect(() => {
|
||||
refreshTypes();
|
||||
@@ -127,7 +139,8 @@ export function AIWritingForm({
|
||||
|
||||
function handleMaterialSourceChange(val: MaterialSource) {
|
||||
setMaterialSource(val);
|
||||
if (val !== 'notebook') {
|
||||
// studioToken 模式下空间和文档已锁定,切换素材来源时不清空
|
||||
if (val !== 'notebook' && !studioToken) {
|
||||
setSelectedNotebookId(null);
|
||||
setSelectedDocumentIds([]);
|
||||
}
|
||||
@@ -153,9 +166,9 @@ export function AIWritingForm({
|
||||
wordCountTarget: wordCount,
|
||||
keywordCount: DEFAULT_KEYWORD_COUNT,
|
||||
writingMode: hideAdvancedFields ? 'loose' : strictMode ? 'strict' : 'loose',
|
||||
materialSource,
|
||||
materialSource: materialSource,
|
||||
modelName: selectedModel?.name || modelName || undefined,
|
||||
// 只有选了"我的笔记"才传笔记参数
|
||||
// 只有选了"我的空间"才传笔记参数
|
||||
noteSearchSpaceId: materialSource === 'notebook' ? (selectedNotebookId ?? undefined) : undefined,
|
||||
noteSourceIds: materialSource === 'notebook' && selectedNotebookId != null ? selectedDocumentIds : undefined,
|
||||
});
|
||||
@@ -285,32 +298,35 @@ export function AIWritingForm({
|
||||
</SelectContent>
|
||||
</Select>
|
||||
|
||||
{/* 选了"我的笔记"才展示笔记本 / 文档选择器 */}
|
||||
{/* 选了"我的空间"才展示空间 / 文档选择器 */}
|
||||
{materialSource === 'notebook' && (
|
||||
<div className="mt-2 space-y-2">
|
||||
<Select
|
||||
value={selectedNotebookId !== null ? String(selectedNotebookId) : ''}
|
||||
onValueChange={handleNotebookChange}
|
||||
disabled={disabled || notebooksLoading}
|
||||
>
|
||||
<SelectTrigger className={FIELD_TRIGGER_CLASS}>
|
||||
<SelectValue placeholder={notebooksLoading ? '加载中…' : '选择笔记本'} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{notebooks.map((nb) => (
|
||||
<SelectItem key={nb.id} value={String(nb.id)} className="text-sm">
|
||||
{nb.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
{/* studioToken 模式:空间已锁定,不展示选择器 */}
|
||||
{!studioToken && (
|
||||
<Select
|
||||
value={selectedNotebookId !== null ? String(selectedNotebookId) : ''}
|
||||
onValueChange={handleNotebookChange}
|
||||
disabled={disabled || notebooksLoading}
|
||||
>
|
||||
<SelectTrigger className={FIELD_TRIGGER_CLASS}>
|
||||
<SelectValue placeholder={notebooksLoading ? '加载中…' : '选择空间'} />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
{notebooks.map((nb) => (
|
||||
<SelectItem key={nb.id} value={String(nb.id)} className="text-sm">
|
||||
{nb.name}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
)}
|
||||
|
||||
{selectedNotebookId !== null && (
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{sourcesLoading
|
||||
? '文档加载中…'
|
||||
: `选择文档(可多选;不选则搜整个笔记本${selectedDocumentIds.length > 0 ? `,已选 ${selectedDocumentIds.length} 篇` : ''})`}
|
||||
: `选择文档(可多选;不选则搜整个空间${selectedDocumentIds.length > 0 ? `,已选 ${selectedDocumentIds.length} 篇` : ''})`}
|
||||
</p>
|
||||
{!sourcesLoading && (
|
||||
<div className="max-h-36 overflow-y-auto rounded-lg border border-[#e8dedd] bg-white divide-y divide-[#f0e8e7]">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useNavigate, useSearchParams } from 'react-router-dom';
|
||||
import { ChevronsDown, Play, Plus, RotateCcw, Settings2, Square, Trash2 } from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
@@ -16,7 +16,7 @@ import { useArticleTypes } from '../../hooks/useArticleTypes';
|
||||
import { useAIWriting } from '../../contexts/AIWritingContext';
|
||||
import { useAIWritingHistory } from '../../contexts/AIWritingHistoryContext';
|
||||
import { useAIWritingSessions } from '../../hooks/useAIWritingSessions';
|
||||
import { adminCleanupAIWritingSessions } from '../../api/ai-writing-sessions';
|
||||
import { adminCleanupAIWritingSessions, getAIWritingSession } from '../../api/ai-writing-sessions';
|
||||
|
||||
/**
|
||||
* 暂停 / 继续功能开关(默认 false = 关)。
|
||||
@@ -70,10 +70,17 @@ const STATUS_LABEL: Record<string, string> = {
|
||||
|
||||
export function AIWritingPanel({
|
||||
hideAdvancedFields = false,
|
||||
initialNotebookId,
|
||||
initialDocumentIds,
|
||||
studioToken,
|
||||
}: {
|
||||
hideAdvancedFields?: boolean;
|
||||
initialNotebookId?: number;
|
||||
initialDocumentIds?: number[];
|
||||
studioToken?: string;
|
||||
} = {}) {
|
||||
const navigate = useNavigate();
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const { state, startWriting, submitIntervention, pauseWriting, resumeWriting, reset } = useAIWriting();
|
||||
const { historySession, isHistoryMode, openHistory, closeHistory } = useAIWritingHistory();
|
||||
const { sessions, loading: sessionsLoading, refresh, rename, remove } = useAIWritingSessions();
|
||||
@@ -213,6 +220,18 @@ export function AIWritingPanel({
|
||||
return () => window.removeEventListener('ai-writing:open-session', onOpenSession);
|
||||
}, []);
|
||||
|
||||
// 路径3:URL ?session=xxx 直接定位到指定会话
|
||||
useEffect(() => {
|
||||
const sessionId = searchParams.get('session');
|
||||
if (!sessionId) return;
|
||||
// 消费参数,避免刷新重复触发
|
||||
setSearchParams((prev) => { prev.delete('session'); return prev; }, { replace: true });
|
||||
getAIWritingSession(sessionId)
|
||||
.then((session) => handleSelectHistoryRef.current(session))
|
||||
.catch(() => { /* session 不存在时静默忽略 */ });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []); // 仅挂载时执行一次
|
||||
|
||||
// ── ⚠️ 临时调试:admin 一键清理所有过期会话 ────────────────────────────────
|
||||
// ai-writing-优化.md §阶段 7 删冗余代码时,本按钮 + 对应 api 一并删除。
|
||||
// 仅 admin 能成功调用(后端 _require_admin),普通用户调会拿到 403。
|
||||
@@ -339,6 +358,9 @@ export function AIWritingPanel({
|
||||
<AIWritingForm
|
||||
onSubmit={startWriting}
|
||||
hideAdvancedFields={hideAdvancedFields}
|
||||
initialNotebookId={initialNotebookId}
|
||||
initialDocumentIds={initialDocumentIds}
|
||||
studioToken={studioToken}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
||||
@@ -246,7 +246,7 @@ export function AIWritingProvider({ children }: { children: ReactNode }) {
|
||||
saveAIWritingTranscript(state.sessionId, {
|
||||
progressEvents: state.progressEvents,
|
||||
completedInterventions: state.completedInterventions,
|
||||
}).catch(() => {});
|
||||
}).catch(() => { });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [state.status, state.sessionId]);
|
||||
|
||||
@@ -256,6 +256,10 @@ export function AIWritingProvider({ children }: { children: ReactNode }) {
|
||||
const stream = streamRef.current;
|
||||
if (!stream) return;
|
||||
try {
|
||||
// 所见即所得:用户在表单选了什么 materialSource,前端就原样传过去。
|
||||
// 三个合法值:knowledge_base / skill / notebook,后端 state.py 接受任意 str。
|
||||
// 与 material_source 独立的 notebook_search_space_id / notebook_source_ids
|
||||
// 控制是否真的去笔记后端拉素材(仅当用户选了"我的空间"模式时由表单填充)。
|
||||
await stream.submit(
|
||||
{
|
||||
user_intent: request.userIntent,
|
||||
@@ -266,7 +270,9 @@ export function AIWritingProvider({ children }: { children: ReactNode }) {
|
||||
word_count_target: request.wordCountTarget,
|
||||
keyword_count: Math.max(1, Math.min(10, request.keywordCount)),
|
||||
writing_mode: request.writingMode === 'strict' ? 'strict' : 'loose',
|
||||
material_source: request.materialSource === 'skill' ? 'skill' : 'knowledge_base',
|
||||
material_source: request.materialSource,
|
||||
notebook_search_space_id: request.noteSearchSpaceId ?? null,
|
||||
notebook_source_ids: request.noteSourceIds ?? null,
|
||||
revision_count: 0,
|
||||
max_revisions: 3,
|
||||
status: 'researching',
|
||||
@@ -368,8 +374,8 @@ export function AIWritingProvider({ children }: { children: ReactNode }) {
|
||||
dispatch({ type: 'set_error', message: err instanceof Error ? err.message : String(err) });
|
||||
}
|
||||
}, [state.sessionId, state.currentPause, state.materials, state.outline,
|
||||
state.currentDraftTitle, state.currentDraftMarkdown, state.reviewResult,
|
||||
state.status]);
|
||||
state.currentDraftTitle, state.currentDraftMarkdown, state.reviewResult,
|
||||
state.status]);
|
||||
|
||||
const pauseWriting = useCallback(() => {
|
||||
void streamRef.current?.stop();
|
||||
|
||||
@@ -53,7 +53,7 @@ export function useWritingFormOptional(): WritingFormValue | null {
|
||||
return useContext(WritingFormContext);
|
||||
}
|
||||
|
||||
export function WritingFormProvider({ children }: { children: ReactNode }) {
|
||||
export function WritingFormProvider({ children, initialMaterialSource }: { children: ReactNode; initialMaterialSource?: 'knowledge_base' | 'skill' | 'notebook' }) {
|
||||
const [intent, setIntent] = useState('');
|
||||
const [showOutline, setShowOutline] = useState(false);
|
||||
const [outlineText, setOutlineText] = useState('');
|
||||
@@ -61,7 +61,7 @@ export function WritingFormProvider({ children }: { children: ReactNode }) {
|
||||
const [wordCountInput, setWordCountInput] = useState('800');
|
||||
const [audience, setAudience] = useState('普通读者');
|
||||
const [strictMode, setStrictMode] = useState(false);
|
||||
const [materialSource, setMaterialSource] = useState<'knowledge_base' | 'skill' | 'notebook'>('knowledge_base');
|
||||
const [materialSource, setMaterialSource] = useState<'knowledge_base' | 'skill' | 'notebook'>(initialMaterialSource ?? 'knowledge_base');
|
||||
const [modelName, setModelName] = useState('');
|
||||
|
||||
const applyProposal = useCallback((payload: SetupWritingApprovalPayload) => {
|
||||
|
||||
@@ -115,11 +115,17 @@ const SETUP_LAYOUT = { [SETUP_CHAT_ID]: 65, [SETUP_FORM_ID]: 35 };
|
||||
/**
|
||||
* 写作配置表单面板(写作助手回填的目标),抽出复用:idle 时在右栏,开始写作后在左栏。
|
||||
*/
|
||||
function WritingFormPanel({ hideAdvancedFields }: { hideAdvancedFields: boolean }) {
|
||||
interface NotebookProps {
|
||||
initialNotebookId?: number;
|
||||
initialDocumentIds?: number[];
|
||||
studioToken?: string;
|
||||
}
|
||||
|
||||
function WritingFormPanel({ hideAdvancedFields, initialNotebookId, initialDocumentIds, studioToken }: { hideAdvancedFields: boolean } & NotebookProps) {
|
||||
return (
|
||||
// 与原布局保持相同的 flex-1 min-h-0 包裹,确保内部 h-full 正确解析
|
||||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
<AIWritingPanel hideAdvancedFields={hideAdvancedFields} />
|
||||
<AIWritingPanel hideAdvancedFields={hideAdvancedFields} initialNotebookId={initialNotebookId} initialDocumentIds={initialDocumentIds} studioToken={studioToken} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -129,7 +135,7 @@ function WritingFormPanel({ hideAdvancedFields }: { hideAdvancedFields: boolean
|
||||
* - idle(初始问答阶段):左=「写作助手 Q&A」,右=「写作配置选项」表单;
|
||||
* - 开始写作后:回到原布局——左=写作配置/时间线面板,右=草稿预览。
|
||||
*/
|
||||
function AIWritingLayout({ hideAdvancedFields }: { hideAdvancedFields: boolean }) {
|
||||
function AIWritingLayout({ hideAdvancedFields, initialNotebookId, initialDocumentIds, studioToken }: { hideAdvancedFields: boolean } & NotebookProps) {
|
||||
const layoutRef = useRef<GroupImperativeHandle>(null);
|
||||
const { state } = useAIWriting();
|
||||
const { isHistoryMode } = useAIWritingHistory();
|
||||
@@ -162,7 +168,7 @@ function AIWritingLayout({ hideAdvancedFields }: { hideAdvancedFields: boolean }
|
||||
minSize={20}
|
||||
className="overflow-hidden flex flex-col"
|
||||
>
|
||||
<WritingFormPanel hideAdvancedFields={hideAdvancedFields} />
|
||||
<WritingFormPanel hideAdvancedFields={hideAdvancedFields} initialNotebookId={initialNotebookId} initialDocumentIds={initialDocumentIds} studioToken={studioToken} />
|
||||
</ResizablePanel>
|
||||
</ResizablePanelGroup>
|
||||
);
|
||||
@@ -202,8 +208,14 @@ function AIWritingLayout({ hideAdvancedFields }: { hideAdvancedFields: boolean }
|
||||
|
||||
export default function AIWritingPage({
|
||||
hideAdvancedFields = false,
|
||||
initialNotebookId,
|
||||
initialDocumentIds,
|
||||
studioToken,
|
||||
}: {
|
||||
hideAdvancedFields?: boolean;
|
||||
initialNotebookId?: number;
|
||||
initialDocumentIds?: number[];
|
||||
studioToken?: string;
|
||||
} = {}) {
|
||||
return (
|
||||
<CanvasThreadProvider>
|
||||
@@ -211,11 +223,11 @@ export default function AIWritingPage({
|
||||
<CanvasUIProvider>
|
||||
<AIWritingHistoryProvider>
|
||||
<AIWritingProvider>
|
||||
<WritingFormProvider>
|
||||
<WritingFormProvider initialMaterialSource={studioToken ?? initialNotebookId != null ? 'notebook' : undefined}>
|
||||
<TooltipProvider delayDuration={300}>
|
||||
<div className="h-full flex flex-col overflow-hidden bg-background">
|
||||
<div className="flex-1 min-h-0 overflow-hidden">
|
||||
<AIWritingLayout hideAdvancedFields={hideAdvancedFields} />
|
||||
<AIWritingLayout hideAdvancedFields={hideAdvancedFields} initialNotebookId={initialNotebookId} initialDocumentIds={initialDocumentIds} studioToken={studioToken} />
|
||||
</div>
|
||||
</div>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -7,7 +7,7 @@ export interface AIMaterial {
|
||||
source: string;
|
||||
publishedAt: string;
|
||||
relevanceScore: number;
|
||||
sourceType: 'internet' | 'intranet' | 'skill' | 'notebook';
|
||||
sourceType: "internet" | "intranet" | "skill" | "notebook";
|
||||
url?: string;
|
||||
recUuid?: string;
|
||||
// skill 类素材的元信息(skill_name / container_path / category / skill_full_content 等)
|
||||
@@ -22,7 +22,13 @@ export interface AIMaterial {
|
||||
// skill_result:某个技能检索返回了 N 条素材
|
||||
// stop_early:累计素材达到上限,停止后续技能
|
||||
export interface SkillSearchStep {
|
||||
type: 'list_skills' | 'match_skills' | 'select_skills' | 'invoke_skill' | 'skill_result' | 'stop_early';
|
||||
type:
|
||||
| "list_skills"
|
||||
| "match_skills"
|
||||
| "select_skills"
|
||||
| "invoke_skill"
|
||||
| "skill_result"
|
||||
| "stop_early";
|
||||
message: string;
|
||||
skillNames?: string[];
|
||||
skillName?: string;
|
||||
@@ -41,8 +47,8 @@ export interface SkillSearchStep {
|
||||
// guard 兼容,防止前端流式接收到旧大纲时崩溃。详见 normalizeKeyPoint() helper。
|
||||
|
||||
export interface KeyPoint {
|
||||
pointTitle: string; // 小节标题(渲染为 ### 三级标题)
|
||||
subPoints: string[]; // 子要点(可空;非空时在小节正文起始处以 bullet 列表呈现)
|
||||
pointTitle: string; // 小节标题(渲染为 ### 三级标题)
|
||||
subPoints: string[]; // 子要点(可空;非空时在小节正文起始处以 bullet 列表呈现)
|
||||
}
|
||||
|
||||
export interface OutlineSection {
|
||||
@@ -65,23 +71,28 @@ export interface AIOutline {
|
||||
* - 任何意外形态 → 空 KeyPoint(防止崩溃,不抛异常)
|
||||
*/
|
||||
export function normalizeKeyPoint(raw: unknown): KeyPoint {
|
||||
if (typeof raw === 'string') {
|
||||
if (typeof raw === "string") {
|
||||
return { pointTitle: raw, subPoints: [] };
|
||||
}
|
||||
if (raw && typeof raw === 'object') {
|
||||
if (raw && typeof raw === "object") {
|
||||
const r = raw as Record<string, unknown>;
|
||||
const title =
|
||||
typeof r.pointTitle === 'string' ? r.pointTitle :
|
||||
typeof r.point_title === 'string' ? r.point_title :
|
||||
typeof r.title === 'string' ? r.title :
|
||||
'';
|
||||
typeof r.pointTitle === "string"
|
||||
? r.pointTitle
|
||||
: typeof r.point_title === "string"
|
||||
? r.point_title
|
||||
: typeof r.title === "string"
|
||||
? r.title
|
||||
: "";
|
||||
const subsRaw = (r.subPoints ?? r.sub_points ?? []) as unknown;
|
||||
const subPoints: string[] = Array.isArray(subsRaw)
|
||||
? subsRaw.map((s) => (typeof s === 'string' ? s : String(s ?? ''))).filter(Boolean)
|
||||
? subsRaw
|
||||
.map((s) => (typeof s === "string" ? s : String(s ?? "")))
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
return { pointTitle: title, subPoints };
|
||||
}
|
||||
return { pointTitle: '', subPoints: [] };
|
||||
return { pointTitle: "", subPoints: [] };
|
||||
}
|
||||
|
||||
// ── 素材不足求助 ──────────────────────────────────────────────────────────────
|
||||
@@ -93,7 +104,7 @@ export interface BlockedSection {
|
||||
}
|
||||
|
||||
/** 单个被阻塞章节的处理决策 */
|
||||
export type SectionDecisionKind = 'supplement' | 'delete' | 'loose';
|
||||
export type SectionDecisionKind = "supplement" | "delete" | "loose";
|
||||
|
||||
export interface SectionDecision {
|
||||
sectionTitle: string;
|
||||
@@ -103,15 +114,15 @@ export interface SectionDecision {
|
||||
// ── 编辑审核 ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export interface ReviewIssue {
|
||||
category: 'fact' | 'logic' | 'language';
|
||||
severity: 'major' | 'minor';
|
||||
category: "fact" | "logic" | "language";
|
||||
severity: "major" | "minor";
|
||||
location: string;
|
||||
issue: string;
|
||||
suggestion: string;
|
||||
}
|
||||
|
||||
export interface ReviewResult {
|
||||
verdict: 'pass' | 'reject';
|
||||
verdict: "pass" | "reject";
|
||||
factScore: number;
|
||||
logicScore: number;
|
||||
languageScore: number;
|
||||
@@ -123,44 +134,44 @@ export interface ReviewResult {
|
||||
// ── 写作状态 ──────────────────────────────────────────────────────────────────
|
||||
|
||||
export type WritingStatus =
|
||||
| 'idle'
|
||||
| 'researching'
|
||||
| 'awaiting_material_confirm'
|
||||
| 'writing_outline'
|
||||
| 'awaiting_outline_confirm'
|
||||
| 'writing_draft'
|
||||
| 'awaiting_section_help'
|
||||
| 'awaiting_draft_confirm'
|
||||
| 'reviewing'
|
||||
| 'awaiting_review_confirm'
|
||||
| 'revising'
|
||||
| 'done'
|
||||
| 'error';
|
||||
| "idle"
|
||||
| "researching"
|
||||
| "awaiting_material_confirm"
|
||||
| "writing_outline"
|
||||
| "awaiting_outline_confirm"
|
||||
| "writing_draft"
|
||||
| "awaiting_section_help"
|
||||
| "awaiting_draft_confirm"
|
||||
| "reviewing"
|
||||
| "awaiting_review_confirm"
|
||||
| "revising"
|
||||
| "done"
|
||||
| "error";
|
||||
|
||||
// ── SSE 进度事件 ──────────────────────────────────────────────────────────────
|
||||
|
||||
export type ProgressEventType =
|
||||
| 'connected'
|
||||
| 'step_start'
|
||||
| 'step_done'
|
||||
| 'intent_chunk'
|
||||
| 'intent_ready'
|
||||
| 'research_progress'
|
||||
| 'materials_ready'
|
||||
| 'outline_chunk'
|
||||
| 'outline_ready'
|
||||
| 'draft_chunk'
|
||||
| 'draft_ready'
|
||||
| 'review_chunk'
|
||||
| 'review_item_done'
|
||||
| 'review_ready'
|
||||
| 'await_user'
|
||||
| 'resumed'
|
||||
| 'notice'
|
||||
| 'skill_search_step'
|
||||
| 'skill_select_chunk'
|
||||
| 'done'
|
||||
| 'error';
|
||||
| "connected"
|
||||
| "step_start"
|
||||
| "step_done"
|
||||
| "intent_chunk"
|
||||
| "intent_ready"
|
||||
| "research_progress"
|
||||
| "materials_ready"
|
||||
| "outline_chunk"
|
||||
| "outline_ready"
|
||||
| "draft_chunk"
|
||||
| "draft_ready"
|
||||
| "review_chunk"
|
||||
| "review_item_done"
|
||||
| "review_ready"
|
||||
| "await_user"
|
||||
| "resumed"
|
||||
| "notice"
|
||||
| "skill_search_step"
|
||||
| "skill_select_chunk"
|
||||
| "done"
|
||||
| "error";
|
||||
|
||||
export interface ProgressEvent {
|
||||
type: ProgressEventType;
|
||||
@@ -183,13 +194,18 @@ export interface ProgressEvent {
|
||||
draftTitle?: string;
|
||||
revisionCount?: number;
|
||||
// review_item_done (streaming per-category result)
|
||||
reviewItemCategory?: 'fact' | 'logic' | 'language';
|
||||
reviewItemCategory?: "fact" | "logic" | "language";
|
||||
reviewItemScore?: number;
|
||||
reviewItemIssues?: ReviewIssue[];
|
||||
// review_ready
|
||||
reviewResult?: ReviewResult;
|
||||
// await_user
|
||||
pausePoint?: 'material_confirm' | 'outline_confirm' | 'draft_confirm' | 'review_confirm' | 'section_help';
|
||||
pausePoint?:
|
||||
| "material_confirm"
|
||||
| "outline_confirm"
|
||||
| "draft_confirm"
|
||||
| "review_confirm"
|
||||
| "section_help";
|
||||
pauseContext?: Record<string, unknown>;
|
||||
// section_help:素材不足、待用户决策的章节
|
||||
blockedSections?: BlockedSection[];
|
||||
@@ -229,19 +245,19 @@ export interface AIWritingRequest {
|
||||
/** 素材检索关键词数量(1~10,默认 1) */
|
||||
keywordCount: number;
|
||||
/** 写作模式:strict=严格依据素材 / loose=可结合通用知识(默认 loose) */
|
||||
writingMode: 'strict' | 'loose';
|
||||
/** 素材来源:knowledge_base=知识库 / skill=技能检索 / notebook=我的笔记 */
|
||||
materialSource: 'knowledge_base' | 'skill' | 'notebook';
|
||||
/** 笔记来源(可选):笔记本 search_space_id */
|
||||
writingMode: "strict" | "loose";
|
||||
/** 素材来源:knowledge_base=知识库 / skill=技能检索 / notebook=我的空间 */
|
||||
materialSource: "knowledge_base" | "skill" | "notebook";
|
||||
/** 笔记来源(可选):空间 search_space_id */
|
||||
noteSearchSpaceId?: number;
|
||||
/** 笔记来源(可选):笔记本内指定文档的 document_id 列表,空数组表示检索整个笔记本 */
|
||||
/** 笔记来源(可选):空间内指定文档的 document_id 列表,空数组表示检索整个空间 */
|
||||
noteSourceIds?: number[];
|
||||
}
|
||||
|
||||
// ── 流式审核条目 ──────────────────────────────────────────────────────────────
|
||||
|
||||
export interface StreamingReviewItem {
|
||||
category: 'fact' | 'logic' | 'language';
|
||||
category: "fact" | "logic" | "language";
|
||||
// 是否已生成完毕(true 时展示结构化评分/意见,false 时展示流式打字机文本)
|
||||
isDone: boolean;
|
||||
// 流式过程文本(正文 / 思考分开)
|
||||
@@ -266,7 +282,7 @@ export interface RevisionSummary {
|
||||
export interface StreamingDraftSection {
|
||||
sectionIndex: number;
|
||||
sectionTitle: string;
|
||||
text: string; // 正文内容(非思考)
|
||||
text: string; // 正文内容(非思考)
|
||||
thinkingText: string; // 思考内容(<think>...</think> 内)
|
||||
isDone: boolean;
|
||||
}
|
||||
@@ -274,7 +290,12 @@ export interface StreamingDraftSection {
|
||||
// ── 已完成干预记录 ────────────────────────────────────────────────────────────
|
||||
|
||||
export interface CompletedIntervention {
|
||||
pausePoint: 'material_confirm' | 'outline_confirm' | 'draft_confirm' | 'review_confirm' | 'section_help';
|
||||
pausePoint:
|
||||
| "material_confirm"
|
||||
| "outline_confirm"
|
||||
| "draft_confirm"
|
||||
| "review_confirm"
|
||||
| "section_help";
|
||||
action: string;
|
||||
// material_confirm
|
||||
materials?: AIMaterial[];
|
||||
|
||||
Reference in new issue
Block a user