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
});
}} />
+
+