From b749c78efa9a32e4b1431cd21d6ac5e45674540f Mon Sep 17 00:00:00 2001 From: xuhd Date: Mon, 1 Jun 2026 11:47:37 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20AI=E5=86=99=E4=BD=9C=E9=9B=86=E6=88=90?= =?UTF-8?q?=E7=AC=94=E8=AE=B0=E6=9D=A5=E6=BA=90=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=A4=9A=E6=96=87=E6=A1=A3=E5=A4=9A=E9=80=89=E6=A3=80=E7=B4=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 前端 AIWritingForm 新增笔记来源选择器(笔记本单选 + 文档多选 checkbox) - AIWritingRequest 类型新增 noteSearchSpaceId / noteSourceIds 字段 - AIMaterial.sourceType 新增 notebook 类型 - useAIWritingStream startWriting 时自动获取 notebook_token 并携带笔记参数 - 后端 AIWritingState 新增 notebook_search_space_id / notebook_source_ids 字段 - researcher 节点新增 _search_notebook,解析 API 返回的 documents/chunks, 每个 chunk 作为独立素材(source_type=notebook)混入素材包 Co-Authored-By: Claude Sonnet 4.6 --- frontend-web/package.json | 1 + .../components/ai-writing/AIWritingForm.tsx | 104 +++++++++++++++++- .../open-canvas/hooks/useAIWritingStream.ts | 24 +++- .../src/open-canvas/types/ai-writing.ts | 6 +- .../agents/ai_writing/nodes/researcher.py | 91 ++++++++++++++- .../deerflow/agents/ai_writing/state.py | 3 + 6 files changed, 224 insertions(+), 5 deletions(-) diff --git a/frontend-web/package.json b/frontend-web/package.json index 1613e744..3b00ed53 100644 --- a/frontend-web/package.json +++ b/frontend-web/package.json @@ -57,6 +57,7 @@ "cmdk": "^1.1.1", "codemirror": "^6.0.2", "date-fns": "^4.1.0", + "dayjs": "^1.11.21", "docx": "^9.5.1", "dotenv": "^17.2.3", "embla-carousel-react": "^8.6.0", diff --git a/frontend-web/src/open-canvas/components/ai-writing/AIWritingForm.tsx b/frontend-web/src/open-canvas/components/ai-writing/AIWritingForm.tsx index 7f9db889..197b2e94 100644 --- a/frontend-web/src/open-canvas/components/ai-writing/AIWritingForm.tsx +++ b/frontend-web/src/open-canvas/components/ai-writing/AIWritingForm.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState } from 'react'; -import { PenLine } from 'lucide-react'; +import { BookOpen, PenLine, X } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Textarea } from '@/components/ui/textarea'; @@ -11,6 +11,7 @@ import { SelectValue, } from '@/components/ui/select'; import { useModels } from '@/core/models/hooks'; +import { useStudioNotebooks, useNotebookSources } from '@/core/studio/hooks'; import type { AIWritingRequest } from '../../types/ai-writing'; import { useArticleTypes } from '../../hooks/useArticleTypes'; @@ -46,7 +47,13 @@ export function AIWritingForm({ const [wordCountInput, setWordCountInput] = useState('800'); const [strictMode, setStrictMode] = useState(false); const [modelName, setModelName] = useState(''); + // 笔记来源:选中的 search_space_id 和多个 document_id + const [selectedNotebookId, setSelectedNotebookId] = useState(null); + const [selectedDocumentIds, setSelectedDocumentIds] = useState([]); + const { models, isLoading: modelsLoading } = useModels(); + const { notebooks, isLoading: notebooksLoading } = useStudioNotebooks(); + const { sources, isLoading: sourcesLoading } = useNotebookSources(selectedNotebookId); const { articleTypes, loading: typesLoading, refresh: refreshTypes } = useArticleTypes(); @@ -98,6 +105,22 @@ export function AIWritingForm({ } } + function handleNotebookChange(val: string) { + setSelectedNotebookId(val ? Number(val) : null); + setSelectedDocumentIds([]); + } + + function clearNoteSelection() { + setSelectedNotebookId(null); + setSelectedDocumentIds([]); + } + + function toggleDocument(docId: number) { + setSelectedDocumentIds((prev) => + prev.includes(docId) ? prev.filter((id) => id !== docId) : [...prev, docId], + ); + } + function handleSubmit(e: React.FormEvent) { e.preventDefault(); if (!intent.trim()) return; @@ -112,6 +135,9 @@ export function AIWritingForm({ keywordCount: DEFAULT_KEYWORD_COUNT, writingMode: hideAdvancedFields ? 'loose' : strictMode ? 'strict' : 'loose', modelName: selectedModel?.name || modelName || undefined, + noteSearchSpaceId: selectedNotebookId ?? undefined, + // 有勾选文档则传 document_id 列表,否则传空数组(搜整个笔记本) + noteSourceIds: selectedNotebookId != null ? selectedDocumentIds : undefined, }); } @@ -206,6 +232,82 @@ export function AIWritingForm({ + {/* 笔记来源(可选) */} +
+
+
+ + 笔记来源(可选) +
+ {selectedNotebookId !== null && ( + + )} +
+ + + + {selectedNotebookId !== null && ( +
+

+ {sourcesLoading + ? '文档加载中…' + : sources.filter((s) => s.document?.id != null).length === 0 + ? '该笔记本暂无文档' + : `选择文档(可多选;不选则搜索整个笔记本${selectedDocumentIds.length > 0 ? `,已选 ${selectedDocumentIds.length} 篇` : ''})`} +

+ {!sourcesLoading && ( +
+ {sources + .filter((s) => s.document?.id != null) + .map((s) => { + const docId = s.document!.id; + const checked = selectedDocumentIds.includes(docId); + return ( + + ); + })} +
+ )} +
+ )} +
+