mirror of
https://github.com/RYDE-WORK/Langchain-Chatchat.git
synced 2026-01-19 21:37:20 +08:00
Merge pull request #3741 from cca313/feature/frontend-knowledgebase
Feature/frontend knowledgebase UI
This commit is contained in:
commit
c6b92bc4d0
@ -0,0 +1,35 @@
|
||||
import { DeleteOutlined, EditOutlined } from '@ant-design/icons';
|
||||
import { Card, Skeleton } from 'antd';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import React, { useState } from 'react';
|
||||
|
||||
const { Meta } = Card;
|
||||
|
||||
interface KnowLedgeCardProps {
|
||||
intro: string;
|
||||
name: string;
|
||||
}
|
||||
const KnowledgeCard: React.FC = (props: KnowLedgeCardProps) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { name, intro } = props;
|
||||
const router = useRouter();
|
||||
const handleCardEditClick = () => {
|
||||
router.push('/knowledge/1/base');
|
||||
};
|
||||
return (
|
||||
<Card
|
||||
actions={[
|
||||
<EditOutlined key="edit" onClick={handleCardEditClick} />,
|
||||
<DeleteOutlined key="ellipsis" />,
|
||||
]}
|
||||
bordered={false}
|
||||
style={{ marginTop: 16, width: 300 }}
|
||||
>
|
||||
<Skeleton active avatar loading={loading}>
|
||||
<Meta description={intro} title={name} />
|
||||
</Skeleton>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default KnowledgeCard;
|
||||
@ -0,0 +1,25 @@
|
||||
import React, { memo } from 'react';
|
||||
|
||||
import KnowledgeCard from './KnowledgeCard';
|
||||
|
||||
const list = [
|
||||
{ intro: '知识库简介', name: '知识库名称' },
|
||||
{ intro: '知识库简介', name: '知识库名称' },
|
||||
{ intro: '知识库简介', name: '知识库名称' },
|
||||
{ intro: '知识库简介', name: '知识库名称' },
|
||||
{ intro: '知识库简介', name: '知识库名称' },
|
||||
{ intro: '知识库简介', name: '知识库名称' },
|
||||
{ intro: '知识库简介', name: '知识库名称' },
|
||||
];
|
||||
|
||||
const RenderList = memo(() =>
|
||||
list.map((item, index) => {
|
||||
return <KnowledgeCard key={index} {...item} />;
|
||||
}),
|
||||
);
|
||||
|
||||
const KnowledgeCardList = memo(() => {
|
||||
return <RenderList />;
|
||||
});
|
||||
|
||||
export default KnowledgeCardList;
|
||||
@ -0,0 +1,61 @@
|
||||
import { Modal, type ModalProps } from '@lobehub/ui';
|
||||
import { Form, Input, Select } from 'antd';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
const DEFAULT_FIELD_VALUE = {
|
||||
// imageType: ImageType.JPG,
|
||||
withBackground: true,
|
||||
withFooter: false,
|
||||
withPluginInfo: false,
|
||||
withSystemRole: false,
|
||||
};
|
||||
interface ModalCreateKnowledgeProps extends ModalProps {
|
||||
toggleModal: (open: boolean) => void;
|
||||
}
|
||||
const CreateKnowledgeBase = memo<ModalCreateKnowledgeProps>(({ toggleModal, open }) => {
|
||||
const { t } = useTranslation('chat');
|
||||
|
||||
return (
|
||||
<Modal
|
||||
allowFullscreen
|
||||
centered={false}
|
||||
maxHeight={false}
|
||||
onCancel={() => toggleModal(false)}
|
||||
onOk={() => toggleModal(false)}
|
||||
open={open}
|
||||
title="创建知识库"
|
||||
>
|
||||
<Form initialValues={DEFAULT_FIELD_VALUE} layout="vertical">
|
||||
<Form.Item label="知识库名称" name="base_name">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label="知识库简介" name="base_intro">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Flexbox direction="horizontal" gap={10} justify="space-between">
|
||||
<div style={{ flex: '1' }}>
|
||||
<Form.Item label="向量库类型" name="base_type">
|
||||
<Select>
|
||||
<Select.Option value="0">文本</Select.Option>
|
||||
<Select.Option value="1">图片</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</div>
|
||||
<div style={{ flex: '1' }}>
|
||||
<Form.Item label="Embedding模型" name="embedding_model">
|
||||
<Select>
|
||||
<Select.Option value="0">Bert</Select.Option>
|
||||
<Select.Option value="1">Word2Vec</Select.Option>
|
||||
<Select.Option value="2">FastText</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</Flexbox>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
export default CreateKnowledgeBase;
|
||||
31
frontend/src/app/knowledge/(desktop)/index.tsx
Normal file
31
frontend/src/app/knowledge/(desktop)/index.tsx
Normal file
@ -0,0 +1,31 @@
|
||||
'use client';
|
||||
|
||||
import { FloatButton } from 'antd';
|
||||
import { Plus } from 'lucide-react';
|
||||
import dynamic from 'next/dynamic';
|
||||
import { memo, useState } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import KnowledgeCardList from './features/KnowledgeList';
|
||||
// import CreateKnowledgeBase from './features/createKnowledgeBase';
|
||||
import Layout from './layout.desktop';
|
||||
|
||||
const ModalCreateKnowledge = dynamic(() => import('./features/ModalCreateKnowledge'));
|
||||
|
||||
const DesktopPage = memo(() => {
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
return (
|
||||
<>
|
||||
<Layout>
|
||||
<Flexbox gap={20} horizontal justify="flex-start" wrap="wrap">
|
||||
<KnowledgeCardList />
|
||||
</Flexbox>
|
||||
<FloatButton icon={<Plus />} onClick={() => setShowModal(true)}>
|
||||
新建知识库
|
||||
</FloatButton>
|
||||
</Layout>
|
||||
<ModalCreateKnowledge open={showModal} toggleModal={setShowModal} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
export default DesktopPage;
|
||||
22
frontend/src/app/knowledge/(desktop)/layout.desktop.tsx
Normal file
22
frontend/src/app/knowledge/(desktop)/layout.desktop.tsx
Normal file
@ -0,0 +1,22 @@
|
||||
'use client';
|
||||
|
||||
import { PropsWithChildren, memo } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import AppLayoutDesktop from '@/layout/AppLayout.desktop';
|
||||
import { SidebarTabKey } from '@/store/global/initialState';
|
||||
|
||||
export default memo(({ children }: PropsWithChildren) => {
|
||||
return (
|
||||
<AppLayoutDesktop sidebarKey={SidebarTabKey.Knowledge}>
|
||||
<Flexbox
|
||||
flex={1}
|
||||
height={'100%'}
|
||||
id={'lobe-conversion-container'}
|
||||
style={{ paddingLeft: 20, paddingRight: 20, position: 'relative' }}
|
||||
>
|
||||
{children}
|
||||
</Flexbox>
|
||||
</AppLayoutDesktop>
|
||||
);
|
||||
});
|
||||
@ -0,0 +1,22 @@
|
||||
import { Input, Modal } from 'antd';
|
||||
import { memo } from 'react';
|
||||
import { Center, Flexbox } from 'react-layout-kit';
|
||||
|
||||
type ModalSegmentProps = {
|
||||
open: boolean;
|
||||
toggleOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
const ModalSegment = memo<ModalSegmentProps>(({ open, toggleOpen }) => {
|
||||
return (
|
||||
<Modal okText="确认修改" onCancel={() => toggleOpen(false)} open={open} title="知识片段">
|
||||
<Flexbox padding={20}>
|
||||
<Center>
|
||||
<Input.TextArea autoSize={{ maxRows: 15, minRows: 10 }} style={{ width: 600 }} />
|
||||
</Center>
|
||||
</Flexbox>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
export default ModalSegment;
|
||||
75
frontend/src/app/knowledge/[id]/base/[fileId]/page.tsx
Normal file
75
frontend/src/app/knowledge/[id]/base/[fileId]/page.tsx
Normal file
@ -0,0 +1,75 @@
|
||||
'use client';
|
||||
|
||||
import { Card, List } from 'antd';
|
||||
import { createStyles } from 'antd-style';
|
||||
import dynamic from 'next/dynamic';
|
||||
import React, { memo, useState } from 'react';
|
||||
|
||||
const ModalSegment = dynamic(() => import('./features/ModalSegment'));
|
||||
|
||||
const data = [
|
||||
{
|
||||
title: 'Title 1',
|
||||
},
|
||||
{
|
||||
title: 'Title 2',
|
||||
},
|
||||
{
|
||||
title: 'Title 3',
|
||||
},
|
||||
{
|
||||
title: 'Title 4',
|
||||
},
|
||||
{
|
||||
title: 'Title 5',
|
||||
},
|
||||
{
|
||||
title: 'Title 6',
|
||||
},
|
||||
];
|
||||
const useStyle = createStyles(({ css, token }) => ({
|
||||
card: css`
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 1px ${token.colorText};
|
||||
}
|
||||
|
||||
&:active {
|
||||
scale: 0.95;
|
||||
}
|
||||
`,
|
||||
}));
|
||||
|
||||
const App = memo(() => {
|
||||
const { styles } = useStyle();
|
||||
const [isModalOpen, toggleOpen] = useState(false);
|
||||
console.log(toggleOpen);
|
||||
const handleSegmentCardClick = () => {
|
||||
toggleOpen(true);
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<List
|
||||
dataSource={data}
|
||||
grid={{
|
||||
gutter: 16,
|
||||
lg: 2,
|
||||
xl: 2,
|
||||
xxl: 2,
|
||||
}}
|
||||
renderItem={() => (
|
||||
<List.Item>
|
||||
<Card className={styles.card} onClick={handleSegmentCardClick}>
|
||||
Card content
|
||||
</Card>
|
||||
</List.Item>
|
||||
)}
|
||||
size="large"
|
||||
/>
|
||||
<ModalSegment open={isModalOpen} toggleOpen={toggleOpen} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default App;
|
||||
@ -0,0 +1,50 @@
|
||||
import { InboxOutlined } from '@ant-design/icons';
|
||||
import type { UploadProps } from 'antd';
|
||||
import { Modal, Upload, message } from 'antd';
|
||||
import React, { memo } from 'react';
|
||||
|
||||
const { Dragger } = Upload;
|
||||
|
||||
type ModalAddFileProps = {
|
||||
open: boolean;
|
||||
setModalOpen: (open: boolean) => void;
|
||||
};
|
||||
|
||||
const props: UploadProps = {
|
||||
action: 'https://660d2bd96ddfa2943b33731c.mockapi.io/api/upload',
|
||||
multiple: true,
|
||||
name: 'file',
|
||||
onChange(info) {
|
||||
const { status } = info.file;
|
||||
if (status !== 'uploading') {
|
||||
console.log(info.file, info.fileList);
|
||||
}
|
||||
if (status === 'done') {
|
||||
message.success(`${info.file.name} file uploaded successfully.`);
|
||||
} else if (status === 'error') {
|
||||
message.error(`${info.file.name} file upload failed.`);
|
||||
}
|
||||
},
|
||||
onDrop(e) {
|
||||
console.log('Dropped files', e.dataTransfer.files);
|
||||
},
|
||||
};
|
||||
|
||||
const ModalAddFile = memo<ModalAddFileProps>(({ open, setModalOpen }) => {
|
||||
return (
|
||||
<Modal onCancel={() => setModalOpen(false)} open={open} title="添加文件">
|
||||
<Dragger {...props}>
|
||||
<p className="ant-upload-drag-icon">
|
||||
<InboxOutlined />
|
||||
</p>
|
||||
<p className="ant-upload-text">Click or drag file to this area to upload</p>
|
||||
<p className="ant-upload-hint">
|
||||
Support for a single or bulk upload. Strictly prohibited from uploading company data or
|
||||
other banned files.
|
||||
</p>
|
||||
</Dragger>
|
||||
</Modal>
|
||||
);
|
||||
});
|
||||
|
||||
export default ModalAddFile;
|
||||
112
frontend/src/app/knowledge/[id]/base/page.tsx
Normal file
112
frontend/src/app/knowledge/[id]/base/page.tsx
Normal file
@ -0,0 +1,112 @@
|
||||
'use client';
|
||||
|
||||
import { Button, Table } from 'antd';
|
||||
import type { TableColumnsType } from 'antd';
|
||||
import dynamic from 'next/dynamic';
|
||||
import Link from 'next/link';
|
||||
import React, { useState } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
// import ModalAddFile from './features/ModalAddFile';
|
||||
const ModalAddFile = dynamic(() => import('./features/ModalAddFile'));
|
||||
interface DataType {
|
||||
address: string;
|
||||
age: number;
|
||||
key: React.Key;
|
||||
name: string;
|
||||
}
|
||||
|
||||
const data: DataType[] = [];
|
||||
for (let i = 0; i < 46; i++) {
|
||||
data.push({
|
||||
address: `London, Park Lane no. ${i}`,
|
||||
age: 32,
|
||||
index: i,
|
||||
key: i,
|
||||
name: `Edward King ${i}`,
|
||||
});
|
||||
}
|
||||
|
||||
const App: React.FC<{ params }> = ({ params }) => {
|
||||
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isShowModal, setModal] = useState(false);
|
||||
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
|
||||
console.log('selectedRowKeys changed:', newSelectedRowKeys);
|
||||
setSelectedRowKeys(newSelectedRowKeys);
|
||||
};
|
||||
const columns: TableColumnsType<DataType> = [
|
||||
{
|
||||
dataIndex: 'index',
|
||||
title: '序号',
|
||||
},
|
||||
{
|
||||
dataIndex: 'name',
|
||||
render: (text) => <Link href={`/knowledge/${params.id}/base/2`}>{text}</Link>,
|
||||
title: '文档名称',
|
||||
},
|
||||
{
|
||||
dataIndex: 'loader',
|
||||
title: '文档加载器',
|
||||
},
|
||||
{
|
||||
dataIndex: 'loader',
|
||||
title: '文档加载器',
|
||||
},
|
||||
{
|
||||
dataIndex: 'splitter',
|
||||
title: '分词器',
|
||||
},
|
||||
{
|
||||
dataIndex: 'source',
|
||||
title: '源文件',
|
||||
},
|
||||
{
|
||||
dataIndex: 'vector',
|
||||
title: '向量库',
|
||||
},
|
||||
];
|
||||
const rowSelection = {
|
||||
onChange: onSelectChange,
|
||||
selectedRowKeys,
|
||||
};
|
||||
const hasSelected = selectedRowKeys.length > 0;
|
||||
console.log(params);
|
||||
return (
|
||||
<>
|
||||
<Flexbox width={'100%'}>
|
||||
<Flexbox direction="horizontal" justify="space-between" style={{ marginBottom: 16 }}>
|
||||
<Flexbox direction="horizontal" gap={20}>
|
||||
<Button disabled={!hasSelected} loading={loading} type="default">
|
||||
下载选中文档
|
||||
</Button>
|
||||
<Button disabled={!hasSelected} loading={loading} type="default">
|
||||
重新添加至向量库
|
||||
</Button>
|
||||
<Button disabled={!hasSelected} loading={loading} type="default">
|
||||
向量库删除
|
||||
</Button>
|
||||
<Button disabled={!hasSelected} loading={loading} type="default">
|
||||
从知识库中删除
|
||||
</Button>
|
||||
</Flexbox>
|
||||
<div>
|
||||
<Button loading={loading} onClick={() => setModal(true)} type="primary">
|
||||
添加文件
|
||||
</Button>
|
||||
</div>
|
||||
</Flexbox>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
rowSelection={rowSelection}
|
||||
size="middle"
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Flexbox>
|
||||
<ModalAddFile open={isShowModal} setModalOpen={setModal} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
76
frontend/src/app/knowledge/[id]/config/page.tsx
Normal file
76
frontend/src/app/knowledge/[id]/config/page.tsx
Normal file
@ -0,0 +1,76 @@
|
||||
'use client';
|
||||
|
||||
import { Form, type ItemGroup } from '@lobehub/ui';
|
||||
import { Form as AntForm, Button, Input, InputNumber, Switch } from 'antd';
|
||||
import { Settings } from 'lucide-react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { Flexbox } from 'react-layout-kit';
|
||||
|
||||
import { FORM_STYLE } from '@/const/layoutTokens';
|
||||
|
||||
const KnowledgeBaseConfig = memo(() => {
|
||||
const [form] = AntForm.useForm();
|
||||
|
||||
const handleConfigChange = useCallback(async () => {
|
||||
try {
|
||||
const values = await form.validateFields();
|
||||
console.log('Success:', values);
|
||||
} catch (errorInfo) {
|
||||
console.log('Failed:', errorInfo);
|
||||
}
|
||||
}, [form]);
|
||||
const system: ItemGroup = {
|
||||
children: [
|
||||
{
|
||||
children: <Input placeholder={'请为知识库命名'} />,
|
||||
label: '知识库名称',
|
||||
name: 'name',
|
||||
rules: [{ message: '请输入知识库名称', required: true }],
|
||||
},
|
||||
{
|
||||
children: <Input.TextArea placeholder={'请简单介绍你的知识库'} />,
|
||||
label: '知识库简介',
|
||||
name: 'intro',
|
||||
rules: [{ message: '请输入知识库简介', required: true }],
|
||||
},
|
||||
{
|
||||
children: <InputNumber placeholder={'请输入数字'} style={{ width: 200 }} />,
|
||||
label: '单段文本最大长度',
|
||||
name: 'paragraphMaxLength',
|
||||
rules: [{ message: '请输入知识库名称', required: true }],
|
||||
},
|
||||
{
|
||||
children: <InputNumber placeholder={'请输入数字'} style={{ width: 200 }} />,
|
||||
label: '相邻文本重合长度',
|
||||
name: 'paragraphOverlapLength',
|
||||
rules: [{ message: '请输入知识库名称', required: true }],
|
||||
},
|
||||
{
|
||||
children: <InputNumber style={{ width: 200 }} />,
|
||||
label: '文本匹配条数',
|
||||
name: 'paragraphMatchCount',
|
||||
rules: [{ message: '请输入知识库名称', required: true }],
|
||||
},
|
||||
{
|
||||
children: <Switch style={{ width: 50 }} />,
|
||||
label: '开启中文标题加强',
|
||||
name: 'chineseTitleEnhance',
|
||||
},
|
||||
],
|
||||
icon: Settings,
|
||||
title: '知识库设置',
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Form form={form} items={[system]} onValuesChange={handleConfigChange} {...FORM_STYLE} />
|
||||
<Flexbox padding={50}>
|
||||
<Button size="large" style={{ width: 400 }}>
|
||||
保存
|
||||
</Button>
|
||||
</Flexbox>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
export default KnowledgeBaseConfig;
|
||||
39
frontend/src/app/knowledge/[id]/layout.tsx
Normal file
39
frontend/src/app/knowledge/[id]/layout.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
'use client';
|
||||
|
||||
import { PropsWithChildren, memo } from 'react';
|
||||
import { Center, Flexbox } from 'react-layout-kit';
|
||||
|
||||
import AppLayoutDesktop from '@/layout/AppLayout.desktop';
|
||||
import { SidebarTabKey } from '@/store/global/initialState';
|
||||
|
||||
import KnowledgeTabs from './tabs';
|
||||
|
||||
interface LayoutProps extends PropsWithChildren {
|
||||
params: Record<string, string>;
|
||||
}
|
||||
export default memo<LayoutProps>(({ children, params }) => {
|
||||
console.log(params);
|
||||
|
||||
return (
|
||||
<AppLayoutDesktop sidebarKey={SidebarTabKey.Knowledge}>
|
||||
<Flexbox direction="horizontal" flex={1} gap={40} height={'100%'}>
|
||||
<Flexbox
|
||||
direction="vertical"
|
||||
gap={12}
|
||||
padding={20}
|
||||
style={{ borderInlineEnd: '1px solid #333333' }}
|
||||
>
|
||||
<Flexbox padding={10}>
|
||||
<Center>图标占位</Center>
|
||||
<Center>知识库名称</Center>
|
||||
</Flexbox>
|
||||
|
||||
<KnowledgeTabs params={params} />
|
||||
</Flexbox>
|
||||
<Flexbox padding={40} width={'100%'}>
|
||||
<Center>{children}</Center>
|
||||
</Flexbox>
|
||||
</Flexbox>
|
||||
</AppLayoutDesktop>
|
||||
);
|
||||
});
|
||||
50
frontend/src/app/knowledge/[id]/tabs/TabItem.tsx
Normal file
50
frontend/src/app/knowledge/[id]/tabs/TabItem.tsx
Normal file
@ -0,0 +1,50 @@
|
||||
import { Icon, List } from '@lobehub/ui';
|
||||
import { createStyles, useResponsive } from 'antd-style';
|
||||
import { ChevronRight, type LucideIcon } from 'lucide-react';
|
||||
import { CSSProperties, ReactNode, memo } from 'react';
|
||||
|
||||
const { Item } = List;
|
||||
|
||||
const useStyles = createStyles(({ css, token, responsive }) => ({
|
||||
container: css`
|
||||
position: relative;
|
||||
padding-top: 16px;
|
||||
padding-bottom: 16px;
|
||||
border-radius: ${token.borderRadius}px;
|
||||
${responsive.mobile} {
|
||||
border-radius: 0;
|
||||
}
|
||||
`,
|
||||
noHover: css`
|
||||
pointer-events: none;
|
||||
`,
|
||||
}));
|
||||
|
||||
export interface ItemProps {
|
||||
active?: boolean;
|
||||
className?: string;
|
||||
hoverable?: boolean;
|
||||
icon: LucideIcon;
|
||||
label: ReactNode;
|
||||
style?: CSSProperties;
|
||||
}
|
||||
|
||||
const KnowledgeTabItem = memo<ItemProps>(
|
||||
({ label, icon, hoverable = true, active = false, style, className }) => {
|
||||
const { cx, styles } = useStyles();
|
||||
const { mobile } = useResponsive();
|
||||
return (
|
||||
<Item
|
||||
active={active}
|
||||
avatar={<Icon icon={icon} size={{ fontSize: 20 }} />}
|
||||
className={cx(styles.container, !hoverable && styles.noHover, className)}
|
||||
style={style}
|
||||
title={label as string}
|
||||
>
|
||||
{mobile && <Icon icon={ChevronRight} size={{ fontSize: 16 }} />}
|
||||
</Item>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
export default KnowledgeTabItem;
|
||||
36
frontend/src/app/knowledge/[id]/tabs/index.tsx
Normal file
36
frontend/src/app/knowledge/[id]/tabs/index.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import { Settings2, Webhook } from 'lucide-react';
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { memo, useState } from 'react';
|
||||
|
||||
import Item from './TabItem';
|
||||
|
||||
export enum KnowledgeTabs {
|
||||
Base = 'base',
|
||||
Config = 'config',
|
||||
}
|
||||
|
||||
export interface KnowledgeTabsProps {
|
||||
activeTab?: KnowledgeTabs;
|
||||
params: Record<string, string>;
|
||||
}
|
||||
|
||||
const KnowledgeTabsBox = memo<KnowledgeTabsProps>(({ params }) => {
|
||||
console.log(params);
|
||||
const [activeTab, setActiveTab] = useState<KnowledgeTabs>(KnowledgeTabs.Base);
|
||||
const items = [
|
||||
{ icon: Webhook, label: '知识库', value: KnowledgeTabs.Base },
|
||||
{ icon: Settings2, label: '配置', value: KnowledgeTabs.Config },
|
||||
];
|
||||
const router = useRouter();
|
||||
const handleTabClick = (value: KnowledgeTabs) => {
|
||||
setActiveTab(value);
|
||||
router.push(`/knowledge/${params.id}/${value}`);
|
||||
};
|
||||
return items.map(({ value, icon, label }) => (
|
||||
<div aria-label={label} key={value} onClick={() => handleTabClick(value)}>
|
||||
<Item active={activeTab === value} hoverable icon={icon} label={label} />
|
||||
</div>
|
||||
));
|
||||
});
|
||||
|
||||
export default KnowledgeTabsBox;
|
||||
16
frontend/src/app/knowledge/page.tsx
Normal file
16
frontend/src/app/knowledge/page.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import DesktopPage from './(desktop)';
|
||||
|
||||
// import MobilePage from './(mobile)';
|
||||
// import SessionHydration from './components/SessionHydration';
|
||||
// import Migration from './features/Migration';
|
||||
|
||||
const Page = () => {
|
||||
// const mobile = isMobileDevice();
|
||||
|
||||
// const Page = mobile ? MobilePage : DesktopPage;
|
||||
const Page = DesktopPage;
|
||||
|
||||
return <Page />;
|
||||
};
|
||||
|
||||
export default Page;
|
||||
@ -1,5 +1,5 @@
|
||||
import { ActionIcon } from '@lobehub/ui';
|
||||
import { Compass, MessageSquare } from 'lucide-react';
|
||||
import { Compass, Library, MessageSquare } from 'lucide-react';
|
||||
import Link from 'next/link';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -43,6 +43,15 @@ const TopActions = memo<TopActionProps>(({ tab }) => {
|
||||
title={t('tab.market')}
|
||||
/>
|
||||
</Link>
|
||||
<Link aria-label={'知识库'} href={'/knowledge'}>
|
||||
<ActionIcon
|
||||
active={tab === SidebarTabKey.Knowledge}
|
||||
icon={Library}
|
||||
placement={'right'}
|
||||
size="large"
|
||||
title={'知识库'}
|
||||
/>
|
||||
</Link>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@ -2,6 +2,7 @@ import { AppRouterInstance } from 'next/dist/shared/lib/app-router-context.share
|
||||
|
||||
export enum SidebarTabKey {
|
||||
Chat = 'chat',
|
||||
Knowledge = 'knowledge',
|
||||
Market = 'market',
|
||||
Setting = 'settings',
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user