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 67f3f6ae..2fc03678 100644 --- a/frontend-web/src/open-canvas/components/ai-writing/AIWritingForm.tsx +++ b/frontend-web/src/open-canvas/components/ai-writing/AIWritingForm.tsx @@ -47,8 +47,8 @@ const MAX_INTENT_LENGTH = 500; const MAX_SAMPLE_TEXT_LENGTH = 120000; const MATERIAL_SOURCE_OPTIONS: Array<{ value: MaterialSource; label: string; desc: string }> = [ - { value: 'general', label: '通用检索', desc: '用素材收集专家配置的技能检索(默认:知识库检索)' }, - { value: 'knowledge_base', label: '知识库检索', desc: '直连内网知识库检索接口,不走技能编排' }, + { value: 'general', label: '通用检索', desc: '素材收集专家按配置技能检索' }, + { value: 'knowledge_base', label: '知识库检索', desc: '直连内网知识库检索接口' }, { value: 'notebook', label: '我的空间', desc: '从空间中检索素材' }, ]; @@ -162,7 +162,7 @@ export function AIWritingForm({ function handleMaterialSourceChange(val: MaterialSource) { setMaterialSource(val); - // studioToken 模式下空间和文档已锁定,切换素材来源时不清空 + // studioToken 模式下空间和文档已锁定,切换素材来源时不清空。 if (val !== 'notebook' && !studioToken) { setSelectedNotebookId(null); setSelectedDocumentIds([]); @@ -222,12 +222,10 @@ export function AIWritingForm({ writingMode: skipResearch ? 'loose' : hideAdvancedFields ? 'loose' : strictMode ? 'strict' : 'loose', addReferences, skipResearch, - // 跳过素材收集时检索类型无意义,归一为 general materialSource: skipResearch ? 'general' : materialSource, modelName: selectedModel?.name || modelName || undefined, - // 只有选了"我的空间"且未跳过素材收集才传笔记参数 - noteSearchSpaceId: !skipResearch && materialSource === 'notebook' ? (selectedNotebookId ?? undefined) : undefined, - noteSourceIds: !skipResearch && materialSource === 'notebook' && selectedNotebookId != null ? selectedDocumentIds : undefined, + noteSearchSpaceId: !skipResearch ? (selectedNotebookId ?? undefined) : undefined, + noteSourceIds: !skipResearch && selectedNotebookId != null ? selectedDocumentIds : undefined, }); } @@ -454,82 +452,80 @@ export function AIWritingForm({ {!skipResearch && ( -
- - +
+ + - {/* 选了"我的空间"才展示空间 / 文档选择器 */} - {materialSource === 'notebook' && ( -
- {/* studioToken 模式:空间已锁定,不展示选择器 */} - {!studioToken && ( - - )} + {materialSource === 'notebook' && ( +
+ {!studioToken && ( + + )} - {selectedNotebookId !== null && ( -
-

- {sourcesLoading - ? '文档加载中…' - : `选择文档(可多选;不选则搜整个空间${selectedDocumentIds.length > 0 ? `,已选 ${selectedDocumentIds.length} 篇` : ''})`} -

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

+ {sourcesLoading + ? '文档加载中…' + : `选择文档(可多选;不选则搜整个空间${selectedDocumentIds.length > 0 ? `,已选 ${selectedDocumentIds.length} 篇` : ''})`} +

+ {!sourcesLoading && ( +
+ {sources.filter((s) => s.document?.id != null).map((s) => { + const docId = s.document!.id; + return ( + + ); + })} +
+ )} +
+ )} +
+ )} +
)}
diff --git a/frontend-web/src/open-canvas/components/ai-writing/AIWritingPanel.tsx b/frontend-web/src/open-canvas/components/ai-writing/AIWritingPanel.tsx index b3958a15..ccef213e 100644 --- a/frontend-web/src/open-canvas/components/ai-writing/AIWritingPanel.tsx +++ b/frontend-web/src/open-canvas/components/ai-writing/AIWritingPanel.tsx @@ -605,6 +605,8 @@ export function AIWritingPanel({ wordCountTarget={state.request?.wordCountTarget} revisionCount={state.revisionCount} skillSearchSteps={state.skillSearchSteps} + materialSource={state.request?.materialSource ?? 'general'} + canUseNotebookSearch={state.request?.noteSearchSpaceId != null} skillSearchInProgress={state.status === 'researching' && state.skillSearchSteps.length > 0} onSubmit={submitIntervention} disabled={false} diff --git a/frontend-web/src/open-canvas/components/ai-writing/InterventionCard.tsx b/frontend-web/src/open-canvas/components/ai-writing/InterventionCard.tsx index 696cdf5a..d1c3f8cc 100644 --- a/frontend-web/src/open-canvas/components/ai-writing/InterventionCard.tsx +++ b/frontend-web/src/open-canvas/components/ai-writing/InterventionCard.tsx @@ -5,6 +5,13 @@ import { Trash2, BookOpen, } from 'lucide-react'; import { Button } from '@/components/ui/button'; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from '@/components/ui/select'; import { cn } from '@/lib/utils'; import type { AIMaterial, AIOutline, ReviewResult, ReviewIssue, @@ -30,6 +37,8 @@ interface InterventionCardProps { wordCountTarget?: number; revisionCount: number; skillSearchSteps?: SkillSearchStep[]; + materialSource?: MaterialSource; + canUseNotebookSearch?: boolean; /** 是否正在检索中(researching/awaiting 切换之间,前端用于流式展示步骤进行中) */ skillSearchInProgress?: boolean; onSubmit: (payload: UserInterventionPayload) => void; @@ -38,7 +47,8 @@ interface InterventionCardProps { export function InterventionCard({ pauseEvent, materials, outline, reviewResult, - draftTitle, draftWordCount, wordCountTarget, revisionCount, skillSearchSteps, skillSearchInProgress, onSubmit, disabled, + draftTitle, draftWordCount, wordCountTarget, revisionCount, skillSearchSteps, + materialSource, canUseNotebookSearch, skillSearchInProgress, onSubmit, disabled, }: InterventionCardProps) { const pausePoint = pauseEvent.pausePoint; @@ -47,6 +57,8 @@ export function InterventionCard({ void; disabled?: boolean; } -function MaterialConfirmCard({ materials, skillSearchSteps, skillSearchInProgress, onSubmit, disabled }: MaterialConfirmCardProps) { +type MaterialSource = NonNullable; + +const MATERIAL_SOURCE_OPTIONS: Array<{ value: MaterialSource; label: string; desc: string }> = [ + { value: 'general', label: '通用检索', desc: '素材收集专家按配置技能检索' }, + { value: 'knowledge_base', label: '知识库检索', desc: '直连内网知识库接口' }, + { value: 'notebook', label: '我的空间', desc: '检索当前笔记空间' }, +]; + +function MaterialSourceSwitcher({ + value, + onChange, + options, + disabled, +}: { + value: MaterialSource; + onChange: (value: MaterialSource) => void; + options: Array<{ value: MaterialSource; label: string; desc: string }>; + disabled?: boolean; +}) { + const current = options.find((opt) => opt.value === value) ?? options[0]; + return ( +
+ 检索方式 + + + {current.desc} + +
+ ); +} + +function MaterialConfirmCard({ + materials, + skillSearchSteps, + initialMaterialSource, + canUseNotebookSearch, + skillSearchInProgress, + onSubmit, + disabled, +}: MaterialConfirmCardProps) { const [selectedIds, setSelectedIds] = useState>( () => new Set(materials.map((m) => m.id)), ); const [userQuery, setUserQuery] = useState(''); + const [materialSource, setMaterialSource] = useState( + initialMaterialSource === 'notebook' && !canUseNotebookSearch ? 'general' : initialMaterialSource, + ); const [submitting, setSubmitting] = useState(false); + const sourceOptions = MATERIAL_SOURCE_OPTIONS.filter((opt) => opt.value !== 'notebook' || canUseNotebookSearch); async function handleConfirm() { setSubmitting(true); @@ -148,7 +216,12 @@ function MaterialConfirmCard({ materials, skillSearchSteps, skillSearchInProgres setSubmitting(true); // 带输入 = 后端在素材收集专家配置的技能中匹配检索、新素材追加到列表; // 无输入 = 重新生成检索词、覆盖式重检 - await onSubmit({ action: 're_search', approvedMaterialIds: [], userQuery: userQuery.trim() || undefined }); + await onSubmit({ + action: 're_search', + approvedMaterialIds: [], + userQuery: userQuery.trim() || undefined, + materialSource, + }); setSubmitting(false); } @@ -163,7 +236,7 @@ function MaterialConfirmCard({ materials, skillSearchSteps, skillSearchInProgres

未检索到任何素材

- 素材收集专家本次没有检索到相关数据。你可以在输入框描述想检索的内容(自动匹配已配置的技能),或在无素材的情况下直接继续写作。 + 素材收集专家本次没有检索到相关数据。你可以切换检索方式并描述想检索的内容,或在无素材的情况下直接继续写作。

@@ -172,6 +245,13 @@ function MaterialConfirmCard({ materials, skillSearchSteps, skillSearchInProgres )} + + 需要您确认素材,才能继续

已搜集到 {materials.length} 条素材(默认全选)。您可以取消勾选不相关的素材, - 或在下方描述想补充检索的内容——系统会匹配素材收集专家配置的技能检索,并把新素材追加进列表。 + 或在下方切换检索方式并描述想补充检索的内容,系统会把新素材追加进列表。

@@ -226,6 +306,13 @@ function MaterialConfirmCard({ materials, skillSearchSteps, skillSearchInProgres }); }} /> + +