Merge branch 'PingChuan' into shiy-login

This commit is contained in:
2025-12-09 21:05:01 +08:00
8 changed files with 778 additions and 802 deletions
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -407,7 +407,7 @@ const ChatSidebar = forwardRef<ChatSidebarRef, ChatSidebarProps>(({
confirmLoading={renameLoading}
okText="确定"
cancelText="取消"
destroyOnClose
destroyOnHidden
>
<div className="py-4">
<Input
@@ -437,7 +437,7 @@ const ChatSidebar = forwardRef<ChatSidebarRef, ChatSidebarProps>(({
okText="删除"
cancelText="取消"
okType="danger"
destroyOnClose
destroyOnHidden
>
<div className="py-4">
<p> <strong>"{deletingConversation?.name}"</strong> </p>
@@ -577,7 +577,7 @@ export default function AreaDatasetConfig() {
),
value: ds.id,
}))}
dropdownStyle={{ maxHeight: '300px' }}
styles={{ popup: { root: { maxHeight: '300px' } } }}
/>
</Form.Item>
@@ -594,7 +594,7 @@ export default function AreaDatasetConfig() {
</Form.Item>
{/* 知识库描述 */}
<Form.Item
{/* <Form.Item
name="dataset_description"
label="知识库描述"
>
@@ -603,7 +603,7 @@ export default function AreaDatasetConfig() {
rows={3}
maxLength={500}
/>
</Form.Item>
</Form.Item> */}
{/* 高级设置折叠面板 */}
<div style={{ marginTop: '24px' }}>
@@ -1,10 +1,8 @@
import { Form, Input, Button, Card, Spin, Divider, Select, Slider, InputNumber, Tooltip, Checkbox } from 'antd';
import { SaveOutlined, QuestionCircleOutlined, CheckCircleFilled } from '@ant-design/icons';
import { CheckCircleFilled, QuestionCircleOutlined, SaveOutlined } from '@ant-design/icons';
import { Button, Card, Checkbox, Descriptions, Divider, InputNumber, Select, Slider, Spin, Tag, Tooltip } from 'antd';
import { useDatasetSettings, type SearchMethod } from '~/hooks/dify-dataset-manager/dataset-settings';
import type { DatasetSettingsProps } from '~/types/dify-dataset-manager/dataset-settings';
const { TextArea } = Input;
// 检索方式选项
const SEARCH_METHOD_OPTIONS: { label: string; value: SearchMethod; description: string }[] = [
{ label: '向量检索', value: 'semantic_search', description: '基于语义理解的智能检索,适合需要理解上下文的场景' },
@@ -15,23 +13,21 @@ const SEARCH_METHOD_OPTIONS: { label: string; value: SearchMethod; description:
/**
* 知识库设置组件
* 用于修改知识库名称和描述
* 使用 Descriptions 展示只读的知识库基本信息,提供可编辑的检索设置
* 注:Dify API 不支持修改知识库名称和描述,故这些字段仅作只读展示
*/
export default function DatasetSettings({
dataset,
onDatasetUpdated,
}: DatasetSettingsProps) {
const [form] = Form.useForm();
const {
saving,
hasChanges,
retrievalSettings,
handleValuesChange,
handleSave,
handleReset,
updateRetrievalSettings,
} = useDatasetSettings(dataset, form, onDatasetUpdated);
} = useDatasetSettings(dataset, onDatasetUpdated);
// 是否需要显示 Reranking 提示(语义检索和混合检索需要,且强制开启)
const showRerankingInfo = retrievalSettings.searchMethod === 'semantic_search' || retrievalSettings.searchMethod === 'hybrid_search';
@@ -53,66 +49,56 @@ export default function DatasetSettings({
<div className="page-header">
<h1></h1>
<p className="page-description">
</p>
</div>
{/* 设置表单 */}
<Card className="settings-card">
<Form
form={form}
layout="vertical"
onValuesChange={handleValuesChange}
{/* 知识库基本信息 */}
<Card
className="settings-card"
title={
<span style={{ fontSize: 16, fontWeight: 500 }}>
</span>
}
>
<Descriptions
column={{ xs: 1, sm: 2, md: 2, lg: 3 }}
labelStyle={{
color: '#8c8c8c',
fontWeight: 400,
}}
contentStyle={{
color: '#262626',
fontWeight: 500,
}}
>
<Form.Item
name="name"
label="知识库名称"
rules={[
{ required: true, message: '请输入知识库名称' },
{ max: 100, message: '名称不能超过100个字符' },
]}
>
<Input placeholder="请输入知识库名称" maxLength={100} />
</Form.Item>
<Form.Item
name="description"
label="知识库描述"
extra="描述知识库的用途和内容(仅展示,暂不支持修改)"
>
<TextArea
placeholder="请输入知识库描述"
rows={4}
maxLength={500}
showCount
disabled
/>
</Form.Item>
{/* 只读信息 */}
<div className="readonly-info">
<div className="info-item">
<span className="info-label"></span>
<span className="info-value">
{dataset.indexing_technique === 'high_quality' ? '高质量' : '经济'}
</span>
</div>
<div className="info-item">
<span className="info-label"></span>
<span className="info-value">{dataset.document_count}</span>
</div>
<div className="info-item">
<span className="info-label"></span>
<span className="info-value">{dataset.word_count?.toLocaleString()}</span>
</div>
<div className="info-item">
<span className="info-label"></span>
<span className="info-value">
{new Date(dataset.created_at * 1000).toLocaleString('zh-CN')}
</span>
</div>
</div>
</Form>
<Descriptions.Item label="知识库名称" span={3}>
<span style={{ fontSize: 15 }}>{dataset.name}</span>
</Descriptions.Item>
{dataset.description && (
<Descriptions.Item label="描述" span={3}>
<span style={{ color: '#595959' }}>{dataset.description}</span>
</Descriptions.Item>
)}
<Descriptions.Item label="索引方式">
<Tag color={dataset.indexing_technique === 'high_quality' ? 'green' : 'blue'}>
{dataset.indexing_technique === 'high_quality' ? '高质量' : '经济'}
</Tag>
</Descriptions.Item>
<Descriptions.Item label="文档数量">
<span style={{ fontFamily: 'monospace' }}>{dataset.document_count}</span>
</Descriptions.Item>
<Descriptions.Item label="总字符数">
<span style={{ fontFamily: 'monospace' }}>{dataset.word_count?.toLocaleString() || 0}</span>
</Descriptions.Item>
<Descriptions.Item label="创建时间" span={2}>
{new Date(dataset.created_at * 1000).toLocaleString('zh-CN')}
</Descriptions.Item>
<Descriptions.Item label="最后更新">
{new Date(dataset.updated_at * 1000).toLocaleString('zh-CN')}
</Descriptions.Item>
</Descriptions>
</Card>
{/* 检索设置卡片 */}
@@ -55,7 +55,7 @@ export default function DatasetLayout({
placeholder="选择知识库"
suffixIcon={<SwapOutlined />}
popupMatchSelectWidth={false}
dropdownStyle={{ minWidth: 200 }}
styles={{ popup: { root: { minWidth: 200 } } }}
>
{availableDatasets.map(ds => (
<Select.Option key={ds.dataset_id} value={ds.dataset_id}>
@@ -1,5 +1,5 @@
import { SearchOutlined, FileSearchOutlined } from '@ant-design/icons';
import { Button, Tag, Input, Slider, Spin, Select, Flex, Switch, InputNumber, Tooltip } from 'antd';
import { FileSearchOutlined, SearchOutlined } from '@ant-design/icons';
import { Button, Card, Flex, Input, InputNumber, Select, Slider, Spin, Switch, Tag, Tooltip } from 'antd';
import type { RetrieveRecord } from '~/api/dify-dataset/type';
import { useRetrieveTest } from '~/hooks/dify-dataset-manager/retrieve-test';
import type { RetrieveTestProps } from '~/types/dify-dataset-manager/retrieve-test';
@@ -25,62 +25,46 @@ function ResultItem({ record, index }: { record: RetrieveRecord; index: number }
const scoreColor = record.score > 0.8 ? '#52c41a' : record.score > 0.5 ? '#faad14' : '#666';
return (
<Flex
vertical
gap={12}
style={{
padding: 16,
background: colors.bgContainer,
borderRadius: 8,
border: `1px solid ${colors.border}`,
}}
>
<Flex justify="space-between" align="center">
<Flex gap={8} align="center">
<Tag style={{ background: scoreColor, color: '#fff', border: 'none' }}>
<div className="segment-item">
<div className="segment-header">
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
<Tag style={{ background: scoreColor, color: '#fff', border: 'none', margin: 0 }}>
{scorePercent}%
</Tag>
<span style={{ color: colors.textSecondary, fontSize: 12 }}>
#{index + 1} · {record.segment.word_count} · {record.segment.hit_count}
<span className="segment-index">#{index + 1}</span>
<span className="segment-chars">
{record.segment.word_count} · {record.segment.hit_count}
</span>
</Flex>
</div>
{record.segment.document && (
<span style={{ color: colors.textTertiary, fontSize: 12 }}>
<span className="segment-chars">
: {record.segment.document.name}
</span>
)}
</Flex>
<div style={{
color: colors.text,
fontSize: 14,
lineHeight: 1.6,
whiteSpace: 'pre-wrap',
}}>
</div>
<div className="segment-content">
{record.segment.content.length > 500
? record.segment.content.substring(0, 500) + '...'
: record.segment.content}
</div>
{record.segment.answer && (
<Flex
vertical
gap={4}
style={{
padding: 12,
background: colors.fillTertiary,
borderRadius: 6,
}}
>
<span style={{ color: colors.textSecondary, fontSize: 12 }}>
<div style={{
padding: 12,
background: '#f0f0f0',
borderRadius: 6,
marginTop: 8,
}}>
<span style={{ color: '#8c8c8c', fontSize: 12, display: 'block', marginBottom: 4 }}>
:
</span>
<span style={{ color: colors.text, fontSize: 14 }}>
<span style={{ color: '#262626', fontSize: 14 }}>
{record.segment.answer.length > 200
? record.segment.answer.substring(0, 200) + '...'
: record.segment.answer}
</span>
</Flex>
</div>
)}
</Flex>
</div>
);
}
@@ -277,63 +261,32 @@ export default function RetrieveTest({ datasetId }: RetrieveTestProps) {
</Flex>
{/* 右侧面板 - 结果展示 */}
<Flex
vertical
flex={1}
gap={16}
style={{
padding: 20,
background: colors.bgElevated,
overflow: 'auto',
}}
>
{retrieving ? (
<Flex
flex={1}
align="center"
justify="center"
vertical
gap={12}
>
<Spin size="large" />
<span style={{ color: colors.textSecondary }}>
...
</span>
</Flex>
) : retrieveResults.length === 0 ? (
<Flex
flex={1}
align="center"
justify="center"
vertical
gap={12}
>
<FileSearchOutlined style={{
fontSize: 48,
color: colors.textQuaternary,
}} />
<span style={{ color: colors.textTertiary }}>
</span>
</Flex>
) : (
<>
<Flex justify="space-between" align="center">
<span style={{
fontSize: 14,
color: colors.text,
fontWeight: 500,
}}>
<div className="preview-panel">
<Card
title={
<div className="preview-header">
<span></span>
<span className="segment-count">
{retrieveResults.length > 0 ? `${retrieveResults.length} 条结果` : '0 条结果'}
</span>
<span style={{
fontSize: 13,
color: colors.textSecondary,
}}>
{retrieveResults.length}
</span>
</Flex>
<Flex vertical gap={12}>
</div>
}
className="preview-card"
>
{retrieving ? (
<div className="preview-loading">
<Spin size="large" />
<div className="loading-text">...</div>
</div>
) : retrieveResults.length === 0 ? (
<div className="preview-empty">
<div className="empty-icon">
<FileSearchOutlined />
</div>
<p></p>
</div>
) : (
<div className="preview-segments">
{retrieveResults.map((record, index) => (
<ResultItem
key={record.segment.id}
@@ -341,10 +294,10 @@ export default function RetrieveTest({ datasetId }: RetrieveTestProps) {
index={index}
/>
))}
</Flex>
</>
)}
</Flex>
</div>
)}
</Card>
</div>
</Flex>
);
}