refactor(knowledge): separate file upload into independent entry point
将知识库的"新建文章"和"上传文件"拆分为两个独立入口: UI 改动: - 移除 Segmented 切换器,"新建文章"弹窗恢复为纯手动输入 - 新增独立的"上传文件"按钮 + 上传弹窗(Upload.Dragger) - 上传提取完成后自动打开"确认提取内容"弹窗,预填标题+内容 - 管理员编辑确认后保存,文章来源标记为 EXTRACT 后端改动: - CreateArticleDto 新增可选 source 字段 - Controller 使用 dto.source || MANUAL(不再硬编码 MANUAL) 流程: - 新建文章 → 手动输入 → source = MANUAL - 上传文件 → 提取文本 → 编辑确认 → source = EXTRACT Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
fc9e0cd17b
commit
93ed3343de
|
|
@ -24,6 +24,7 @@ export interface CreateArticleParams {
|
|||
content: string;
|
||||
category: string;
|
||||
tags?: string[];
|
||||
source?: string;
|
||||
}
|
||||
|
||||
export interface ExtractedTextResponse {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import {
|
|||
Typography,
|
||||
Drawer,
|
||||
Upload,
|
||||
Segmented,
|
||||
message,
|
||||
} from 'antd';
|
||||
import {
|
||||
|
|
@ -55,10 +54,11 @@ export function KnowledgePage() {
|
|||
const [searchText, setSearchText] = useState('');
|
||||
const [categoryFilter, setCategoryFilter] = useState<string>();
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [isUploadModalOpen, setIsUploadModalOpen] = useState(false);
|
||||
const [isDrawerOpen, setIsDrawerOpen] = useState(false);
|
||||
const [selectedArticle, setSelectedArticle] = useState<Article | null>(null);
|
||||
const [inputMode, setInputMode] = useState<'manual' | 'upload'>('manual');
|
||||
const [isExtracting, setIsExtracting] = useState(false);
|
||||
const [articleSource, setArticleSource] = useState<'MANUAL' | 'EXTRACT'>('MANUAL');
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const { data, isLoading } = useKnowledgeArticles(categoryFilter);
|
||||
|
|
@ -98,10 +98,11 @@ export function KnowledgePage() {
|
|||
}
|
||||
);
|
||||
} else {
|
||||
createMutation.mutate(values, {
|
||||
createMutation.mutate({ ...values, source: articleSource }, {
|
||||
onSuccess: () => {
|
||||
setIsModalOpen(false);
|
||||
form.resetFields();
|
||||
setArticleSource('MANUAL');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -111,19 +112,24 @@ export function KnowledgePage() {
|
|||
setIsExtracting(true);
|
||||
uploadMutation.mutate(file, {
|
||||
onSuccess: (result) => {
|
||||
// 关闭上传弹窗,打开文章编辑弹窗(预填提取内容)
|
||||
setIsUploadModalOpen(false);
|
||||
setSelectedArticle(null);
|
||||
setArticleSource('EXTRACT');
|
||||
form.resetFields();
|
||||
form.setFieldsValue({
|
||||
title: result.suggestedTitle,
|
||||
content: result.extractedText,
|
||||
});
|
||||
setIsModalOpen(true);
|
||||
const info = result.pageCount
|
||||
? `已提取 ${result.wordCount} 字(${result.pageCount} 页)`
|
||||
: `已提取 ${result.wordCount} 字`;
|
||||
? `已提取 ${result.wordCount} 字(${result.pageCount} 页),请编辑后保存`
|
||||
: `已提取 ${result.wordCount} 字,请编辑后保存`;
|
||||
message.success(info);
|
||||
setInputMode('manual');
|
||||
},
|
||||
onSettled: () => setIsExtracting(false),
|
||||
});
|
||||
return false; // prevent default upload
|
||||
return false;
|
||||
};
|
||||
|
||||
const columns = [
|
||||
|
|
@ -256,18 +262,26 @@ export function KnowledgePage() {
|
|||
options={CATEGORIES}
|
||||
/>
|
||||
</Space>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
setSelectedArticle(null);
|
||||
form.resetFields();
|
||||
setInputMode('manual');
|
||||
setIsModalOpen(true);
|
||||
}}
|
||||
>
|
||||
新建文章
|
||||
</Button>
|
||||
<Space>
|
||||
<Button
|
||||
icon={<UploadOutlined />}
|
||||
onClick={() => setIsUploadModalOpen(true)}
|
||||
>
|
||||
上传文件
|
||||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
setSelectedArticle(null);
|
||||
setArticleSource('MANUAL');
|
||||
form.resetFields();
|
||||
setIsModalOpen(true);
|
||||
}}
|
||||
>
|
||||
新建文章
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
|
|
@ -283,13 +297,48 @@ export function KnowledgePage() {
|
|||
/>
|
||||
</Card>
|
||||
|
||||
{/* 上传文件弹窗 */}
|
||||
<Modal
|
||||
title="上传文件"
|
||||
open={isUploadModalOpen}
|
||||
onCancel={() => {
|
||||
if (!isExtracting) setIsUploadModalOpen(false);
|
||||
}}
|
||||
footer={null}
|
||||
width={520}
|
||||
>
|
||||
<Upload.Dragger
|
||||
accept=".pdf,.docx,.txt,.md"
|
||||
showUploadList={false}
|
||||
beforeUpload={handleFileUpload}
|
||||
disabled={isExtracting}
|
||||
>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<InboxOutlined />
|
||||
</p>
|
||||
<p className="ant-upload-text">
|
||||
{isExtracting ? '正在提取文本...' : '点击或拖拽文件到此区域'}
|
||||
</p>
|
||||
<p className="ant-upload-hint">
|
||||
支持 PDF、Word(.docx)、TXT、Markdown 格式,最大 200MB
|
||||
</p>
|
||||
</Upload.Dragger>
|
||||
</Modal>
|
||||
|
||||
{/* 编辑/新建弹窗 */}
|
||||
<Modal
|
||||
title={selectedArticle ? '编辑文章' : '新建文章'}
|
||||
title={
|
||||
selectedArticle
|
||||
? '编辑文章'
|
||||
: articleSource === 'EXTRACT'
|
||||
? '确认提取内容'
|
||||
: '新建文章'
|
||||
}
|
||||
open={isModalOpen}
|
||||
onCancel={() => {
|
||||
setIsModalOpen(false);
|
||||
setSelectedArticle(null);
|
||||
setArticleSource('MANUAL');
|
||||
form.resetFields();
|
||||
}}
|
||||
footer={null}
|
||||
|
|
@ -316,47 +365,13 @@ export function KnowledgePage() {
|
|||
<Select mode="tags" placeholder="输入标签后回车" />
|
||||
</Form.Item>
|
||||
|
||||
{!selectedArticle && (
|
||||
<Form.Item label="内容来源">
|
||||
<Segmented
|
||||
value={inputMode}
|
||||
onChange={(val) => setInputMode(val as 'manual' | 'upload')}
|
||||
options={[
|
||||
{ label: '手动输入', value: 'manual', icon: <EditOutlined /> },
|
||||
{ label: '文件上传', value: 'upload', icon: <UploadOutlined /> },
|
||||
]}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
{inputMode === 'upload' && !selectedArticle ? (
|
||||
<Form.Item label="上传文件">
|
||||
<Upload.Dragger
|
||||
accept=".pdf,.docx,.txt,.md"
|
||||
showUploadList={false}
|
||||
beforeUpload={handleFileUpload}
|
||||
disabled={isExtracting}
|
||||
>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<InboxOutlined />
|
||||
</p>
|
||||
<p className="ant-upload-text">
|
||||
{isExtracting ? '正在提取文本...' : '点击或拖拽文件到此区域'}
|
||||
</p>
|
||||
<p className="ant-upload-hint">
|
||||
支持 PDF、Word(.docx)、TXT、Markdown 格式,最大 200MB
|
||||
</p>
|
||||
</Upload.Dragger>
|
||||
</Form.Item>
|
||||
) : (
|
||||
<Form.Item
|
||||
name="content"
|
||||
label="内容"
|
||||
rules={[{ required: true, message: '请输入内容' }]}
|
||||
>
|
||||
<TextArea rows={12} placeholder="支持Markdown格式" />
|
||||
</Form.Item>
|
||||
)}
|
||||
<Form.Item
|
||||
name="content"
|
||||
label="内容"
|
||||
rules={[{ required: true, message: '请输入内容' }]}
|
||||
>
|
||||
<TextArea rows={12} placeholder="支持Markdown格式" />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item className="mb-0 text-right">
|
||||
<Space>
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ export class KnowledgeController {
|
|||
async createArticle(@Body() dto: CreateArticleDto) {
|
||||
const article = await this.knowledgeService.createArticle({
|
||||
...dto,
|
||||
source: KnowledgeSource.MANUAL,
|
||||
source: dto.source || KnowledgeSource.MANUAL,
|
||||
});
|
||||
return {
|
||||
success: true,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ export class CreateArticleDto {
|
|||
content: string;
|
||||
category: string;
|
||||
tags?: string[];
|
||||
source?: KnowledgeSource;
|
||||
sourceUrl?: string;
|
||||
autoPublish?: boolean;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue