feat: 素材来源新增「我的笔记」选项
- materialSource 枚举新增 notebook 值 - 选择「我的笔记」时展开笔记本 + 文档多选选择器 - 切换其他来源自动清空笔记选择 - 只有 notebook 模式才向后端传笔记参数 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
e4983be9d7
commit
7f0a0d92a3
2 files changed
+74
-96
No files matched your search
@@ -1,5 +1,5 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { BookOpen, PenLine, X } from 'lucide-react';
|
||||
import { PenLine } from 'lucide-react';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
@@ -30,13 +30,14 @@ const INPUT_CLASS =
|
||||
|
||||
const DEFAULT_KEYWORD_COUNT = 1;
|
||||
const DEFAULT_AUDIENCE = '普通读者';
|
||||
const DEFAULT_MATERIAL_SOURCE: 'knowledge_base' | 'skill' = 'knowledge_base';
|
||||
type MaterialSource = 'knowledge_base' | 'skill' | 'notebook';
|
||||
const DEFAULT_MATERIAL_SOURCE: MaterialSource = 'knowledge_base';
|
||||
const MAX_INTENT_LENGTH = 500;
|
||||
|
||||
// 素材来源选项:知识库=素材专家默认 web/intranet 检索;技能检索=用技能编排去检索
|
||||
const MATERIAL_SOURCE_OPTIONS: Array<{ value: 'knowledge_base' | 'skill'; label: string }> = [
|
||||
{ value: 'knowledge_base', label: '知识库' },
|
||||
{ value: 'skill', label: '技能检索' },
|
||||
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: '从笔记本中检索素材' },
|
||||
];
|
||||
|
||||
const FIELD_LABEL_CLASS = 'text-[16px] font-semibold leading-none text-[#222]';
|
||||
@@ -53,8 +54,7 @@ export function AIWritingForm({
|
||||
const [audience, setAudience] = useState('普通读者');
|
||||
const [wordCountInput, setWordCountInput] = useState('800');
|
||||
const [strictMode, setStrictMode] = useState(false);
|
||||
const [materialSource, setMaterialSource] =
|
||||
useState<'knowledge_base' | 'skill'>(DEFAULT_MATERIAL_SOURCE);
|
||||
const [materialSource, setMaterialSource] = useState<MaterialSource>(DEFAULT_MATERIAL_SOURCE);
|
||||
const [modelName, setModelName] = useState('');
|
||||
// 笔记来源:选中的 search_space_id 和多个 document_id
|
||||
const [selectedNotebookId, setSelectedNotebookId] = useState<number | null>(null);
|
||||
@@ -119,9 +119,12 @@ export function AIWritingForm({
|
||||
setSelectedDocumentIds([]);
|
||||
}
|
||||
|
||||
function clearNoteSelection() {
|
||||
setSelectedNotebookId(null);
|
||||
setSelectedDocumentIds([]);
|
||||
function handleMaterialSourceChange(val: MaterialSource) {
|
||||
setMaterialSource(val);
|
||||
if (val !== 'notebook') {
|
||||
setSelectedNotebookId(null);
|
||||
setSelectedDocumentIds([]);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDocument(docId: number) {
|
||||
@@ -145,9 +148,9 @@ export function AIWritingForm({
|
||||
writingMode: hideAdvancedFields ? 'loose' : strictMode ? 'strict' : 'loose',
|
||||
materialSource,
|
||||
modelName: selectedModel?.name || modelName || undefined,
|
||||
noteSearchSpaceId: selectedNotebookId ?? undefined,
|
||||
// 有勾选文档则传 document_id 列表,否则传空数组(搜整个笔记本)
|
||||
noteSourceIds: selectedNotebookId != null ? selectedDocumentIds : undefined,
|
||||
// 只有选了"我的笔记"才传笔记参数
|
||||
noteSearchSpaceId: materialSource === 'notebook' ? (selectedNotebookId ?? undefined) : undefined,
|
||||
noteSourceIds: materialSource === 'notebook' && selectedNotebookId != null ? selectedDocumentIds : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -218,7 +221,7 @@ export function AIWritingForm({
|
||||
<label className={FIELD_LABEL_CLASS}>素材来源</label>
|
||||
<Select
|
||||
value={materialSource}
|
||||
onValueChange={(v) => setMaterialSource(v as 'knowledge_base' | 'skill')}
|
||||
onValueChange={(v) => handleMaterialSourceChange(v as MaterialSource)}
|
||||
disabled={disabled}
|
||||
>
|
||||
<SelectTrigger className={FIELD_TRIGGER_CLASS}>
|
||||
@@ -228,13 +231,64 @@ export function AIWritingForm({
|
||||
{MATERIAL_SOURCE_OPTIONS.map((opt) => (
|
||||
<SelectItem key={opt.value} value={opt.value} className="text-sm">
|
||||
{opt.label}
|
||||
<span className="ml-1 text-xs text-muted-foreground">— {opt.desc}</span>
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<p className="text-xs leading-relaxed text-muted-foreground/80">
|
||||
知识库:素材专家默认检索方式;技能检索:用已启用的技能去编排检索。
|
||||
</p>
|
||||
|
||||
{/* 选了"我的笔记"才展示笔记本 / 文档选择器 */}
|
||||
{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>
|
||||
|
||||
{selectedNotebookId !== null && (
|
||||
<div className="space-y-1">
|
||||
<p className="text-xs text-muted-foreground">
|
||||
{sourcesLoading
|
||||
? '文档加载中…'
|
||||
: `选择文档(可多选;不选则搜整个笔记本${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]">
|
||||
{sources.filter((s) => s.document?.id != null).map((s) => {
|
||||
const docId = s.document!.id;
|
||||
return (
|
||||
<label key={s.id} className="flex items-center gap-2 px-3 py-2 cursor-pointer hover:bg-[#f9f5f5] select-none">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={selectedDocumentIds.includes(docId)}
|
||||
onChange={() => toggleDocument(docId)}
|
||||
disabled={disabled}
|
||||
className="size-3.5 shrink-0 accent-primary"
|
||||
/>
|
||||
<span className="text-xs text-foreground truncate">
|
||||
{s.document?.title ?? `文档 ${docId}`}
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 space-y-2">
|
||||
@@ -265,82 +319,6 @@ export function AIWritingForm({
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
{/* 笔记来源(可选) */}
|
||||
<div className="mt-4 rounded-xl border border-[#e8dedd] bg-[#f9f5f5] px-3 py-3 space-y-2">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-1.5">
|
||||
<BookOpen className="size-3.5 text-[#8b6f6f]" />
|
||||
<span className="text-sm font-semibold text-foreground">笔记来源(可选)</span>
|
||||
</div>
|
||||
{selectedNotebookId !== null && (
|
||||
<button
|
||||
type="button"
|
||||
onClick={clearNoteSelection}
|
||||
className="flex items-center gap-0.5 text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<X className="size-3" />
|
||||
清除
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<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
|
||||
? '文档加载中…'
|
||||
: sources.filter((s) => s.document?.id != null).length === 0
|
||||
? '该笔记本暂无文档'
|
||||
: `选择文档(可多选;不选则搜索整个笔记本${selectedDocumentIds.length > 0 ? `,已选 ${selectedDocumentIds.length} 篇` : ''})`}
|
||||
</p>
|
||||
{!sourcesLoading && (
|
||||
<div className="max-h-40 overflow-y-auto rounded-lg border border-[#e8dedd] bg-white divide-y divide-[#f0e8e7]">
|
||||
{sources
|
||||
.filter((s) => s.document?.id != null)
|
||||
.map((s) => {
|
||||
const docId = s.document!.id;
|
||||
const checked = selectedDocumentIds.includes(docId);
|
||||
return (
|
||||
<label
|
||||
key={s.id}
|
||||
className="flex items-center gap-2 px-3 py-2 cursor-pointer hover:bg-[#f9f5f5] select-none"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={checked}
|
||||
onChange={() => toggleDocument(docId)}
|
||||
disabled={disabled}
|
||||
className="size-3.5 shrink-0 accent-primary"
|
||||
/>
|
||||
<span className="text-xs text-foreground truncate">
|
||||
{s.document?.title ?? `文档 ${docId}`}
|
||||
</span>
|
||||
</label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
disabled={disabled || !intent.trim()}
|
||||
|
||||
@@ -228,8 +228,8 @@ export interface AIWritingRequest {
|
||||
keywordCount: number;
|
||||
/** 写作模式:strict=严格依据素材 / loose=可结合通用知识(默认 loose) */
|
||||
writingMode: 'strict' | 'loose';
|
||||
/** 素材来源:knowledge_base=知识库(默认检索)/ skill=技能检索(默认 knowledge_base) */
|
||||
materialSource: 'knowledge_base' | 'skill';
|
||||
/** 素材来源:knowledge_base=知识库 / skill=技能检索 / notebook=我的笔记 */
|
||||
materialSource: 'knowledge_base' | 'skill' | 'notebook';
|
||||
/** 笔记来源(可选):笔记本 search_space_id */
|
||||
noteSearchSpaceId?: number;
|
||||
/** 笔记来源(可选):笔记本内指定文档的 document_id 列表,空数组表示检索整个笔记本 */
|
||||
|
||||
Reference in new issue
Block a user