UI设计和界面完成

This commit is contained in:
cca313 2024-04-10 17:54:54 +08:00
parent 3d5d1a00fa
commit 14dc3f1394
15 changed files with 588 additions and 11 deletions

View File

@ -0,0 +1,78 @@
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';
// export const imageTypeOptions: SegmentedProps['options'] = [
// {
// label: 'JPG',
// value: ImageType.JPG,
// },
// {
// label: 'PNG',
// value: ImageType.PNG,
// },
// {
// label: 'SVG',
// value: ImageType.SVG,
// },
// {
// label: 'WEBP',
// value: ImageType.WEBP,
// },
// ];
const DEFAULT_FIELD_VALUE = {
// imageType: ImageType.JPG,
withBackground: true,
withFooter: false,
withPluginInfo: false,
withSystemRole: false,
};
const CreateKnowledgeBase = memo<ModalProps>(({ onClose, open }) => {
const { t } = useTranslation('chat');
return (
<Modal
allowFullscreen
centered={false}
maxHeight={false}
onCancel={onClose}
onOk={onClose}
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;

View File

@ -1,22 +1,38 @@
'use client';
import { memo } from 'react';
import { FloatButton } from 'antd';
import { Plus } from 'lucide-react';
import dynamic from 'next/dynamic';
import { memo, useEffect, useState } from 'react';
import { Flexbox } from 'react-layout-kit';
import ResponsiveIndex from '@/components/ResponsiveIndex';
import KnowledgeCardList from './features/KnowledgeList';
// import CreateKnowledgeBase from './features/createKnowledgeBase';
import Layout from './layout.desktop';
const CreateKnowledgeBase = dynamic(() => import('./features/CreateKnowledgeBase'));
// const Mobile: FC = dynamic(() => import('../(mobile)'), { ssr: false }) as FC;
const DesktopPage = memo(() => (
<ResponsiveIndex Mobile={() => <div>321</div>}>
<Layout>
<Flexbox gap={20} horizontal justify="flex-start" wrap="wrap">
<KnowledgeCardList />
</Flexbox>
</Layout>
</ResponsiveIndex>
));
const DesktopPage = memo(() => {
const [showModal, setShowModal] = useState(false);
useEffect(() => {
setShowModal(true);
}, []);
const onClose = () => setShowModal(false);
return (
<ResponsiveIndex Mobile={() => <div>321</div>}>
<Layout>
<Flexbox gap={20} horizontal justify="flex-start" wrap="wrap">
<KnowledgeCardList />
<CreateKnowledgeBase onClose={onClose} open={showModal} />
</Flexbox>
<FloatButton icon={<Plus />} onClick={() => setShowModal(true)}>
</FloatButton>
</Layout>
</ResponsiveIndex>
);
});
export default DesktopPage;

View File

@ -0,0 +1,17 @@
import { Input, Modal } from 'antd';
import { memo } from 'react';
import { Center, Flexbox } from 'react-layout-kit';
const ModalSegment = memo(() => {
return (
<Modal okText="确认修改" open title="知识片段">
<Flexbox padding={20}>
<Center>
<Input.TextArea autoSize={{ maxRows: 15, minRows: 10 }} style={{ width: 600 }} />
</Center>
</Flexbox>
</Modal>
);
});
export default ModalSegment;

View File

@ -0,0 +1,70 @@
'use client';
import { Card, List } from 'antd';
import { createStyles } from 'antd-style';
import React, { memo, useState } from 'react';
import ModalSegment from './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 [isShowModal, setModal] = useState();
return (
<>
<List
dataSource={data}
grid={{
gutter: 16,
lg: 2,
xl: 2,
xxl: 2,
}}
renderItem={() => (
<List.Item>
<Card className={styles.card} onClick={() => console.log(1)}>
Card content
</Card>
</List.Item>
)}
size="large"
/>
<ModalSegment />
</>
);
});
export default App;

View File

@ -0,0 +1,45 @@
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;
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(() => {
return (
<Modal 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;

View File

@ -0,0 +1,121 @@
'use client';
import { Button, Table } from 'antd';
import type { TableColumnsType } from 'antd';
import React, { useState } from 'react';
import { Flexbox } from 'react-layout-kit';
import ModalAddFile from './features/ModalAddFile';
interface DataType {
address: string;
age: number;
key: React.Key;
name: string;
}
const columns: TableColumnsType<DataType> = [
{
dataIndex: 'index',
title: '序号',
},
{
dataIndex: 'name',
title: '文档名称',
},
{
dataIndex: 'loader',
title: '文档加载器',
},
{
dataIndex: 'loader',
title: '文档加载器',
},
{
dataIndex: 'splitter',
title: '分词器',
},
{
dataIndex: 'source',
title: '源文件',
},
{
dataIndex: 'vector',
title: '向量库',
},
];
const data: DataType[] = [];
for (let i = 0; i < 46; i++) {
data.push({
address: `London, Park Lane no. ${i}`,
age: 32,
key: i,
name: `Edward King ${i}`,
});
}
const App: React.FC = () => {
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
const [loading, setLoading] = useState(false);
const start = () => {
setLoading(true);
// ajax request after empty completing
setTimeout(() => {
setSelectedRowKeys([]);
setLoading(false);
}, 1000);
};
const onSelectChange = (newSelectedRowKeys: React.Key[]) => {
console.log('selectedRowKeys changed:', newSelectedRowKeys);
setSelectedRowKeys(newSelectedRowKeys);
};
const rowSelection = {
onChange: onSelectChange,
selectedRowKeys,
};
const hasSelected = selectedRowKeys.length > 0;
return (
<>
<Flexbox width={'100%'}>
<Flexbox direction="horizontal" justify="space-between" style={{ marginBottom: 16 }}>
<Flexbox direction="horizontal" gap={20}>
<Button disabled={!hasSelected} loading={loading} onClick={start} type="default">
</Button>
<Button disabled={!hasSelected} loading={loading} onClick={start} type="default">
</Button>
<Button disabled={!hasSelected} loading={loading} onClick={start} type="default">
</Button>
<Button disabled={!hasSelected} loading={loading} onClick={start} type="default">
</Button>
<span style={{ marginLeft: 8 }}>
{hasSelected ? `Selected ${selectedRowKeys.length} items` : ''}
</span>
</Flexbox>
<div>
<Button loading={loading} onClick={start} type="primary">
</Button>
</div>
</Flexbox>
<Table
columns={columns}
dataSource={data}
rowSelection={rowSelection}
style={{ width: '100%' }}
/>
</Flexbox>
<ModalAddFile />
</>
);
};
export default App;

View File

@ -0,0 +1,62 @@
'use client';
import { Form, type ItemGroup } from '@lobehub/ui';
import { Form as AntForm, Input, InputNumber, Slider, Switch } from 'antd';
import { Settings } from 'lucide-react';
import { memo, useCallback } from 'react';
import { FORM_STYLE } from '@/const/layoutTokens';
const KnowledgeBaseConfig = memo(() => {
const [form] = AntForm.useForm();
const handleConfigChange = useCallback(() => {
console.log(321);
}, []);
const system: ItemGroup = {
children: [
{
children: <Input placeholder={'请为知识库命名'} />,
desc: '名称',
label: '知识库名称',
name: 'name',
},
{
children: <Input.TextArea placeholder={'请简单介绍你的知识库'} />,
desc: '简介',
label: '知识库简介',
name: 'intro',
},
{
children: <InputNumber placeholder={'请输入数字'} style={{ width: 200 }} />,
desc: '321',
label: '单段文本最大长度',
name: 'paragraphMaxLength',
},
{
children: <InputNumber placeholder={'请输入数字'} style={{ width: 200 }} />,
desc: '321',
label: '相邻文本重合长度',
name: 'paragraphOverlapLength',
},
{
children: <Slider style={{ width: 100 }} />,
desc: '321',
label: '文本匹配条数',
name: 'paragraphMatchCount',
},
{
children: <Switch style={{ width: 100 }} />,
desc: '321',
label: '开启中文标题加强',
name: 'chineseTitleEnhance',
},
],
icon: Settings,
title: '知识库设置',
};
return <Form form={form} items={[system]} onValuesChange={handleConfigChange} {...FORM_STYLE} />;
});
export default KnowledgeBaseConfig;

View File

@ -0,0 +1,31 @@
import { Form, type ItemGroup } from '@lobehub/ui';
import { Form as AntForm, Input } from 'antd';
import { AppWindow } from 'lucide-react';
import { memo, useCallback } from 'react';
import { FORM_STYLE } from '@/const/layoutTokens';
// 参考settings/llm/Anthropic/index.tsx的代码生成一个名为KnowledgeBaseConfig.tsx的表单组件
const KnowledgeBaseConfig = memo(() => {
const [form] = AntForm.useForm();
const handleConfigChange = useCallback(() => {
console.log(321);
}, []);
const system: ItemGroup = {
children: [
{
children: <Input placeholder={'321'} />,
desc: '知识库名称321',
label: '知识库名称',
name: 'password',
},
],
icon: AppWindow,
title: '321',
};
return <Form form={form} items={[system]} onValuesChange={handleConfigChange} {...FORM_STYLE} />;
});
export default KnowledgeBaseConfig;

View File

@ -0,0 +1,43 @@
'use client';
import { createStyles } from 'antd-style';
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';
const useStyles = createStyles(({ stylish, token, css }) => ({
container: {
paddingLeft: 20,
paddingRight: 20,
position: 'relative',
},
}));
export default memo(({ children, params }: PropsWithChildren) => {
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>
);
});

View File

@ -0,0 +1,11 @@
'use client';
import dynamic from 'next/dynamic';
import { memo } from 'react';
const KnowledgeBaseConfig = dynamic(() => import('./features/KnowledgeBaseConfig'));
const KnowledgeDetail = memo(() => {
return <KnowledgeBaseConfig />;
});
export default KnowledgeDetail;

View 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;

View File

@ -0,0 +1,28 @@
import { Settings2, Webhook } from 'lucide-react';
import Link from 'next/link';
import { memo } from 'react';
import { KnowledgeTabs } from '@/store/global/initialState';
import Item from './TabItem';
export interface KnowledgeTabsProps {
activeTab?: KnowledgeTabs;
params: Record<string, string>;
}
const KnowledgeTabsBox = memo<KnowledgeTabsProps>(({ activeTab, params }) => {
console.log(params);
const items = [
{ icon: Webhook, label: '知识库', value: KnowledgeTabs.Base },
{ icon: Settings2, label: '配置', value: KnowledgeTabs.Config },
];
return items.map(({ value, icon, label }) => (
<Link aria-label={label} href={`/knowledge/${params.id}/${value}`} key={value}>
<Item active={activeTab === value} hoverable icon={icon} label={label} />
</Link>
));
});
export default KnowledgeTabsBox;

View File

@ -2,7 +2,7 @@ import { GlobalCommonState, initialCommonState } from './slices/common/initialSt
import { GlobalPreferenceState, initialPreferenceState } from './slices/preference/initialState';
import { GlobalSettingsState, initialSettingsState } from './slices/settings/initialState';
export { SettingsTabs, SidebarTabKey } from './slices/common/initialState';
export { KnowledgeTabs, SettingsTabs, SidebarTabKey } from './slices/common/initialState';
export type GlobalState = GlobalCommonState & GlobalSettingsState & GlobalPreferenceState;

View File

@ -15,6 +15,11 @@ export enum SettingsTabs {
TTS = 'tts',
}
export enum KnowledgeTabs {
Base = 'base',
Config = 'config',
}
export interface Guide {
// Topic 引导
topic?: boolean;