temp:临时备份,完成一半知识库管理模块
This commit is contained in:
@@ -0,0 +1,367 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
Input,
|
||||
Button,
|
||||
InputNumber,
|
||||
Checkbox,
|
||||
Select,
|
||||
Card,
|
||||
Empty,
|
||||
Spin,
|
||||
message,
|
||||
Divider,
|
||||
Tooltip,
|
||||
} from 'antd';
|
||||
import {
|
||||
QuestionCircleOutlined,
|
||||
ReloadOutlined,
|
||||
EyeOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import type { Document } from '~/api/dify-dataset/type/documentTypes';
|
||||
import type { Segment } from '~/api/dify-dataset/type';
|
||||
import { fetchSegments } from '~/api/dify-dataset/api/segmentApi';
|
||||
import { updateDocumentWithSettings } from '~/api/dify-dataset/api/documentApi';
|
||||
|
||||
interface DocumentDetailProps {
|
||||
datasetId: string;
|
||||
document: Document | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分段设置配置
|
||||
* 注意:Dify API 支持的参数有限
|
||||
* - separator: ✅ 支持
|
||||
* - maxTokens: ✅ 支持
|
||||
* - removeExtraSpaces: ✅ 支持
|
||||
* - removeUrlsEmails: ✅ 支持
|
||||
* - useQASegment: ⚠️ 需要 doc_form: "qa_model"
|
||||
*/
|
||||
interface SegmentationSettings {
|
||||
separator: string;
|
||||
maxTokens: number;
|
||||
removeExtraSpaces: boolean;
|
||||
removeUrlsEmails: boolean;
|
||||
useQASegment: boolean;
|
||||
qaLanguage: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认分段设置
|
||||
*/
|
||||
const DEFAULT_SETTINGS: SegmentationSettings = {
|
||||
separator: '\\n\\n',
|
||||
maxTokens: 500,
|
||||
removeExtraSpaces: true,
|
||||
removeUrlsEmails: false,
|
||||
useQASegment: false,
|
||||
qaLanguage: 'Chinese',
|
||||
};
|
||||
|
||||
/**
|
||||
* 文档详情组件
|
||||
* 显示文档的分段设置,支持修改并重新处理
|
||||
*/
|
||||
export default function DocumentDetail({
|
||||
datasetId,
|
||||
document,
|
||||
}: DocumentDetailProps) {
|
||||
// 分段设置状态
|
||||
const [settings, setSettings] = useState<SegmentationSettings>(DEFAULT_SETTINGS);
|
||||
|
||||
// 预览状态
|
||||
const [previewSegments, setPreviewSegments] = useState<Segment[]>([]);
|
||||
const [previewLoading, setPreviewLoading] = useState(false);
|
||||
const [showPreview, setShowPreview] = useState(false);
|
||||
|
||||
// 保存状态
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
// 当文档变化时重置设置
|
||||
useEffect(() => {
|
||||
if (document) {
|
||||
// 可以从文档中读取已有的设置,这里使用默认值
|
||||
setSettings(DEFAULT_SETTINGS);
|
||||
setPreviewSegments([]);
|
||||
setShowPreview(false);
|
||||
}
|
||||
}, [document?.id]);
|
||||
|
||||
/**
|
||||
* 更新设置
|
||||
*/
|
||||
const updateSettings = (key: keyof SegmentationSettings, value: any) => {
|
||||
setSettings(prev => ({ ...prev, [key]: value }));
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置设置
|
||||
*/
|
||||
const handleReset = () => {
|
||||
setSettings(DEFAULT_SETTINGS);
|
||||
setPreviewSegments([]);
|
||||
setShowPreview(false);
|
||||
};
|
||||
|
||||
/**
|
||||
* 预览分段
|
||||
*/
|
||||
const handlePreview = async () => {
|
||||
if (!document) return;
|
||||
|
||||
setPreviewLoading(true);
|
||||
setShowPreview(true);
|
||||
try {
|
||||
// 获取当前文档的分段作为预览
|
||||
const response = await fetchSegments(datasetId, document.id, 1, 50);
|
||||
setPreviewSegments(response.data || []);
|
||||
if (response.data?.length === 0) {
|
||||
message.info('该文档暂无分段数据');
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('预览分段失败:', err);
|
||||
message.error(err.message || '预览失败');
|
||||
} finally {
|
||||
setPreviewLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 保存并处理
|
||||
*/
|
||||
const handleSaveAndProcess = async () => {
|
||||
if (!document) return;
|
||||
|
||||
setSaving(true);
|
||||
try {
|
||||
await updateDocumentWithSettings(datasetId, document.id, {
|
||||
indexing_technique: 'high_quality',
|
||||
process_rule: {
|
||||
mode: 'custom',
|
||||
rules: {
|
||||
pre_processing_rules: [
|
||||
{ id: 'remove_extra_spaces', enabled: settings.removeExtraSpaces },
|
||||
{ id: 'remove_urls_emails', enabled: settings.removeUrlsEmails },
|
||||
],
|
||||
segmentation: {
|
||||
separator: settings.separator.replace(/\\n/g, '\n'),
|
||||
max_tokens: settings.maxTokens,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
message.success('设置已保存,文档正在重新处理...');
|
||||
} catch (err: any) {
|
||||
console.error('保存设置失败:', err);
|
||||
message.error(err.message || '保存失败');
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (!document) {
|
||||
return (
|
||||
<div className="document-detail-empty">
|
||||
<Empty description="请选择一个文档" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="document-detail-page">
|
||||
<div className="document-detail-content">
|
||||
{/* 左侧设置区域 */}
|
||||
<div className="settings-panel">
|
||||
{/* 分段设置 */}
|
||||
<div className="settings-section">
|
||||
<h3 className="section-title">分段设置</h3>
|
||||
|
||||
{/* 分块模式 */}
|
||||
<div className="setting-item mode-selector">
|
||||
<div className="mode-option active">
|
||||
<div className="mode-icon">
|
||||
<i className="ri-text-spacing"></i>
|
||||
</div>
|
||||
<div className="mode-info">
|
||||
<span className="mode-name">通用</span>
|
||||
<span className="mode-desc">通用文本分块模式,检索和召回的块是相同的</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 分段标识符 */}
|
||||
<div className="setting-item">
|
||||
<label className="setting-label">
|
||||
分段标识符
|
||||
<Tooltip title="系统会在遇到指定分隔符时自动分段,默认值为 \n\n(按段落分段)">
|
||||
<QuestionCircleOutlined className="help-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<Input
|
||||
value={settings.separator}
|
||||
onChange={(e) => updateSettings('separator', e.target.value)}
|
||||
placeholder="\n\n"
|
||||
className="setting-input"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* 分段最大长度 */}
|
||||
<div className="setting-item">
|
||||
<label className="setting-label">
|
||||
分段最大长度
|
||||
<Tooltip title="指定每个分段允许的最大字符数,超过此限制系统会强制分段">
|
||||
<QuestionCircleOutlined className="help-icon" />
|
||||
</Tooltip>
|
||||
</label>
|
||||
<div className="setting-input-with-suffix">
|
||||
<InputNumber
|
||||
value={settings.maxTokens}
|
||||
onChange={(value) => updateSettings('maxTokens', value || 500)}
|
||||
min={100}
|
||||
max={4000}
|
||||
className="setting-input-number"
|
||||
/>
|
||||
<span className="input-suffix">characters</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* 文本预处理规则 */}
|
||||
<div className="settings-section">
|
||||
<h3 className="section-title">文本预处理规则</h3>
|
||||
|
||||
<div className="checkbox-group">
|
||||
<Checkbox
|
||||
checked={settings.removeExtraSpaces}
|
||||
onChange={(e) => updateSettings('removeExtraSpaces', e.target.checked)}
|
||||
>
|
||||
替换掉连续的空格、换行符和制表符
|
||||
</Checkbox>
|
||||
|
||||
<Checkbox
|
||||
checked={settings.removeUrlsEmails}
|
||||
onChange={(e) => updateSettings('removeUrlsEmails', e.target.checked)}
|
||||
>
|
||||
删除所有 URL 和电子邮件地址
|
||||
</Checkbox>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* Q&A 分段 */}
|
||||
<div className="settings-section">
|
||||
<div className="qa-segment-row">
|
||||
<Checkbox
|
||||
checked={settings.useQASegment}
|
||||
onChange={(e) => updateSettings('useQASegment', e.target.checked)}
|
||||
>
|
||||
使用 Q&A 分段,语言
|
||||
</Checkbox>
|
||||
<Select
|
||||
value={settings.qaLanguage}
|
||||
onChange={(value) => updateSettings('qaLanguage', value)}
|
||||
disabled={!settings.useQASegment}
|
||||
style={{ width: 120 }}
|
||||
options={[
|
||||
{ value: 'Chinese', label: 'Chinese' },
|
||||
{ value: 'English', label: 'English' },
|
||||
{ value: 'Japanese', label: 'Japanese' },
|
||||
{ value: 'Korean', label: 'Korean' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 操作按钮 */}
|
||||
<div className="settings-actions">
|
||||
<Button
|
||||
icon={<EyeOutlined />}
|
||||
onClick={handlePreview}
|
||||
loading={previewLoading}
|
||||
>
|
||||
预览块
|
||||
</Button>
|
||||
<Button
|
||||
icon={<ReloadOutlined />}
|
||||
onClick={handleReset}
|
||||
>
|
||||
重置
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
{/* 保存并处理按钮 */}
|
||||
<div className="save-actions">
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleSaveAndProcess}
|
||||
loading={saving}
|
||||
block
|
||||
>
|
||||
保存并处理
|
||||
</Button>
|
||||
<Button block style={{ marginTop: 8 }}>
|
||||
取消
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右侧预览区域 */}
|
||||
<div className="preview-panel">
|
||||
<Card
|
||||
title={
|
||||
<div className="preview-header">
|
||||
<span>预览</span>
|
||||
<Select
|
||||
value={document.name}
|
||||
style={{ width: 200 }}
|
||||
disabled
|
||||
options={[{ value: document.name, label: document.name }]}
|
||||
/>
|
||||
<span className="segment-count">
|
||||
{showPreview ? `${previewSegments.length} 段块` : '0 段块'}
|
||||
</span>
|
||||
</div>
|
||||
}
|
||||
className="preview-card"
|
||||
>
|
||||
{previewLoading ? (
|
||||
<div className="preview-loading">
|
||||
<Spin size="large" />
|
||||
<div className="loading-text">加载中...</div>
|
||||
</div>
|
||||
) : !showPreview ? (
|
||||
<div className="preview-empty">
|
||||
<div className="empty-icon">
|
||||
<EyeOutlined />
|
||||
</div>
|
||||
<p>点击左侧的"预览块"按钮来预览</p>
|
||||
</div>
|
||||
) : previewSegments.length === 0 ? (
|
||||
<Empty description="暂无分段数据" />
|
||||
) : (
|
||||
<div className="preview-segments">
|
||||
{previewSegments.map((segment, index) => (
|
||||
<div key={segment.id} className="segment-item">
|
||||
<div className="segment-header">
|
||||
<span className="segment-index">#{index + 1}</span>
|
||||
<span className="segment-chars">
|
||||
{segment.word_count} 字符
|
||||
</span>
|
||||
</div>
|
||||
<div className="segment-content">
|
||||
{segment.content}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user