balibabu commited on
Commit
85a7d1b
·
1 Parent(s): ac557fe

Feat: Add TagTable #4367 (#4368)

Browse files

### What problem does this PR solve?

Feat: Add TagTable #4367

### Type of change


- [x] New Feature (non-breaking change which adds functionality)

web/src/components/auto-keywords-item.tsx CHANGED
@@ -16,11 +16,7 @@ export const AutoKeywordsItem = () => {
16
  <Slider max={30} style={{ width: '100%' }} />
17
  </Form.Item>
18
  </Flex>
19
- <Form.Item
20
- name={['parser_config', 'auto_keywords']}
21
- noStyle
22
- initialValue={0}
23
- >
24
  <InputNumber max={30} min={0} />
25
  </Form.Item>
26
  </Flex>
@@ -43,11 +39,7 @@ export const AutoQuestionsItem = () => {
43
  <Slider max={10} style={{ width: '100%' }} />
44
  </Form.Item>
45
  </Flex>
46
- <Form.Item
47
- name={['parser_config', 'auto_questions']}
48
- noStyle
49
- initialValue={0}
50
- >
51
  <InputNumber max={10} min={0} />
52
  </Form.Item>
53
  </Flex>
 
16
  <Slider max={30} style={{ width: '100%' }} />
17
  </Form.Item>
18
  </Flex>
19
+ <Form.Item name={['parser_config', 'auto_keywords']} noStyle>
 
 
 
 
20
  <InputNumber max={30} min={0} />
21
  </Form.Item>
22
  </Flex>
 
39
  <Slider max={10} style={{ width: '100%' }} />
40
  </Form.Item>
41
  </Flex>
42
+ <Form.Item name={['parser_config', 'auto_questions']} noStyle>
 
 
 
 
43
  <InputNumber max={10} min={0} />
44
  </Form.Item>
45
  </Flex>
web/src/components/chunk-method-modal/hooks.ts CHANGED
@@ -119,7 +119,7 @@ export const useFetchParserListOnMount = (
119
  return { parserList: nextParserList, handleChange, selectedTag };
120
  };
121
 
122
- const hideAutoKeywords = ['qa', 'table', 'resume', 'knowledge_graph'];
123
 
124
  export const useShowAutoKeywords = () => {
125
  const showAutoKeywords = useCallback((selectedTag: string) => {
 
119
  return { parserList: nextParserList, handleChange, selectedTag };
120
  };
121
 
122
+ const hideAutoKeywords = ['qa', 'table', 'resume', 'knowledge_graph', 'tag'];
123
 
124
  export const useShowAutoKeywords = () => {
125
  const showAutoKeywords = useCallback((selectedTag: string) => {
web/src/components/confirm-delete-dialog.tsx CHANGED
@@ -21,6 +21,7 @@ interface IProps {
21
  export function ConfirmDeleteDialog({
22
  children,
23
  title,
 
24
  }: IProps & PropsWithChildren) {
25
  const { t } = useTranslation();
26
 
@@ -39,7 +40,10 @@ export function ConfirmDeleteDialog({
39
  </AlertDialogHeader>
40
  <AlertDialogFooter>
41
  <AlertDialogCancel>{t('common.cancel')}</AlertDialogCancel>
42
- <AlertDialogAction className="bg-colors-background-functional-solid-danger text--colors-text-neutral-strong">
 
 
 
43
  <Trash2 />
44
  {t('common.ok')}
45
  </AlertDialogAction>
 
21
  export function ConfirmDeleteDialog({
22
  children,
23
  title,
24
+ onOk,
25
  }: IProps & PropsWithChildren) {
26
  const { t } = useTranslation();
27
 
 
40
  </AlertDialogHeader>
41
  <AlertDialogFooter>
42
  <AlertDialogCancel>{t('common.cancel')}</AlertDialogCancel>
43
+ <AlertDialogAction
44
+ className="bg-colors-background-functional-solid-danger text--colors-text-neutral-strong"
45
+ onClick={onOk}
46
+ >
47
  <Trash2 />
48
  {t('common.ok')}
49
  </AlertDialogAction>
web/src/components/max-token-number.tsx CHANGED
@@ -25,7 +25,6 @@ const MaxTokenNumber = ({ initialValue = 128, max = 2048 }: IProps) => {
25
  <Form.Item
26
  name={['parser_config', 'chunk_token_num']}
27
  noStyle
28
- initialValue={initialValue}
29
  rules={[{ required: true, message: t('chunkTokenNumberMessage') }]}
30
  >
31
  <InputNumber max={max} min={0} />
 
25
  <Form.Item
26
  name={['parser_config', 'chunk_token_num']}
27
  noStyle
 
28
  rules={[{ required: true, message: t('chunkTokenNumberMessage') }]}
29
  >
30
  <InputNumber max={max} min={0} />
web/src/components/page-rank.tsx CHANGED
@@ -17,12 +17,7 @@ const PageRank = () => {
17
  <Slider max={100} style={{ width: '100%' }} />
18
  </Form.Item>
19
  </Flex>
20
- <Form.Item
21
- name={['pagerank']}
22
- noStyle
23
- initialValue={0}
24
- rules={[{ required: true }]}
25
- >
26
  <InputNumber max={100} min={0} />
27
  </Form.Item>
28
  </Flex>
 
17
  <Slider max={100} style={{ width: '100%' }} />
18
  </Form.Item>
19
  </Flex>
20
+ <Form.Item name={['pagerank']} noStyle rules={[{ required: true }]}>
 
 
 
 
 
21
  <InputNumber max={100} min={0} />
22
  </Form.Item>
23
  </Flex>
web/src/components/parse-configuration/index.tsx CHANGED
@@ -18,6 +18,8 @@ export const excludedParseMethods = [
18
  'one',
19
  'picture',
20
  'knowledge_graph',
 
 
21
  ];
22
 
23
  export const showRaptorParseConfiguration = (parserId: string) => {
 
18
  'one',
19
  'picture',
20
  'knowledge_graph',
21
+ 'qa',
22
+ 'tag',
23
  ];
24
 
25
  export const showRaptorParseConfiguration = (parserId: string) => {
web/src/hooks/knowledge-hooks.ts CHANGED
@@ -1,7 +1,15 @@
1
  import { ResponsePostType } from '@/interfaces/database/base';
2
- import { IKnowledge, ITestingResult } from '@/interfaces/database/knowledge';
 
 
 
 
3
  import i18n from '@/locales/config';
4
- import kbService from '@/services/knowledge-service';
 
 
 
 
5
  import {
6
  useInfiniteQuery,
7
  useIsMutating,
@@ -259,3 +267,74 @@ export const useSelectIsTestingSuccess = () => {
259
  return status.at(-1) === 'success';
260
  };
261
  //#endregion
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import { ResponsePostType } from '@/interfaces/database/base';
2
+ import {
3
+ IKnowledge,
4
+ IRenameTag,
5
+ ITestingResult,
6
+ } from '@/interfaces/database/knowledge';
7
  import i18n from '@/locales/config';
8
+ import kbService, {
9
+ listTag,
10
+ removeTag,
11
+ renameTag,
12
+ } from '@/services/knowledge-service';
13
  import {
14
  useInfiniteQuery,
15
  useIsMutating,
 
267
  return status.at(-1) === 'success';
268
  };
269
  //#endregion
270
+
271
+ //#region tags
272
+
273
+ export const useFetchTagList = () => {
274
+ const knowledgeBaseId = useKnowledgeBaseId();
275
+
276
+ const { data, isFetching: loading } = useQuery<Array<[string, number]>>({
277
+ queryKey: ['fetchTagList'],
278
+ initialData: [],
279
+ gcTime: 0, // https://tanstack.com/query/latest/docs/framework/react/guides/caching?from=reactQueryV3
280
+ queryFn: async () => {
281
+ const { data } = await listTag(knowledgeBaseId);
282
+ const list = data?.data || [];
283
+ return list;
284
+ },
285
+ });
286
+
287
+ return { list: data, loading };
288
+ };
289
+
290
+ export const useDeleteTag = () => {
291
+ const knowledgeBaseId = useKnowledgeBaseId();
292
+
293
+ const queryClient = useQueryClient();
294
+ const {
295
+ data,
296
+ isPending: loading,
297
+ mutateAsync,
298
+ } = useMutation({
299
+ mutationKey: ['deleteTag'],
300
+ mutationFn: async (tags: string[]) => {
301
+ const { data } = await removeTag(knowledgeBaseId, tags);
302
+ if (data.code === 0) {
303
+ message.success(i18n.t(`message.deleted`));
304
+ queryClient.invalidateQueries({
305
+ queryKey: ['fetchTagList'],
306
+ });
307
+ }
308
+ return data?.data ?? [];
309
+ },
310
+ });
311
+
312
+ return { data, loading, deleteTag: mutateAsync };
313
+ };
314
+
315
+ export const useRenameTag = () => {
316
+ const knowledgeBaseId = useKnowledgeBaseId();
317
+
318
+ const queryClient = useQueryClient();
319
+ const {
320
+ data,
321
+ isPending: loading,
322
+ mutateAsync,
323
+ } = useMutation({
324
+ mutationKey: ['deleteTag'],
325
+ mutationFn: async (params: IRenameTag) => {
326
+ const { data } = await renameTag(knowledgeBaseId, params);
327
+ if (data.code === 0) {
328
+ message.success(i18n.t(`message.modified`));
329
+ queryClient.invalidateQueries({
330
+ queryKey: ['fetchTagList'],
331
+ });
332
+ }
333
+ return data?.data ?? [];
334
+ },
335
+ });
336
+
337
+ return { data, loading, renameTag: mutateAsync };
338
+ };
339
+
340
+ //#endregion
web/src/interfaces/database/knowledge.ts CHANGED
@@ -117,3 +117,5 @@ export interface ITestingResult {
117
  documents: ITestingDocument[];
118
  total: number;
119
  }
 
 
 
117
  documents: ITestingDocument[];
118
  total: number;
119
  }
120
+
121
+ export type IRenameTag = { fromTag: string; toTag: string };
web/src/locales/en.ts CHANGED
@@ -34,6 +34,8 @@ export default {
34
  pleaseInput: 'Please input',
35
  submit: 'Submit',
36
  embedIntoSite: 'Embed into webpage',
 
 
37
  },
38
  login: {
39
  login: 'Sign in',
@@ -308,6 +310,9 @@ The above is the content you need to summarize.`,
308
  vietnamese: 'Vietnamese',
309
  pageRank: 'Page rank',
310
  pageRankTip: `This increases the relevance score of the knowledge base. Its value will be added to the relevance score of all retrieved chunks from this knowledge base. Useful when you are searching within multiple knowledge bases and wanting to assign a higher pagerank score to a specific one.`,
 
 
 
311
  },
312
  chunk: {
313
  chunk: 'Chunk',
 
34
  pleaseInput: 'Please input',
35
  submit: 'Submit',
36
  embedIntoSite: 'Embed into webpage',
37
+ previousPage: 'Previous',
38
+ nextPage: 'Next',
39
  },
40
  login: {
41
  login: 'Sign in',
 
310
  vietnamese: 'Vietnamese',
311
  pageRank: 'Page rank',
312
  pageRankTip: `This increases the relevance score of the knowledge base. Its value will be added to the relevance score of all retrieved chunks from this knowledge base. Useful when you are searching within multiple knowledge bases and wanting to assign a higher pagerank score to a specific one.`,
313
+ tag: 'Tag',
314
+ frequency: 'Frequency',
315
+ searchTags: 'Search tags',
316
  },
317
  chunk: {
318
  chunk: 'Chunk',
web/src/locales/zh-traditional.ts CHANGED
@@ -34,6 +34,8 @@ export default {
34
  pleaseInput: '請輸入',
35
  submit: '提交',
36
  embedIntoSite: '嵌入網站',
 
 
37
  },
38
  login: {
39
  login: '登入',
@@ -292,6 +294,9 @@ export default {
292
  pageRank: '頁面排名',
293
  pageRankTip: `這用來提高相關性分數。所有檢索到的區塊的相關性得分將加上該數字。
294
  當您想要先搜尋給定的知識庫時,請設定比其他人更高的 pagerank 分數。`,
 
 
 
295
  },
296
  chunk: {
297
  chunk: '解析塊',
 
34
  pleaseInput: '請輸入',
35
  submit: '提交',
36
  embedIntoSite: '嵌入網站',
37
+ previousPage: '上一頁',
38
+ nextPage: '下一頁',
39
  },
40
  login: {
41
  login: '登入',
 
294
  pageRank: '頁面排名',
295
  pageRankTip: `這用來提高相關性分數。所有檢索到的區塊的相關性得分將加上該數字。
296
  當您想要先搜尋給定的知識庫時,請設定比其他人更高的 pagerank 分數。`,
297
+ tag: '標籤',
298
+ frequency: '頻次',
299
+ searchTags: '搜尋標籤',
300
  },
301
  chunk: {
302
  chunk: '解析塊',
web/src/locales/zh.ts CHANGED
@@ -34,6 +34,8 @@ export default {
34
  pleaseInput: '请输入',
35
  submit: '提交',
36
  embedIntoSite: '嵌入网站',
 
 
37
  },
38
  login: {
39
  login: '登录',
@@ -309,6 +311,9 @@ export default {
309
  pageRank: '页面排名',
310
  pageRankTip: `这用于提高相关性得分。所有检索到的块的相关性得分将加上此数字。
311
  当您想首先搜索给定的知识库时,请设置比其他知识库更高的 pagerank 得分。`,
 
 
 
312
  },
313
  chunk: {
314
  chunk: '解析块',
 
34
  pleaseInput: '请输入',
35
  submit: '提交',
36
  embedIntoSite: '嵌入网站',
37
+ previousPage: '上一页',
38
+ nextPage: '下一页',
39
  },
40
  login: {
41
  login: '登录',
 
311
  pageRank: '页面排名',
312
  pageRankTip: `这用于提高相关性得分。所有检索到的块的相关性得分将加上此数字。
313
  当您想首先搜索给定的知识库时,请设置比其他知识库更高的 pagerank 得分。`,
314
+ tag: '标签',
315
+ frequency: '频次',
316
+ searchTags: '搜索标签',
317
  },
318
  chunk: {
319
  chunk: '解析块',
web/src/pages/add-knowledge/components/knowledge-setting/category-panel.tsx CHANGED
@@ -6,6 +6,7 @@ import DOMPurify from 'dompurify';
6
  import camelCase from 'lodash/camelCase';
7
  import { useMemo } from 'react';
8
  import styles from './index.less';
 
9
  import { ImageMap } from './utils';
10
 
11
  const { Title, Text } = Typography;
@@ -68,6 +69,7 @@ const CategoryPanel = ({ chunkMethod }: { chunkMethod: string }) => {
68
  <SvgIcon name={'chunk-method/chunk-empty'} width={'100%'}></SvgIcon>
69
  </Empty>
70
  )}
 
71
  </section>
72
  );
73
  };
 
6
  import camelCase from 'lodash/camelCase';
7
  import { useMemo } from 'react';
8
  import styles from './index.less';
9
+ import { TagTable } from './tag-table';
10
  import { ImageMap } from './utils';
11
 
12
  const { Title, Text } = Typography;
 
69
  <SvgIcon name={'chunk-method/chunk-empty'} width={'100%'}></SvgIcon>
70
  </Empty>
71
  )}
72
+ {chunkMethod === 'tag' && <TagTable></TagTable>}
73
  </section>
74
  );
75
  };
web/src/pages/add-knowledge/components/knowledge-setting/hooks.ts CHANGED
@@ -1,6 +1,8 @@
1
  import { LlmModelType } from '@/constants/knowledge';
 
2
  import {
3
  useFetchKnowledgeBaseConfiguration,
 
4
  useUpdateKnowledge,
5
  } from '@/hooks/knowledge-hooks';
6
  import { useSelectLlmOptionsByModelType } from '@/hooks/llm-hooks';
@@ -14,7 +16,7 @@ import { useIsFetching } from '@tanstack/react-query';
14
  import { Form, UploadFile } from 'antd';
15
  import { FormInstance } from 'antd/lib';
16
  import pick from 'lodash/pick';
17
- import { useCallback, useEffect } from 'react';
18
 
19
  export const useSubmitKnowledgeConfiguration = (form: FormInstance) => {
20
  const { saveKnowledgeConfiguration, loading } = useUpdateKnowledge();
@@ -87,3 +89,44 @@ export const useHandleChunkMethodChange = () => {
87
 
88
  return { form, chunkMethod };
89
  };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import { LlmModelType } from '@/constants/knowledge';
2
+ import { useSetModalState } from '@/hooks/common-hooks';
3
  import {
4
  useFetchKnowledgeBaseConfiguration,
5
+ useRenameTag,
6
  useUpdateKnowledge,
7
  } from '@/hooks/knowledge-hooks';
8
  import { useSelectLlmOptionsByModelType } from '@/hooks/llm-hooks';
 
16
  import { Form, UploadFile } from 'antd';
17
  import { FormInstance } from 'antd/lib';
18
  import pick from 'lodash/pick';
19
+ import { useCallback, useEffect, useState } from 'react';
20
 
21
  export const useSubmitKnowledgeConfiguration = (form: FormInstance) => {
22
  const { saveKnowledgeConfiguration, loading } = useUpdateKnowledge();
 
89
 
90
  return { form, chunkMethod };
91
  };
92
+
93
+ export const useRenameKnowledgeTag = () => {
94
+ const [tag, setTag] = useState<string>('');
95
+ const {
96
+ visible: tagRenameVisible,
97
+ hideModal: hideTagRenameModal,
98
+ showModal: showFileRenameModal,
99
+ } = useSetModalState();
100
+ const { renameTag, loading } = useRenameTag();
101
+
102
+ const onTagRenameOk = useCallback(
103
+ async (name: string) => {
104
+ const ret = await renameTag({
105
+ fromTag: tag,
106
+ toTag: name,
107
+ });
108
+
109
+ if (ret === 0) {
110
+ hideTagRenameModal();
111
+ }
112
+ },
113
+ [renameTag, tag, hideTagRenameModal],
114
+ );
115
+
116
+ const handleShowTagRenameModal = useCallback(
117
+ (record: string) => {
118
+ setTag(record);
119
+ showFileRenameModal();
120
+ },
121
+ [showFileRenameModal],
122
+ );
123
+
124
+ return {
125
+ renameLoading: loading,
126
+ initialName: tag,
127
+ onTagRenameOk,
128
+ tagRenameVisible,
129
+ hideTagRenameModal,
130
+ showTagRenameModal: handleShowTagRenameModal,
131
+ };
132
+ };
web/src/pages/add-knowledge/components/knowledge-setting/tag-table/index.tsx ADDED
@@ -0,0 +1,309 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import {
4
+ ColumnDef,
5
+ ColumnFiltersState,
6
+ SortingState,
7
+ VisibilityState,
8
+ flexRender,
9
+ getCoreRowModel,
10
+ getFilteredRowModel,
11
+ getPaginationRowModel,
12
+ getSortedRowModel,
13
+ useReactTable,
14
+ } from '@tanstack/react-table';
15
+ import { ArrowUpDown, Pencil, Trash2 } from 'lucide-react';
16
+ import * as React from 'react';
17
+
18
+ import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
19
+ import { Button } from '@/components/ui/button';
20
+ import { Checkbox } from '@/components/ui/checkbox';
21
+ import { Input } from '@/components/ui/input';
22
+ import {
23
+ Table,
24
+ TableBody,
25
+ TableCell,
26
+ TableHead,
27
+ TableHeader,
28
+ TableRow,
29
+ } from '@/components/ui/table';
30
+ import {
31
+ Tooltip,
32
+ TooltipContent,
33
+ TooltipProvider,
34
+ TooltipTrigger,
35
+ } from '@/components/ui/tooltip';
36
+ import { useDeleteTag, useFetchTagList } from '@/hooks/knowledge-hooks';
37
+ import { useCallback, useEffect, useState } from 'react';
38
+ import { useTranslation } from 'react-i18next';
39
+ import { useRenameKnowledgeTag } from '../hooks';
40
+ import { RenameDialog } from './rename-dialog';
41
+
42
+ export type ITag = {
43
+ tag: string;
44
+ frequency: number;
45
+ };
46
+
47
+ export function TagTable() {
48
+ const { t } = useTranslation();
49
+ const { list } = useFetchTagList();
50
+ const [tagList, setTagList] = useState<ITag[]>([]);
51
+
52
+ const [sorting, setSorting] = React.useState<SortingState>([]);
53
+ const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
54
+ [],
55
+ );
56
+ const [columnVisibility, setColumnVisibility] =
57
+ React.useState<VisibilityState>({});
58
+ const [rowSelection, setRowSelection] = useState({});
59
+
60
+ const { deleteTag } = useDeleteTag();
61
+
62
+ useEffect(() => {
63
+ setTagList(list.map((x) => ({ tag: x[0], frequency: x[1] })));
64
+ }, [list]);
65
+
66
+ const handleDeleteTag = useCallback(
67
+ (tags: string[]) => () => {
68
+ deleteTag(tags);
69
+ },
70
+ [deleteTag],
71
+ );
72
+
73
+ const {
74
+ showTagRenameModal,
75
+ hideTagRenameModal,
76
+ tagRenameVisible,
77
+ onTagRenameOk,
78
+ initialName,
79
+ } = useRenameKnowledgeTag();
80
+
81
+ const columns: ColumnDef<ITag>[] = [
82
+ {
83
+ id: 'select',
84
+ header: ({ table }) => (
85
+ <Checkbox
86
+ checked={
87
+ table.getIsAllPageRowsSelected() ||
88
+ (table.getIsSomePageRowsSelected() && 'indeterminate')
89
+ }
90
+ onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
91
+ aria-label="Select all"
92
+ />
93
+ ),
94
+ cell: ({ row }) => (
95
+ <Checkbox
96
+ checked={row.getIsSelected()}
97
+ onCheckedChange={(value) => row.toggleSelected(!!value)}
98
+ aria-label="Select row"
99
+ />
100
+ ),
101
+ enableSorting: false,
102
+ enableHiding: false,
103
+ },
104
+ {
105
+ accessorKey: 'tag',
106
+ header: ({ column }) => {
107
+ return (
108
+ <Button
109
+ variant="ghost"
110
+ onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
111
+ >
112
+ {t('knowledgeConfiguration.tag')}
113
+ <ArrowUpDown />
114
+ </Button>
115
+ );
116
+ },
117
+ cell: ({ row }) => {
118
+ const value: string = row.getValue('tag');
119
+ return <div>{value}</div>;
120
+ },
121
+ },
122
+ {
123
+ accessorKey: 'frequency',
124
+ header: ({ column }) => {
125
+ return (
126
+ <Button
127
+ variant="ghost"
128
+ onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
129
+ >
130
+ {t('knowledgeConfiguration.frequency')}
131
+ <ArrowUpDown />
132
+ </Button>
133
+ );
134
+ },
135
+ cell: ({ row }) => (
136
+ <div className="capitalize ">{row.getValue('frequency')}</div>
137
+ ),
138
+ },
139
+ {
140
+ id: 'actions',
141
+ enableHiding: false,
142
+ header: t('common.action'),
143
+ cell: ({ row }) => {
144
+ return (
145
+ <div className="flex gap-1">
146
+ <Tooltip>
147
+ <ConfirmDeleteDialog onOk={handleDeleteTag([row.original.tag])}>
148
+ <TooltipTrigger asChild>
149
+ <Button variant="ghost" size="icon">
150
+ <Trash2 />
151
+ </Button>
152
+ </TooltipTrigger>
153
+ </ConfirmDeleteDialog>
154
+ <TooltipContent>
155
+ <p>{t('common.delete')}</p>
156
+ </TooltipContent>
157
+ </Tooltip>
158
+ <Tooltip>
159
+ <TooltipTrigger asChild>
160
+ <Button
161
+ variant="ghost"
162
+ size="icon"
163
+ onClick={() => showTagRenameModal(row.original.tag)}
164
+ >
165
+ <Pencil />
166
+ </Button>
167
+ </TooltipTrigger>
168
+ <TooltipContent>
169
+ <p>{t('common.rename')}</p>
170
+ </TooltipContent>
171
+ </Tooltip>
172
+ </div>
173
+ );
174
+ },
175
+ },
176
+ ];
177
+
178
+ const table = useReactTable({
179
+ data: tagList,
180
+ columns,
181
+ onSortingChange: setSorting,
182
+ onColumnFiltersChange: setColumnFilters,
183
+ getCoreRowModel: getCoreRowModel(),
184
+ getPaginationRowModel: getPaginationRowModel(),
185
+ getSortedRowModel: getSortedRowModel(),
186
+ getFilteredRowModel: getFilteredRowModel(),
187
+ onColumnVisibilityChange: setColumnVisibility,
188
+ onRowSelectionChange: setRowSelection,
189
+ state: {
190
+ sorting,
191
+ columnFilters,
192
+ columnVisibility,
193
+ rowSelection,
194
+ },
195
+ });
196
+
197
+ const selectedRowLength = table.getFilteredSelectedRowModel().rows.length;
198
+
199
+ return (
200
+ <TooltipProvider>
201
+ <div className="w-full">
202
+ <div className="flex items-center justify-between py-4 ">
203
+ <Input
204
+ placeholder={t('knowledgeConfiguration.searchTags')}
205
+ value={(table.getColumn('tag')?.getFilterValue() as string) ?? ''}
206
+ onChange={(event) =>
207
+ table.getColumn('tag')?.setFilterValue(event.target.value)
208
+ }
209
+ className="w-1/2"
210
+ />
211
+ {selectedRowLength > 0 && (
212
+ <ConfirmDeleteDialog
213
+ onOk={handleDeleteTag(
214
+ table
215
+ .getFilteredSelectedRowModel()
216
+ .rows.map((x) => x.original.tag),
217
+ )}
218
+ >
219
+ <Button variant="outline" size="icon">
220
+ <Trash2 />
221
+ </Button>
222
+ </ConfirmDeleteDialog>
223
+ )}
224
+ </div>
225
+ <div className="rounded-md border">
226
+ <Table>
227
+ <TableHeader>
228
+ {table.getHeaderGroups().map((headerGroup) => (
229
+ <TableRow key={headerGroup.id}>
230
+ {headerGroup.headers.map((header) => {
231
+ return (
232
+ <TableHead key={header.id}>
233
+ {header.isPlaceholder
234
+ ? null
235
+ : flexRender(
236
+ header.column.columnDef.header,
237
+ header.getContext(),
238
+ )}
239
+ </TableHead>
240
+ );
241
+ })}
242
+ </TableRow>
243
+ ))}
244
+ </TableHeader>
245
+ <TableBody>
246
+ {table.getRowModel().rows?.length ? (
247
+ table.getRowModel().rows.map((row) => (
248
+ <TableRow
249
+ key={row.id}
250
+ data-state={row.getIsSelected() && 'selected'}
251
+ >
252
+ {row.getVisibleCells().map((cell) => (
253
+ <TableCell key={cell.id}>
254
+ {flexRender(
255
+ cell.column.columnDef.cell,
256
+ cell.getContext(),
257
+ )}
258
+ </TableCell>
259
+ ))}
260
+ </TableRow>
261
+ ))
262
+ ) : (
263
+ <TableRow>
264
+ <TableCell
265
+ colSpan={columns.length}
266
+ className="h-24 text-center"
267
+ >
268
+ No results.
269
+ </TableCell>
270
+ </TableRow>
271
+ )}
272
+ </TableBody>
273
+ </Table>
274
+ </div>
275
+ <div className="flex items-center justify-end space-x-2 py-4">
276
+ <div className="flex-1 text-sm text-muted-foreground">
277
+ {selectedRowLength} of {table.getFilteredRowModel().rows.length}{' '}
278
+ row(s) selected.
279
+ </div>
280
+ <div className="space-x-2">
281
+ <Button
282
+ variant="outline"
283
+ size="sm"
284
+ onClick={() => table.previousPage()}
285
+ disabled={!table.getCanPreviousPage()}
286
+ >
287
+ {t('common.previousPage')}
288
+ </Button>
289
+ <Button
290
+ variant="outline"
291
+ size="sm"
292
+ onClick={() => table.nextPage()}
293
+ disabled={!table.getCanNextPage()}
294
+ >
295
+ {t('common.nextPage')}
296
+ </Button>
297
+ </div>
298
+ </div>
299
+ </div>
300
+ {tagRenameVisible && (
301
+ <RenameDialog
302
+ hideModal={hideTagRenameModal}
303
+ onOk={onTagRenameOk}
304
+ initialName={initialName}
305
+ ></RenameDialog>
306
+ )}
307
+ </TooltipProvider>
308
+ );
309
+ }
web/src/pages/add-knowledge/components/knowledge-setting/tag-table/rename-dialog/index.tsx ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Button } from '@/components/ui/button';
2
+ import {
3
+ Dialog,
4
+ DialogContent,
5
+ DialogFooter,
6
+ DialogHeader,
7
+ DialogTitle,
8
+ } from '@/components/ui/dialog';
9
+ import { IModalProps } from '@/interfaces/common';
10
+ import { TagRenameId } from '@/pages/add-knowledge/constant';
11
+ import { useTranslation } from 'react-i18next';
12
+ import { RenameForm } from './rename-form';
13
+
14
+ export function RenameDialog({
15
+ hideModal,
16
+ initialName,
17
+ }: IModalProps<any> & { initialName: string }) {
18
+ const { t } = useTranslation();
19
+
20
+ return (
21
+ <Dialog open onOpenChange={hideModal}>
22
+ <DialogContent className="sm:max-w-[425px]">
23
+ <DialogHeader>
24
+ <DialogTitle>{t('common.rename')}</DialogTitle>
25
+ </DialogHeader>
26
+ <RenameForm
27
+ initialName={initialName}
28
+ hideModal={hideModal}
29
+ ></RenameForm>
30
+ <DialogFooter>
31
+ <Button type="submit" form={TagRenameId}>
32
+ {t('common.save')}
33
+ </Button>
34
+ </DialogFooter>
35
+ </DialogContent>
36
+ </Dialog>
37
+ );
38
+ }
web/src/pages/add-knowledge/components/knowledge-setting/tag-table/rename-dialog/rename-form.tsx ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { zodResolver } from '@hookform/resolvers/zod';
4
+ import { useForm } from 'react-hook-form';
5
+ import { z } from 'zod';
6
+
7
+ import {
8
+ Form,
9
+ FormControl,
10
+ FormField,
11
+ FormItem,
12
+ FormLabel,
13
+ FormMessage,
14
+ } from '@/components/ui/form';
15
+ import { Input } from '@/components/ui/input';
16
+ import { useRenameTag } from '@/hooks/knowledge-hooks';
17
+ import { IModalProps } from '@/interfaces/common';
18
+ import { TagRenameId } from '@/pages/add-knowledge/constant';
19
+ import { useEffect } from 'react';
20
+ import { useTranslation } from 'react-i18next';
21
+
22
+ export function RenameForm({
23
+ initialName,
24
+ hideModal,
25
+ }: IModalProps<any> & { initialName: string }) {
26
+ const { t } = useTranslation();
27
+ const FormSchema = z.object({
28
+ name: z
29
+ .string()
30
+ .min(1, {
31
+ message: t('common.namePlaceholder'),
32
+ })
33
+ .trim(),
34
+ });
35
+
36
+ const form = useForm<z.infer<typeof FormSchema>>({
37
+ resolver: zodResolver(FormSchema),
38
+ defaultValues: {
39
+ name: '',
40
+ },
41
+ });
42
+
43
+ const { renameTag } = useRenameTag();
44
+
45
+ async function onSubmit(data: z.infer<typeof FormSchema>) {
46
+ const ret = await renameTag({ fromTag: initialName, toTag: data.name });
47
+ if (ret) {
48
+ hideModal?.();
49
+ }
50
+ }
51
+
52
+ useEffect(() => {
53
+ form.setValue('name', initialName);
54
+ }, [form, initialName]);
55
+
56
+ return (
57
+ <Form {...form}>
58
+ <form
59
+ onSubmit={form.handleSubmit(onSubmit)}
60
+ className="space-y-6"
61
+ id={TagRenameId}
62
+ >
63
+ <FormField
64
+ control={form.control}
65
+ name="name"
66
+ render={({ field }) => (
67
+ <FormItem>
68
+ <FormLabel>{t('common.name')}</FormLabel>
69
+ <FormControl>
70
+ <Input
71
+ placeholder={t('common.namePlaceholder')}
72
+ {...field}
73
+ autoComplete="off"
74
+ />
75
+ </FormControl>
76
+ <FormMessage />
77
+ </FormItem>
78
+ )}
79
+ />
80
+ </form>
81
+ </Form>
82
+ );
83
+ }
web/src/pages/add-knowledge/constant.ts CHANGED
@@ -17,3 +17,5 @@ export const datasetRouteMap = {
17
  };
18
 
19
  export * from '@/constants/knowledge';
 
 
 
17
  };
18
 
19
  export * from '@/constants/knowledge';
20
+
21
+ export const TagRenameId = 'tagRename';
web/src/services/knowledge-service.ts CHANGED
@@ -1,6 +1,7 @@
 
1
  import api from '@/utils/api';
2
  import registerServer from '@/utils/register-server';
3
- import request from '@/utils/request';
4
 
5
  const {
6
  create_kb,
@@ -143,4 +144,15 @@ const methods = {
143
 
144
  const kbService = registerServer<keyof typeof methods>(methods, request);
145
 
 
 
 
 
 
 
 
 
 
 
 
146
  export default kbService;
 
1
+ import { IRenameTag } from '@/interfaces/database/knowledge';
2
  import api from '@/utils/api';
3
  import registerServer from '@/utils/register-server';
4
+ import request, { post } from '@/utils/request';
5
 
6
  const {
7
  create_kb,
 
144
 
145
  const kbService = registerServer<keyof typeof methods>(methods, request);
146
 
147
+ export const listTag = (knowledgeId: string) =>
148
+ request.get(api.listTag(knowledgeId));
149
+
150
+ export const removeTag = (knowledgeId: string, tags: string[]) =>
151
+ post(api.removeTag(knowledgeId), { tags });
152
+
153
+ export const renameTag = (
154
+ knowledgeId: string,
155
+ { fromTag, toTag }: IRenameTag,
156
+ ) => post(api.renameTag(knowledgeId), { fromTag, toTag });
157
+
158
  export default kbService;
web/src/utils/api.ts CHANGED
@@ -37,6 +37,12 @@ export default {
37
  rm_kb: `${api_host}/kb/rm`,
38
  get_kb_detail: `${api_host}/kb/detail`,
39
 
 
 
 
 
 
 
40
  // chunk
41
  chunk_list: `${api_host}/chunk/list`,
42
  create_chunk: `${api_host}/chunk/create`,
 
37
  rm_kb: `${api_host}/kb/rm`,
38
  get_kb_detail: `${api_host}/kb/detail`,
39
 
40
+ // tags
41
+ listTag: (knowledgeId: string) => `${api_host}/kb/${knowledgeId}/tags`,
42
+ removeTag: (knowledgeId: string) => `${api_host}/kb/${knowledgeId}/rm_tags`,
43
+ renameTag: (knowledgeId: string) =>
44
+ `${api_host}/kb/${knowledgeId}/rename_tag`,
45
+
46
  // chunk
47
  chunk_list: `${api_host}/chunk/list`,
48
  create_chunk: `${api_host}/chunk/create`,