瀏覽代碼

Feat: Add TagTable #4367 (#4368)

### What problem does this PR solve?

Feat: Add TagTable #4367

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
tags/v0.16.0
balibabu 10 月之前
父節點
當前提交
1d93eb81ae
沒有連結到貢獻者的電子郵件帳戶。

+ 2
- 10
web/src/components/auto-keywords-item.tsx 查看文件

<Slider max={30} style={{ width: '100%' }} /> <Slider max={30} style={{ width: '100%' }} />
</Form.Item> </Form.Item>
</Flex> </Flex>
<Form.Item
name={['parser_config', 'auto_keywords']}
noStyle
initialValue={0}
>
<Form.Item name={['parser_config', 'auto_keywords']} noStyle>
<InputNumber max={30} min={0} /> <InputNumber max={30} min={0} />
</Form.Item> </Form.Item>
</Flex> </Flex>
<Slider max={10} style={{ width: '100%' }} /> <Slider max={10} style={{ width: '100%' }} />
</Form.Item> </Form.Item>
</Flex> </Flex>
<Form.Item
name={['parser_config', 'auto_questions']}
noStyle
initialValue={0}
>
<Form.Item name={['parser_config', 'auto_questions']} noStyle>
<InputNumber max={10} min={0} /> <InputNumber max={10} min={0} />
</Form.Item> </Form.Item>
</Flex> </Flex>

+ 1
- 1
web/src/components/chunk-method-modal/hooks.ts 查看文件

return { parserList: nextParserList, handleChange, selectedTag }; return { parserList: nextParserList, handleChange, selectedTag };
}; };


const hideAutoKeywords = ['qa', 'table', 'resume', 'knowledge_graph'];
const hideAutoKeywords = ['qa', 'table', 'resume', 'knowledge_graph', 'tag'];


export const useShowAutoKeywords = () => { export const useShowAutoKeywords = () => {
const showAutoKeywords = useCallback((selectedTag: string) => { const showAutoKeywords = useCallback((selectedTag: string) => {

+ 5
- 1
web/src/components/confirm-delete-dialog.tsx 查看文件

export function ConfirmDeleteDialog({ export function ConfirmDeleteDialog({
children, children,
title, title,
onOk,
}: IProps & PropsWithChildren) { }: IProps & PropsWithChildren) {
const { t } = useTranslation(); const { t } = useTranslation();


</AlertDialogHeader> </AlertDialogHeader>
<AlertDialogFooter> <AlertDialogFooter>
<AlertDialogCancel>{t('common.cancel')}</AlertDialogCancel> <AlertDialogCancel>{t('common.cancel')}</AlertDialogCancel>
<AlertDialogAction className="bg-colors-background-functional-solid-danger text--colors-text-neutral-strong">
<AlertDialogAction
className="bg-colors-background-functional-solid-danger text--colors-text-neutral-strong"
onClick={onOk}
>
<Trash2 /> <Trash2 />
{t('common.ok')} {t('common.ok')}
</AlertDialogAction> </AlertDialogAction>

+ 0
- 1
web/src/components/max-token-number.tsx 查看文件

<Form.Item <Form.Item
name={['parser_config', 'chunk_token_num']} name={['parser_config', 'chunk_token_num']}
noStyle noStyle
initialValue={initialValue}
rules={[{ required: true, message: t('chunkTokenNumberMessage') }]} rules={[{ required: true, message: t('chunkTokenNumberMessage') }]}
> >
<InputNumber max={max} min={0} /> <InputNumber max={max} min={0} />

+ 1
- 6
web/src/components/page-rank.tsx 查看文件

<Slider max={100} style={{ width: '100%' }} /> <Slider max={100} style={{ width: '100%' }} />
</Form.Item> </Form.Item>
</Flex> </Flex>
<Form.Item
name={['pagerank']}
noStyle
initialValue={0}
rules={[{ required: true }]}
>
<Form.Item name={['pagerank']} noStyle rules={[{ required: true }]}>
<InputNumber max={100} min={0} /> <InputNumber max={100} min={0} />
</Form.Item> </Form.Item>
</Flex> </Flex>

+ 2
- 0
web/src/components/parse-configuration/index.tsx 查看文件

'one', 'one',
'picture', 'picture',
'knowledge_graph', 'knowledge_graph',
'qa',
'tag',
]; ];


export const showRaptorParseConfiguration = (parserId: string) => { export const showRaptorParseConfiguration = (parserId: string) => {

+ 81
- 2
web/src/hooks/knowledge-hooks.ts 查看文件

import { ResponsePostType } from '@/interfaces/database/base'; import { ResponsePostType } from '@/interfaces/database/base';
import { IKnowledge, ITestingResult } from '@/interfaces/database/knowledge';
import {
IKnowledge,
IRenameTag,
ITestingResult,
} from '@/interfaces/database/knowledge';
import i18n from '@/locales/config'; import i18n from '@/locales/config';
import kbService from '@/services/knowledge-service';
import kbService, {
listTag,
removeTag,
renameTag,
} from '@/services/knowledge-service';
import { import {
useInfiniteQuery, useInfiniteQuery,
useIsMutating, useIsMutating,
return status.at(-1) === 'success'; return status.at(-1) === 'success';
}; };
//#endregion //#endregion

//#region tags

export const useFetchTagList = () => {
const knowledgeBaseId = useKnowledgeBaseId();

const { data, isFetching: loading } = useQuery<Array<[string, number]>>({
queryKey: ['fetchTagList'],
initialData: [],
gcTime: 0, // https://tanstack.com/query/latest/docs/framework/react/guides/caching?from=reactQueryV3
queryFn: async () => {
const { data } = await listTag(knowledgeBaseId);
const list = data?.data || [];
return list;
},
});

return { list: data, loading };
};

export const useDeleteTag = () => {
const knowledgeBaseId = useKnowledgeBaseId();

const queryClient = useQueryClient();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: ['deleteTag'],
mutationFn: async (tags: string[]) => {
const { data } = await removeTag(knowledgeBaseId, tags);
if (data.code === 0) {
message.success(i18n.t(`message.deleted`));
queryClient.invalidateQueries({
queryKey: ['fetchTagList'],
});
}
return data?.data ?? [];
},
});

return { data, loading, deleteTag: mutateAsync };
};

export const useRenameTag = () => {
const knowledgeBaseId = useKnowledgeBaseId();

const queryClient = useQueryClient();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: ['deleteTag'],
mutationFn: async (params: IRenameTag) => {
const { data } = await renameTag(knowledgeBaseId, params);
if (data.code === 0) {
message.success(i18n.t(`message.modified`));
queryClient.invalidateQueries({
queryKey: ['fetchTagList'],
});
}
return data?.data ?? [];
},
});

return { data, loading, renameTag: mutateAsync };
};

//#endregion

+ 2
- 0
web/src/interfaces/database/knowledge.ts 查看文件

documents: ITestingDocument[]; documents: ITestingDocument[];
total: number; total: number;
} }

export type IRenameTag = { fromTag: string; toTag: string };

+ 5
- 0
web/src/locales/en.ts 查看文件

pleaseInput: 'Please input', pleaseInput: 'Please input',
submit: 'Submit', submit: 'Submit',
embedIntoSite: 'Embed into webpage', embedIntoSite: 'Embed into webpage',
previousPage: 'Previous',
nextPage: 'Next',
}, },
login: { login: {
login: 'Sign in', login: 'Sign in',
vietnamese: 'Vietnamese', vietnamese: 'Vietnamese',
pageRank: 'Page rank', pageRank: 'Page rank',
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.`, 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.`,
tag: 'Tag',
frequency: 'Frequency',
searchTags: 'Search tags',
}, },
chunk: { chunk: {
chunk: 'Chunk', chunk: 'Chunk',

+ 5
- 0
web/src/locales/zh-traditional.ts 查看文件

pleaseInput: '請輸入', pleaseInput: '請輸入',
submit: '提交', submit: '提交',
embedIntoSite: '嵌入網站', embedIntoSite: '嵌入網站',
previousPage: '上一頁',
nextPage: '下一頁',
}, },
login: { login: {
login: '登入', login: '登入',
pageRank: '頁面排名', pageRank: '頁面排名',
pageRankTip: `這用來提高相關性分數。所有檢索到的區塊的相關性得分將加上該數字。 pageRankTip: `這用來提高相關性分數。所有檢索到的區塊的相關性得分將加上該數字。
當您想要先搜尋給定的知識庫時,請設定比其他人更高的 pagerank 分數。`, 當您想要先搜尋給定的知識庫時,請設定比其他人更高的 pagerank 分數。`,
tag: '標籤',
frequency: '頻次',
searchTags: '搜尋標籤',
}, },
chunk: { chunk: {
chunk: '解析塊', chunk: '解析塊',

+ 5
- 0
web/src/locales/zh.ts 查看文件

pleaseInput: '请输入', pleaseInput: '请输入',
submit: '提交', submit: '提交',
embedIntoSite: '嵌入网站', embedIntoSite: '嵌入网站',
previousPage: '上一页',
nextPage: '下一页',
}, },
login: { login: {
login: '登录', login: '登录',
pageRank: '页面排名', pageRank: '页面排名',
pageRankTip: `这用于提高相关性得分。所有检索到的块的相关性得分将加上此数字。 pageRankTip: `这用于提高相关性得分。所有检索到的块的相关性得分将加上此数字。
当您想首先搜索给定的知识库时,请设置比其他知识库更高的 pagerank 得分。`, 当您想首先搜索给定的知识库时,请设置比其他知识库更高的 pagerank 得分。`,
tag: '标签',
frequency: '频次',
searchTags: '搜索标签',
}, },
chunk: { chunk: {
chunk: '解析块', chunk: '解析块',

+ 2
- 0
web/src/pages/add-knowledge/components/knowledge-setting/category-panel.tsx 查看文件

import camelCase from 'lodash/camelCase'; import camelCase from 'lodash/camelCase';
import { useMemo } from 'react'; import { useMemo } from 'react';
import styles from './index.less'; import styles from './index.less';
import { TagTable } from './tag-table';
import { ImageMap } from './utils'; import { ImageMap } from './utils';


const { Title, Text } = Typography; const { Title, Text } = Typography;
<SvgIcon name={'chunk-method/chunk-empty'} width={'100%'}></SvgIcon> <SvgIcon name={'chunk-method/chunk-empty'} width={'100%'}></SvgIcon>
</Empty> </Empty>
)} )}
{chunkMethod === 'tag' && <TagTable></TagTable>}
</section> </section>
); );
}; };

+ 44
- 1
web/src/pages/add-knowledge/components/knowledge-setting/hooks.ts 查看文件

import { LlmModelType } from '@/constants/knowledge'; import { LlmModelType } from '@/constants/knowledge';
import { useSetModalState } from '@/hooks/common-hooks';
import { import {
useFetchKnowledgeBaseConfiguration, useFetchKnowledgeBaseConfiguration,
useRenameTag,
useUpdateKnowledge, useUpdateKnowledge,
} from '@/hooks/knowledge-hooks'; } from '@/hooks/knowledge-hooks';
import { useSelectLlmOptionsByModelType } from '@/hooks/llm-hooks'; import { useSelectLlmOptionsByModelType } from '@/hooks/llm-hooks';
import { Form, UploadFile } from 'antd'; import { Form, UploadFile } from 'antd';
import { FormInstance } from 'antd/lib'; import { FormInstance } from 'antd/lib';
import pick from 'lodash/pick'; import pick from 'lodash/pick';
import { useCallback, useEffect } from 'react';
import { useCallback, useEffect, useState } from 'react';


export const useSubmitKnowledgeConfiguration = (form: FormInstance) => { export const useSubmitKnowledgeConfiguration = (form: FormInstance) => {
const { saveKnowledgeConfiguration, loading } = useUpdateKnowledge(); const { saveKnowledgeConfiguration, loading } = useUpdateKnowledge();


return { form, chunkMethod }; return { form, chunkMethod };
}; };

export const useRenameKnowledgeTag = () => {
const [tag, setTag] = useState<string>('');
const {
visible: tagRenameVisible,
hideModal: hideTagRenameModal,
showModal: showFileRenameModal,
} = useSetModalState();
const { renameTag, loading } = useRenameTag();

const onTagRenameOk = useCallback(
async (name: string) => {
const ret = await renameTag({
fromTag: tag,
toTag: name,
});

if (ret === 0) {
hideTagRenameModal();
}
},
[renameTag, tag, hideTagRenameModal],
);

const handleShowTagRenameModal = useCallback(
(record: string) => {
setTag(record);
showFileRenameModal();
},
[showFileRenameModal],
);

return {
renameLoading: loading,
initialName: tag,
onTagRenameOk,
tagRenameVisible,
hideTagRenameModal,
showTagRenameModal: handleShowTagRenameModal,
};
};

+ 309
- 0
web/src/pages/add-knowledge/components/knowledge-setting/tag-table/index.tsx 查看文件

'use client';

import {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
flexRender,
getCoreRowModel,
getFilteredRowModel,
getPaginationRowModel,
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import { ArrowUpDown, Pencil, Trash2 } from 'lucide-react';
import * as React from 'react';

import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import { Input } from '@/components/ui/input';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { useDeleteTag, useFetchTagList } from '@/hooks/knowledge-hooks';
import { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useRenameKnowledgeTag } from '../hooks';
import { RenameDialog } from './rename-dialog';

export type ITag = {
tag: string;
frequency: number;
};

export function TagTable() {
const { t } = useTranslation();
const { list } = useFetchTagList();
const [tagList, setTagList] = useState<ITag[]>([]);

const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[],
);
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = useState({});

const { deleteTag } = useDeleteTag();

useEffect(() => {
setTagList(list.map((x) => ({ tag: x[0], frequency: x[1] })));
}, [list]);

const handleDeleteTag = useCallback(
(tags: string[]) => () => {
deleteTag(tags);
},
[deleteTag],
);

const {
showTagRenameModal,
hideTagRenameModal,
tagRenameVisible,
onTagRenameOk,
initialName,
} = useRenameKnowledgeTag();

const columns: ColumnDef<ITag>[] = [
{
id: 'select',
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && 'indeterminate')
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: 'tag',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
{t('knowledgeConfiguration.tag')}
<ArrowUpDown />
</Button>
);
},
cell: ({ row }) => {
const value: string = row.getValue('tag');
return <div>{value}</div>;
},
},
{
accessorKey: 'frequency',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
{t('knowledgeConfiguration.frequency')}
<ArrowUpDown />
</Button>
);
},
cell: ({ row }) => (
<div className="capitalize ">{row.getValue('frequency')}</div>
),
},
{
id: 'actions',
enableHiding: false,
header: t('common.action'),
cell: ({ row }) => {
return (
<div className="flex gap-1">
<Tooltip>
<ConfirmDeleteDialog onOk={handleDeleteTag([row.original.tag])}>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon">
<Trash2 />
</Button>
</TooltipTrigger>
</ConfirmDeleteDialog>
<TooltipContent>
<p>{t('common.delete')}</p>
</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={() => showTagRenameModal(row.original.tag)}
>
<Pencil />
</Button>
</TooltipTrigger>
<TooltipContent>
<p>{t('common.rename')}</p>
</TooltipContent>
</Tooltip>
</div>
);
},
},
];

const table = useReactTable({
data: tagList,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
getCoreRowModel: getCoreRowModel(),
getPaginationRowModel: getPaginationRowModel(),
getSortedRowModel: getSortedRowModel(),
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
},
});

const selectedRowLength = table.getFilteredSelectedRowModel().rows.length;

return (
<TooltipProvider>
<div className="w-full">
<div className="flex items-center justify-between py-4 ">
<Input
placeholder={t('knowledgeConfiguration.searchTags')}
value={(table.getColumn('tag')?.getFilterValue() as string) ?? ''}
onChange={(event) =>
table.getColumn('tag')?.setFilterValue(event.target.value)
}
className="w-1/2"
/>
{selectedRowLength > 0 && (
<ConfirmDeleteDialog
onOk={handleDeleteTag(
table
.getFilteredSelectedRowModel()
.rows.map((x) => x.original.tag),
)}
>
<Button variant="outline" size="icon">
<Trash2 />
</Button>
</ConfirmDeleteDialog>
)}
</div>
<div className="rounded-md border">
<Table>
<TableHeader>
{table.getHeaderGroups().map((headerGroup) => (
<TableRow key={headerGroup.id}>
{headerGroup.headers.map((header) => {
return (
<TableHead key={header.id}>
{header.isPlaceholder
? null
: flexRender(
header.column.columnDef.header,
header.getContext(),
)}
</TableHead>
);
})}
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
data-state={row.getIsSelected() && 'selected'}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
)}
</TableCell>
))}
</TableRow>
))
) : (
<TableRow>
<TableCell
colSpan={columns.length}
className="h-24 text-center"
>
No results.
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</div>
<div className="flex items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-muted-foreground">
{selectedRowLength} of {table.getFilteredRowModel().rows.length}{' '}
row(s) selected.
</div>
<div className="space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
{t('common.previousPage')}
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
{t('common.nextPage')}
</Button>
</div>
</div>
</div>
{tagRenameVisible && (
<RenameDialog
hideModal={hideTagRenameModal}
onOk={onTagRenameOk}
initialName={initialName}
></RenameDialog>
)}
</TooltipProvider>
);
}

+ 38
- 0
web/src/pages/add-knowledge/components/knowledge-setting/tag-table/rename-dialog/index.tsx 查看文件

import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { IModalProps } from '@/interfaces/common';
import { TagRenameId } from '@/pages/add-knowledge/constant';
import { useTranslation } from 'react-i18next';
import { RenameForm } from './rename-form';

export function RenameDialog({
hideModal,
initialName,
}: IModalProps<any> & { initialName: string }) {
const { t } = useTranslation();

return (
<Dialog open onOpenChange={hideModal}>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
<DialogTitle>{t('common.rename')}</DialogTitle>
</DialogHeader>
<RenameForm
initialName={initialName}
hideModal={hideModal}
></RenameForm>
<DialogFooter>
<Button type="submit" form={TagRenameId}>
{t('common.save')}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

+ 83
- 0
web/src/pages/add-knowledge/components/knowledge-setting/tag-table/rename-dialog/rename-form.tsx 查看文件

'use client';

import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { z } from 'zod';

import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { useRenameTag } from '@/hooks/knowledge-hooks';
import { IModalProps } from '@/interfaces/common';
import { TagRenameId } from '@/pages/add-knowledge/constant';
import { useEffect } from 'react';
import { useTranslation } from 'react-i18next';

export function RenameForm({
initialName,
hideModal,
}: IModalProps<any> & { initialName: string }) {
const { t } = useTranslation();
const FormSchema = z.object({
name: z
.string()
.min(1, {
message: t('common.namePlaceholder'),
})
.trim(),
});

const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: {
name: '',
},
});

const { renameTag } = useRenameTag();

async function onSubmit(data: z.infer<typeof FormSchema>) {
const ret = await renameTag({ fromTag: initialName, toTag: data.name });
if (ret) {
hideModal?.();
}
}

useEffect(() => {
form.setValue('name', initialName);
}, [form, initialName]);

return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-6"
id={TagRenameId}
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('common.name')}</FormLabel>
<FormControl>
<Input
placeholder={t('common.namePlaceholder')}
{...field}
autoComplete="off"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
);
}

+ 2
- 0
web/src/pages/add-knowledge/constant.ts 查看文件

}; };


export * from '@/constants/knowledge'; export * from '@/constants/knowledge';

export const TagRenameId = 'tagRename';

+ 13
- 1
web/src/services/knowledge-service.ts 查看文件

import { IRenameTag } from '@/interfaces/database/knowledge';
import api from '@/utils/api'; import api from '@/utils/api';
import registerServer from '@/utils/register-server'; import registerServer from '@/utils/register-server';
import request from '@/utils/request';
import request, { post } from '@/utils/request';


const { const {
create_kb, create_kb,


const kbService = registerServer<keyof typeof methods>(methods, request); const kbService = registerServer<keyof typeof methods>(methods, request);


export const listTag = (knowledgeId: string) =>
request.get(api.listTag(knowledgeId));

export const removeTag = (knowledgeId: string, tags: string[]) =>
post(api.removeTag(knowledgeId), { tags });

export const renameTag = (
knowledgeId: string,
{ fromTag, toTag }: IRenameTag,
) => post(api.renameTag(knowledgeId), { fromTag, toTag });

export default kbService; export default kbService;

+ 6
- 0
web/src/utils/api.ts 查看文件

rm_kb: `${api_host}/kb/rm`, rm_kb: `${api_host}/kb/rm`,
get_kb_detail: `${api_host}/kb/detail`, get_kb_detail: `${api_host}/kb/detail`,


// tags
listTag: (knowledgeId: string) => `${api_host}/kb/${knowledgeId}/tags`,
removeTag: (knowledgeId: string) => `${api_host}/kb/${knowledgeId}/rm_tags`,
renameTag: (knowledgeId: string) =>
`${api_host}/kb/${knowledgeId}/rename_tag`,

// chunk // chunk
chunk_list: `${api_host}/chunk/list`, chunk_list: `${api_host}/chunk/list`,
create_chunk: `${api_host}/chunk/create`, create_chunk: `${api_host}/chunk/create`,

Loading…
取消
儲存