@lobehub/chat 1.15.6 → 1.15.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +6 -6
  3. package/README.zh-CN.md +6 -6
  4. package/docs/self-hosting/server-database/docker-compose.mdx +2 -2
  5. package/docs/self-hosting/server-database/docker-compose.zh-CN.mdx +2 -2
  6. package/locales/ar/knowledgeBase.json +1 -0
  7. package/locales/ar/ragEval.json +91 -0
  8. package/locales/bg-BG/knowledgeBase.json +1 -0
  9. package/locales/bg-BG/ragEval.json +91 -0
  10. package/locales/de-DE/knowledgeBase.json +1 -0
  11. package/locales/de-DE/ragEval.json +91 -0
  12. package/locales/en-US/knowledgeBase.json +1 -0
  13. package/locales/en-US/ragEval.json +91 -0
  14. package/locales/es-ES/knowledgeBase.json +1 -0
  15. package/locales/es-ES/ragEval.json +91 -0
  16. package/locales/fr-FR/knowledgeBase.json +1 -0
  17. package/locales/fr-FR/ragEval.json +91 -0
  18. package/locales/it-IT/knowledgeBase.json +1 -0
  19. package/locales/it-IT/ragEval.json +91 -0
  20. package/locales/ja-JP/knowledgeBase.json +1 -0
  21. package/locales/ja-JP/ragEval.json +91 -0
  22. package/locales/ko-KR/knowledgeBase.json +1 -0
  23. package/locales/ko-KR/ragEval.json +91 -0
  24. package/locales/nl-NL/knowledgeBase.json +1 -0
  25. package/locales/nl-NL/ragEval.json +91 -0
  26. package/locales/pl-PL/knowledgeBase.json +1 -0
  27. package/locales/pl-PL/ragEval.json +91 -0
  28. package/locales/pt-BR/knowledgeBase.json +1 -0
  29. package/locales/pt-BR/ragEval.json +91 -0
  30. package/locales/ru-RU/knowledgeBase.json +1 -0
  31. package/locales/ru-RU/ragEval.json +91 -0
  32. package/locales/tr-TR/knowledgeBase.json +1 -0
  33. package/locales/tr-TR/ragEval.json +91 -0
  34. package/locales/vi-VN/knowledgeBase.json +1 -0
  35. package/locales/vi-VN/ragEval.json +91 -0
  36. package/locales/zh-CN/knowledgeBase.json +1 -0
  37. package/locales/zh-CN/ragEval.json +91 -0
  38. package/locales/zh-TW/knowledgeBase.json +1 -0
  39. package/locales/zh-TW/ragEval.json +91 -0
  40. package/package.json +2 -1
  41. package/src/app/(main)/repos/[id]/@menu/Head/index.tsx +4 -13
  42. package/src/app/(main)/repos/[id]/@menu/Menu/index.tsx +30 -21
  43. package/src/app/(main)/repos/[id]/@menu/default.tsx +8 -2
  44. package/src/app/(main)/repos/[id]/evals/components/Container.tsx +25 -0
  45. package/src/app/(main)/repos/[id]/evals/components/Tabs.tsx +35 -0
  46. package/src/app/(main)/repos/[id]/evals/dataset/CreateDataset/CreateForm.tsx +72 -0
  47. package/src/app/(main)/repos/[id]/evals/dataset/CreateDataset/index.tsx +37 -0
  48. package/src/app/(main)/repos/[id]/evals/dataset/DatasetDetail/index.tsx +126 -0
  49. package/src/app/(main)/repos/[id]/evals/dataset/DatasetList/Item.tsx +59 -0
  50. package/src/app/(main)/repos/[id]/evals/dataset/DatasetList/index.tsx +32 -0
  51. package/src/app/(main)/repos/[id]/evals/dataset/EmptyGuide/index.tsx +33 -0
  52. package/src/app/(main)/repos/[id]/evals/dataset/page.tsx +47 -0
  53. package/src/app/(main)/repos/[id]/evals/evaluation/CreateEvaluation/CreateForm.tsx +93 -0
  54. package/src/app/(main)/repos/[id]/evals/evaluation/CreateEvaluation/index.tsx +28 -0
  55. package/src/app/(main)/repos/[id]/evals/evaluation/CreateEvaluation/useModal.tsx +39 -0
  56. package/src/app/(main)/repos/[id]/evals/evaluation/EmptyGuide/index.tsx +25 -0
  57. package/src/app/(main)/repos/[id]/evals/evaluation/EvaluationList/index.tsx +209 -0
  58. package/src/app/(main)/repos/[id]/evals/evaluation/page.tsx +32 -0
  59. package/src/app/(main)/repos/[id]/evals/layout.tsx +22 -0
  60. package/src/app/(main)/repos/[id]/evals/page.tsx +9 -0
  61. package/src/app/(main)/repos/[id]/evals/type.ts +5 -0
  62. package/src/app/(main)/repos/[id]/not-found.tsx +3 -0
  63. package/src/app/(main)/settings/llm/components/ProviderConfig/index.tsx +2 -2
  64. package/src/components/FileIcon/index.tsx +2 -2
  65. package/src/config/featureFlags/schema.ts +3 -1
  66. package/src/database/server/migrations/0008_add_rag_evals.sql +120 -0
  67. package/src/database/server/migrations/meta/0008_snapshot.json +3463 -0
  68. package/src/database/server/migrations/meta/_journal.json +7 -0
  69. package/src/database/server/models/file.ts +11 -2
  70. package/src/database/server/models/ragEval/dataset.ts +59 -0
  71. package/src/database/server/models/ragEval/datasetRecord.ts +87 -0
  72. package/src/database/server/models/ragEval/evaluation.ts +96 -0
  73. package/src/database/server/models/ragEval/evaluationRecord.ts +64 -0
  74. package/src/database/server/models/ragEval/index.ts +4 -0
  75. package/src/database/server/schemas/lobechat/asyncTask.ts +24 -0
  76. package/src/database/server/schemas/lobechat/file.ts +2 -18
  77. package/src/database/server/schemas/lobechat/index.ts +2 -0
  78. package/src/database/server/schemas/lobechat/ragEvals.ts +105 -0
  79. package/src/database/server/schemas/lobechat/relations.ts +2 -1
  80. package/src/libs/agent-runtime/types/chat.ts +3 -0
  81. package/src/libs/agent-runtime/utils/openaiCompatibleFactory/index.ts +3 -1
  82. package/src/libs/langchain/loaders/index.ts +1 -1
  83. package/src/locales/default/index.ts +2 -0
  84. package/src/locales/default/knowledgeBase.ts +1 -0
  85. package/src/locales/default/ragEval.ts +93 -0
  86. package/src/server/modules/S3/index.ts +11 -0
  87. package/src/server/routers/async/index.ts +2 -0
  88. package/src/server/routers/async/ragEval.ts +138 -0
  89. package/src/server/routers/lambda/index.ts +2 -1
  90. package/src/server/routers/lambda/ragEval.ts +296 -0
  91. package/src/services/ragEval.ts +67 -0
  92. package/src/services/upload.ts +11 -4
  93. package/src/store/file/slices/upload/action.ts +8 -6
  94. package/src/store/knowledgeBase/initialState.ts +3 -1
  95. package/src/store/knowledgeBase/slices/ragEval/actions/dataset.ts +88 -0
  96. package/src/store/knowledgeBase/slices/ragEval/actions/evaluation.ts +62 -0
  97. package/src/store/knowledgeBase/slices/ragEval/actions/index.ts +20 -0
  98. package/src/store/knowledgeBase/slices/ragEval/index.ts +2 -0
  99. package/src/store/knowledgeBase/slices/ragEval/initialState.ts +7 -0
  100. package/src/store/knowledgeBase/store.ts +9 -3
  101. package/src/store/serverConfig/selectors.test.ts +1 -0
  102. package/src/types/eval/dataset.ts +47 -0
  103. package/src/types/eval/evaluation.ts +53 -0
  104. package/src/types/eval/index.ts +3 -0
  105. package/src/types/eval/ragas.ts +9 -0
@@ -0,0 +1,91 @@
1
+ {
2
+ "addDataset": {
3
+ "confirm": "新建",
4
+ "description": {
5
+ "placeholder": "數據集簡介(選填)"
6
+ },
7
+ "name": {
8
+ "placeholder": "數據集名稱",
9
+ "required": "請填寫數據集名稱"
10
+ },
11
+ "title": "添加數據集"
12
+ },
13
+ "dataset": {
14
+ "addNewButton": "創建數據集",
15
+ "emptyGuide": "當前數據集為空,請創建一個數據集。",
16
+ "list": {
17
+ "table": {
18
+ "actions": {
19
+ "importData": "導入數據"
20
+ },
21
+ "columns": {
22
+ "actions": "操作",
23
+ "ideal": {
24
+ "title": "期望回答"
25
+ },
26
+ "question": {
27
+ "title": "問題"
28
+ },
29
+ "referenceFiles": {
30
+ "title": "參考文件"
31
+ }
32
+ },
33
+ "notSelected": "請在左側選擇數據集",
34
+ "title": "數據集詳情"
35
+ },
36
+ "title": "數據集"
37
+ }
38
+ },
39
+ "evaluation": {
40
+ "addEvaluation": {
41
+ "confirm": "新建",
42
+ "datasetId": {
43
+ "placeholder": "請選擇你的評測數據集",
44
+ "required": "請選擇評測數據集"
45
+ },
46
+ "description": {
47
+ "placeholder": "評測任務簡介(選填)"
48
+ },
49
+ "name": {
50
+ "placeholder": "評測任務名稱",
51
+ "required": "請填寫評測任務名稱"
52
+ },
53
+ "title": "添加評測任務"
54
+ },
55
+ "addNewButton": "創建評測",
56
+ "emptyGuide": "當前評測任務為空,開始創建評測。",
57
+ "table": {
58
+ "columns": {
59
+ "actions": {
60
+ "checkStatus": "檢查狀態",
61
+ "confirmDelete": "是否刪除本條評測",
62
+ "confirmRun": "是否開始運行?開始運行後將在後台異步執行評測任務,關閉頁面不影響異步任務的執行",
63
+ "downloadRecords": "下載評測",
64
+ "retry": "重試",
65
+ "run": "運行",
66
+ "title": "操作"
67
+ },
68
+ "datasetId": {
69
+ "title": "數據集"
70
+ },
71
+ "name": {
72
+ "title": "評測任務名稱"
73
+ },
74
+ "records": {
75
+ "title": "評測記錄數"
76
+ },
77
+ "referenceFiles": {
78
+ "title": "參考文件"
79
+ },
80
+ "status": {
81
+ "error": "執行出錯",
82
+ "pending": "待運行",
83
+ "processing": "運行中",
84
+ "success": "執行成功",
85
+ "title": "狀態"
86
+ }
87
+ },
88
+ "title": "評測任務列表"
89
+ }
90
+ }
91
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lobehub/chat",
3
- "version": "1.15.6",
3
+ "version": "1.15.8",
4
4
  "description": "Lobe Chat - an open-source, high-performance chatbot framework that supports speech synthesis, multimodal, and extensible Function Call plugin system. Supports one-click free deployment of your private ChatGPT/LLM web application.",
5
5
  "keywords": [
6
6
  "framework",
@@ -159,6 +159,7 @@
159
159
  "ip": "^2.0.1",
160
160
  "jose": "^5.7.0",
161
161
  "js-sha256": "^0.11.0",
162
+ "jsonl-parse-stringify": "^1.0.3",
162
163
  "langchain": "^0.2.17",
163
164
  "langfuse": "^3.19.0",
164
165
  "langfuse-core": "^3.19.0",
@@ -1,17 +1,13 @@
1
1
  'use client';
2
2
 
3
- import { Skeleton, Typography } from 'antd';
3
+ import { Typography } from 'antd';
4
4
  import { memo } from 'react';
5
5
  import { Center, Flexbox } from 'react-layout-kit';
6
6
 
7
7
  import GoBack from '@/components/GoBack';
8
8
  import RepoIcon from '@/components/RepoIcon';
9
9
 
10
- import { useKnowledgeBaseItem } from '../../hooks/useKnowledgeItem';
11
-
12
- const Head = memo<{ id: string }>(({ id }) => {
13
- const { data, isLoading } = useKnowledgeBaseItem(id);
14
-
10
+ const Head = memo<{ name?: string }>(({ name }) => {
15
11
  return (
16
12
  <Flexbox gap={8}>
17
13
  <GoBack href={'/files'} />
@@ -19,13 +15,8 @@ const Head = memo<{ id: string }>(({ id }) => {
19
15
  <Center style={{ minWidth: 24 }} width={24}>
20
16
  <RepoIcon />
21
17
  </Center>
22
- {isLoading ? (
23
- <Skeleton active paragraph={{ rows: 1, style: { marginBottom: 0 } }} title={false} />
24
- ) : (
25
- <Typography.Text style={{ fontSize: 16, fontWeight: 'bold' }}>
26
- {data?.name}
27
- </Typography.Text>
28
- )}
18
+
19
+ <Typography.Text style={{ fontSize: 16, fontWeight: 'bold' }}>{name}</Typography.Text>
29
20
  </Flexbox>
30
21
  </Flexbox>
31
22
  );
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import { Icon } from '@lobehub/ui';
4
- import { FileText } from 'lucide-react';
4
+ import { FileText, GaugeIcon } from 'lucide-react';
5
5
  import Link from 'next/link';
6
6
  import { usePathname } from 'next/navigation';
7
7
  import { memo, useMemo, useState } from 'react';
@@ -10,31 +10,40 @@ import { Flexbox } from 'react-layout-kit';
10
10
 
11
11
  import Menu from '@/components/Menu';
12
12
  import type { MenuProps } from '@/components/Menu';
13
+ import { featureFlagsSelectors, useServerConfigStore } from '@/store/serverConfig';
13
14
 
14
15
  const FileMenu = memo<{ id: string }>(({ id }) => {
15
16
  const { t } = useTranslation('knowledgeBase');
16
17
  const pathname = usePathname();
18
+ const { enableRAGEval } = useServerConfigStore(featureFlagsSelectors);
19
+ const [activeKey, setActiveKey] = useState(
20
+ pathname.startsWith(`/repos/${id}/evals`) ? 'eval' : 'files',
21
+ );
17
22
 
18
- const [activeKey, setActiveKey] = useState(pathname);
19
-
20
- const items: MenuProps['items'] = useMemo(
21
- () => [
22
- {
23
- icon: <Icon icon={FileText} />,
24
- key: `/repos/${id}`,
25
- label: <Link href={`/repos/${id}`}>{t('tab.files')}</Link>,
26
- },
27
- // {
28
- // icon: <Icon icon={TestTubeDiagonal} />,
29
- // key: `/repos/${id}/testing`,
30
- // label: <Link href={`/repos/${id}/testing`}>{t('tab.testing')}</Link>,
31
- // },
32
- // {
33
- // icon: <Icon icon={Settings2Icon} />,
34
- // key: `/repos/${id}/settings`,
35
- // label: <Link href={`/repos/${id}/settings`}>{t('tab.settings')}</Link>,
36
- // },
37
- ],
23
+ const items = useMemo(
24
+ () =>
25
+ [
26
+ {
27
+ icon: <Icon icon={FileText} />,
28
+ key: 'files',
29
+ label: <Link href={`/repos/${id}`}>{t('tab.files')}</Link>,
30
+ },
31
+ enableRAGEval && {
32
+ icon: <Icon icon={GaugeIcon} />,
33
+ key: 'eval',
34
+ label: <Link href={`/repos/${id}/evals/dataset`}>{t('tab.evals')}</Link>,
35
+ },
36
+ // {
37
+ // icon: <Icon icon={TestTubeDiagonal} />,
38
+ // key: `/repos/${id}/testing`,
39
+ // label: <Link href={`/repos/${id}/testing`}>{t('tab.testing')}</Link>,
40
+ // },
41
+ // {
42
+ // icon: <Icon icon={Settings2Icon} />,
43
+ // key: `/repos/${id}/settings`,
44
+ // label: <Link href={`/repos/${id}/settings`}>{t('tab.settings')}</Link>,
45
+ // },
46
+ ].filter(Boolean) as MenuProps['items'],
38
47
  [t],
39
48
  );
40
49
 
@@ -1,5 +1,8 @@
1
+ import { notFound } from 'next/navigation';
1
2
  import { Flexbox } from 'react-layout-kit';
2
3
 
4
+ import { KnowledgeBaseModel } from '@/database/server/models/knowledgeBase';
5
+
3
6
  import Head from './Head';
4
7
  import Menu from './Menu';
5
8
 
@@ -9,12 +12,15 @@ interface Params {
9
12
 
10
13
  type Props = { params: Params };
11
14
 
12
- const MenuPage = ({ params }: Props) => {
15
+ const MenuPage = async ({ params }: Props) => {
13
16
  const id = params.id;
17
+ const item = await KnowledgeBaseModel.findById(params.id);
18
+
19
+ if (!item) return notFound();
14
20
 
15
21
  return (
16
22
  <Flexbox gap={16} height={'100%'} paddingInline={12} style={{ paddingTop: 12 }}>
17
- <Head id={id} />
23
+ <Head name={item.name} />
18
24
  <Menu id={id} />
19
25
  </Flexbox>
20
26
  );
@@ -0,0 +1,25 @@
1
+ 'use client';
2
+
3
+ import { createStyles } from 'antd-style';
4
+ import { PropsWithChildren } from 'react';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ const useStyles = createStyles(({ css, token }) => ({
8
+ container: css`
9
+ padding: 16px;
10
+ background: ${token.colorBgContainer};
11
+ border-radius: 8px;
12
+ `,
13
+ }));
14
+
15
+ const Container = ({ children }: PropsWithChildren) => {
16
+ const { styles } = useStyles();
17
+
18
+ return (
19
+ <Flexbox className={styles.container} height={'100%'}>
20
+ {children}
21
+ </Flexbox>
22
+ );
23
+ };
24
+
25
+ export default Container;
@@ -0,0 +1,35 @@
1
+ 'use client';
2
+
3
+ import { TabsNav } from '@lobehub/ui';
4
+ import Link from 'next/link';
5
+ import { usePathname } from 'next/navigation';
6
+
7
+ export const Tabs = ({ knowledgeBaseId }: { knowledgeBaseId: string }) => {
8
+ const pathname = usePathname();
9
+
10
+ const key = pathname.split('/').pop();
11
+
12
+ return (
13
+ <TabsNav
14
+ activeKey={key}
15
+ items={[
16
+ {
17
+ key: 'dataset',
18
+ label: (
19
+ <Link href={`/repos/${knowledgeBaseId}/evals/dataset`} style={{ color: 'initial' }}>
20
+ 数据集
21
+ </Link>
22
+ ),
23
+ },
24
+ {
25
+ key: 'evaluation',
26
+ label: (
27
+ <Link href={`/repos/${knowledgeBaseId}/evals/evaluation`} style={{ color: 'initial' }}>
28
+ 评测任务
29
+ </Link>
30
+ ),
31
+ },
32
+ ]}
33
+ />
34
+ );
35
+ };
@@ -0,0 +1,72 @@
1
+ import { Button, Form, Input } from 'antd';
2
+ import { css, cx } from 'antd-style';
3
+ import { memo, useState } from 'react';
4
+ import { useTranslation } from 'react-i18next';
5
+ import { Flexbox } from 'react-layout-kit';
6
+
7
+ import { useKnowledgeBaseStore } from '@/store/knowledgeBase';
8
+ import { CreateNewEvalDatasets } from '@/types/eval';
9
+
10
+ const formItem = css`
11
+ display: flex;
12
+ flex-direction: column;
13
+ gap: 12px;
14
+
15
+ .ant-form-item {
16
+ margin-block-end: 0;
17
+ }
18
+ `;
19
+
20
+ interface CreateFormProps {
21
+ knowledgeBaseId: string;
22
+ onClose?: () => void;
23
+ }
24
+
25
+ const CreateForm = memo<CreateFormProps>(({ onClose, knowledgeBaseId }) => {
26
+ const { t } = useTranslation('ragEval');
27
+ const [loading, setLoading] = useState(false);
28
+ const createNewDataset = useKnowledgeBaseStore((s) => s.createNewDataset);
29
+
30
+ const onFinish = async (values: CreateNewEvalDatasets) => {
31
+ setLoading(true);
32
+
33
+ try {
34
+ await createNewDataset({ ...values, knowledgeBaseId });
35
+ setLoading(false);
36
+ onClose?.();
37
+ } catch (e) {
38
+ console.error(e);
39
+ setLoading(false);
40
+ }
41
+ };
42
+
43
+ return (
44
+ <Flexbox gap={8}>
45
+ <Form className={cx(formItem)} onFinish={onFinish}>
46
+ <Form.Item
47
+ name={'name'}
48
+ rules={[{ message: t('addDataset.name.required'), required: true }]}
49
+ >
50
+ <Input autoFocus placeholder={t('addDataset.name.placeholder')} />
51
+ </Form.Item>
52
+ <Form.Item name={'description'}>
53
+ <Input.TextArea
54
+ placeholder={t('addDataset.description.placeholder')}
55
+ style={{ minHeight: 120 }}
56
+ />
57
+ </Form.Item>
58
+ <Button
59
+ block
60
+ htmlType={'submit'}
61
+ loading={loading}
62
+ style={{ marginTop: 16 }}
63
+ type={'primary'}
64
+ >
65
+ {t('addDataset.confirm')}
66
+ </Button>
67
+ </Form>
68
+ </Flexbox>
69
+ );
70
+ });
71
+
72
+ export default CreateForm;
@@ -0,0 +1,37 @@
1
+ import { Icon } from '@lobehub/ui';
2
+ import { SheetIcon } from 'lucide-react';
3
+ import { useTranslation } from 'react-i18next';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { createModal } from '@/components/FunctionModal';
7
+
8
+ import CreateForm from './CreateForm';
9
+
10
+ const Title = () => {
11
+ const { t } = useTranslation('ragEval');
12
+
13
+ return (
14
+ <Flexbox gap={8} horizontal>
15
+ <Icon icon={SheetIcon} />
16
+ {t('addDataset.title')}
17
+ </Flexbox>
18
+ );
19
+ };
20
+
21
+ interface CreateDatasetModalProps {
22
+ knowledgeBaseId: string;
23
+ }
24
+
25
+ export const useCreateDatasetModal = createModal<CreateDatasetModalProps>((instance, params) => ({
26
+ content: (
27
+ <Flexbox paddingInline={16} style={{ marginBlock: 24 }}>
28
+ <CreateForm
29
+ knowledgeBaseId={params!.knowledgeBaseId}
30
+ onClose={() => {
31
+ instance.current?.destroy();
32
+ }}
33
+ />
34
+ </Flexbox>
35
+ ),
36
+ title: <Title />,
37
+ }));
@@ -0,0 +1,126 @@
1
+ 'use client';
2
+
3
+ import { ProColumns, ProTable } from '@ant-design/pro-components';
4
+ import { ActionIcon } from '@lobehub/ui';
5
+ import { Button, Typography, Upload } from 'antd';
6
+ import { createStyles } from 'antd-style';
7
+ import { Edit2Icon, Trash2Icon } from 'lucide-react';
8
+ import { parseAsInteger, useQueryState } from 'nuqs';
9
+ import { useTranslation } from 'react-i18next';
10
+ import { Center, Flexbox } from 'react-layout-kit';
11
+
12
+ import FileIcon from '@/components/FileIcon';
13
+ import { ragEvalService } from '@/services/ragEval';
14
+ import { useKnowledgeBaseStore } from '@/store/knowledgeBase';
15
+ import { EvalDatasetRecordRefFile } from '@/types/eval';
16
+
17
+ const createRequest = (activeDatasetId: number) => async () => {
18
+ const records = await ragEvalService.getDatasetRecords(activeDatasetId);
19
+
20
+ return {
21
+ data: records,
22
+ success: true,
23
+ total: records.length,
24
+ };
25
+ };
26
+
27
+ const useStyles = createStyles(({ css }) => ({
28
+ container: css`
29
+ padding-block: 0;
30
+ padding-inline: 12px;
31
+ `,
32
+ icon: css`
33
+ min-width: 24px;
34
+ border-radius: 4px;
35
+ `,
36
+ title: css`
37
+ font-size: 16px;
38
+ `,
39
+ }));
40
+
41
+ const DatasetDetail = () => {
42
+ const { t } = useTranslation(['ragEval', 'common']);
43
+ const { styles } = useStyles();
44
+ const [importDataset] = useKnowledgeBaseStore((s) => [s.importDataset]);
45
+
46
+ const [activeDatasetId] = useQueryState('id', parseAsInteger);
47
+
48
+ const columns: ProColumns[] = [
49
+ {
50
+ dataIndex: 'question',
51
+ ellipsis: true,
52
+ title: t('dataset.list.table.columns.question.title'),
53
+ width: '40%',
54
+ },
55
+ { dataIndex: 'ideal', ellipsis: true, title: t('dataset.list.table.columns.ideal.title') },
56
+ {
57
+ dataIndex: 'referenceFiles',
58
+ render: (dom, entity) => {
59
+ const referenceFiles = entity.referenceFiles as EvalDatasetRecordRefFile[];
60
+
61
+ return (
62
+ !!referenceFiles && (
63
+ <Flexbox>
64
+ {referenceFiles?.map((file) => (
65
+ <Flexbox gap={4} horizontal key={file.id}>
66
+ <FileIcon fileName={file.name} fileType={file.fileType} size={20} />
67
+ <Typography.Text ellipsis={{ tooltip: true }}>{file.name}</Typography.Text>
68
+ </Flexbox>
69
+ ))}
70
+ </Flexbox>
71
+ )
72
+ );
73
+ },
74
+ title: t('dataset.list.table.columns.referenceFiles.title'),
75
+ width: 200,
76
+ },
77
+ {
78
+ dataIndex: 'actions',
79
+ render: () => (
80
+ <Flexbox gap={4} horizontal>
81
+ <ActionIcon icon={Edit2Icon} size={'small'} title={t('edit', { ns: 'common' })} />
82
+ <ActionIcon icon={Trash2Icon} size={'small'} title={t('delete', { ns: 'common' })} />
83
+ </Flexbox>
84
+ ),
85
+ title: t('dataset.list.table.columns.actions'),
86
+
87
+ width: 80,
88
+ },
89
+ ];
90
+
91
+ const request = !!activeDatasetId ? createRequest(activeDatasetId) : undefined;
92
+
93
+ return !activeDatasetId ? (
94
+ <Center height={'100%'} width={'100%'}>
95
+ {t('dataset.list.table.notSelected')}
96
+ </Center>
97
+ ) : (
98
+ <Flexbox className={styles.container} gap={24}>
99
+ <ProTable
100
+ columns={columns}
101
+ request={request}
102
+ search={false}
103
+ size={'small'}
104
+ toolbar={{
105
+ actions: [
106
+ <Upload
107
+ beforeUpload={async (file) => {
108
+ await importDataset(file, activeDatasetId);
109
+
110
+ return false;
111
+ }}
112
+ key={'upload'}
113
+ multiple={false}
114
+ showUploadList={false}
115
+ >
116
+ <Button type={'primary'}>{t('dataset.list.table.actions.importData')}</Button>
117
+ </Upload>,
118
+ ],
119
+ title: <div className={styles.title}>{t('dataset.list.table.title')}</div>,
120
+ }}
121
+ />
122
+ </Flexbox>
123
+ );
124
+ };
125
+
126
+ export default DatasetDetail;
@@ -0,0 +1,59 @@
1
+ import { createStyles } from 'antd-style';
2
+ import { parseAsInteger, useQueryState } from 'nuqs';
3
+ import { memo } from 'react';
4
+ import { Flexbox } from 'react-layout-kit';
5
+
6
+ import { RAGEvalDataSetItem } from '@/types/eval';
7
+
8
+ const useStyles = createStyles(({ css, token }) => ({
9
+ active: css`
10
+ background: ${token.colorFillTertiary};
11
+
12
+ &:hover {
13
+ background-color: ${token.colorFillSecondary};
14
+ }
15
+ `,
16
+ container: css`
17
+ cursor: pointer;
18
+
19
+ margin-block-end: 2px;
20
+ padding-block: 12px;
21
+ padding-inline: 8px;
22
+
23
+ border-radius: 8px;
24
+
25
+ &:hover {
26
+ background-color: ${token.colorFillTertiary};
27
+ }
28
+ `,
29
+ icon: css`
30
+ min-width: 24px;
31
+ border-radius: 4px;
32
+ `,
33
+ title: css`
34
+ text-align: start;
35
+ `,
36
+ }));
37
+
38
+ const Item = memo<RAGEvalDataSetItem>(({ name, description, id }) => {
39
+ const { styles, cx } = useStyles();
40
+
41
+ const [activeDatasetId, activateDataset] = useQueryState('id', parseAsInteger);
42
+
43
+ const isActive = activeDatasetId === id;
44
+ return (
45
+ <Flexbox
46
+ className={cx(styles.container, isActive && styles.active)}
47
+ onClick={() => {
48
+ if (!isActive) {
49
+ activateDataset(id);
50
+ }
51
+ }}
52
+ >
53
+ <div className={styles.title}>{name}</div>
54
+ {description && <div>{description}</div>}
55
+ </Flexbox>
56
+ );
57
+ });
58
+
59
+ export default Item;
@@ -0,0 +1,32 @@
1
+ 'use client';
2
+
3
+ import { ActionIcon } from '@lobehub/ui';
4
+ import { PlusIcon } from 'lucide-react';
5
+ import { memo } from 'react';
6
+ import { useTranslation } from 'react-i18next';
7
+ import { Flexbox } from 'react-layout-kit';
8
+ import { Virtuoso } from 'react-virtuoso';
9
+
10
+ import { RAGEvalDataSetItem } from '@/types/eval';
11
+
12
+ import Item from './Item';
13
+
14
+ interface DatasetListProps {
15
+ dataSource: RAGEvalDataSetItem[];
16
+ }
17
+
18
+ const DatasetList = memo<DatasetListProps>(({ dataSource }) => {
19
+ const { t } = useTranslation('ragEval');
20
+
21
+ return (
22
+ <Flexbox gap={24} height={'100%'}>
23
+ <Flexbox align={'center'} horizontal justify={'space-between'}>
24
+ <span>{t('dataset.list.title')}</span>
25
+ <ActionIcon icon={PlusIcon} size={'small'} />
26
+ </Flexbox>
27
+ <Virtuoso data={dataSource} itemContent={(index, data) => <Item {...data} key={data.id} />} />
28
+ </Flexbox>
29
+ );
30
+ });
31
+
32
+ export default DatasetList;
@@ -0,0 +1,33 @@
1
+ 'use client';
2
+
3
+ import { Button } from 'antd';
4
+ import { memo } from 'react';
5
+ import { useTranslation } from 'react-i18next';
6
+ import { Center, Flexbox } from 'react-layout-kit';
7
+
8
+ import { useCreateDatasetModal } from '../CreateDataset';
9
+
10
+ interface EmptyGuideProps {
11
+ knowledgeBaseId: string;
12
+ }
13
+
14
+ const EmptyGuide = memo<EmptyGuideProps>(({ knowledgeBaseId }) => {
15
+ const { t } = useTranslation('ragEval');
16
+ const modal = useCreateDatasetModal();
17
+ return (
18
+ <Center gap={24} height={'100%'} width={'100%'}>
19
+ <div>{t('dataset.emptyGuide')}</div>
20
+ <Flexbox gap={8} horizontal>
21
+ <Button
22
+ onClick={() => {
23
+ modal.open({ knowledgeBaseId });
24
+ }}
25
+ type={'primary'}
26
+ >
27
+ {t('dataset.addNewButton')}
28
+ </Button>
29
+ </Flexbox>
30
+ </Center>
31
+ );
32
+ });
33
+ export default EmptyGuide;