feat: 抽离 inputbar
This commit is contained in:
parent
2e12e2d93b
commit
40766106d8
|
|
@ -0,0 +1,17 @@
|
||||||
|
.InputBar {
|
||||||
|
.CenterCard {
|
||||||
|
width: 50vw;
|
||||||
|
padding: 18px 18px 0 18px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding-bottom: 24px,
|
||||||
|
}
|
||||||
|
|
||||||
|
.Flex {
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.ant-form-item {
|
||||||
|
--ant-form-item-margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
import { ProForm, ProFormTextArea } from '@ant-design/pro-components';
|
||||||
|
import { useModel, history } from '@umijs/max';
|
||||||
|
import { Button, Divider, Dropdown, Flex, message, Tag, theme } from 'antd';
|
||||||
|
import React, { useRef, useState } from 'react';
|
||||||
|
import './index.less'
|
||||||
|
import { FileSearchOutlined, GlobalOutlined, PlusOutlined } from '@ant-design/icons';
|
||||||
|
import { TweenOneGroup } from 'rc-tween-one';
|
||||||
|
|
||||||
|
export type InputBarEvent = {
|
||||||
|
query: string;
|
||||||
|
tools: Object[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InputBarSubmitHandler = (event: InputBarEvent) => void;
|
||||||
|
|
||||||
|
export type InputBarProps = {
|
||||||
|
onSubmit?: InputBarSubmitHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const InputBarToolItems = [{
|
||||||
|
key: 'searchWeb',
|
||||||
|
label: '搜索网站',
|
||||||
|
color: '#55acee',
|
||||||
|
icon: <GlobalOutlined />,
|
||||||
|
}, {
|
||||||
|
key: 'searchKB',
|
||||||
|
label: '知识库',
|
||||||
|
color: '#55acee',
|
||||||
|
icon: <FileSearchOutlined />,
|
||||||
|
children: [
|
||||||
|
{ key: 'searchKB_kb1', label: '知识库1' },
|
||||||
|
{ key: 'searchKB_kb2', label: '知识库2' },
|
||||||
|
{ type: 'divider' },
|
||||||
|
{ key: 'searchKB_add', label: '新增知识库' },
|
||||||
|
],
|
||||||
|
}]
|
||||||
|
|
||||||
|
const tagGroupStyle: React.CSSProperties = {
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 8,
|
||||||
|
marginBottom: 8,
|
||||||
|
marginTop: 16,
|
||||||
|
};
|
||||||
|
|
||||||
|
const InputBar = (props: InputBarProps) => {
|
||||||
|
const textAreaRef = useRef<any>(null);
|
||||||
|
const [tools, setTools] = useState<any>([]);
|
||||||
|
const textAreaFieldProps: any = {
|
||||||
|
ref: textAreaRef,
|
||||||
|
autoSize: {
|
||||||
|
minRows: 1,
|
||||||
|
maxRows: 5,
|
||||||
|
},
|
||||||
|
variant: 'borderless',
|
||||||
|
onPressEnter: (e: any) => {
|
||||||
|
const { nativeEvent: { shiftKey, key } } = e;
|
||||||
|
if (shiftKey && key === "Enter") {
|
||||||
|
const { current: { resizableTextArea: { textArea } } } = textAreaRef;
|
||||||
|
e.preventDefault();
|
||||||
|
const text = textArea?.value;
|
||||||
|
console.log({
|
||||||
|
query: text,
|
||||||
|
tools: tools,
|
||||||
|
});
|
||||||
|
props?.onSubmit && props.onSubmit({
|
||||||
|
query: text,
|
||||||
|
tools: tools?.map((e:any) => e.key),
|
||||||
|
})
|
||||||
|
textArea.value = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemOnClick = ({ key }: any) => {
|
||||||
|
if (key === 'searchKB_add') {
|
||||||
|
message.info("创建知识库")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const toolsKeys: string[] = tools?.map((e: any) => e.key);
|
||||||
|
if (toolsKeys?.includes(key)) {
|
||||||
|
setTools(tools.filter((e: any) => e.key !== key));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (key.includes('kb')) {
|
||||||
|
const kbObj = InputBarToolItems?.find(i => i.key === 'searchKB')
|
||||||
|
const obj = kbObj?.children?.find(i => i.key === key);
|
||||||
|
setTools([{ ...obj, icon: kbObj?.icon }, ...tools]);
|
||||||
|
} else {
|
||||||
|
const obj = InputBarToolItems?.find(i => i.key === key);
|
||||||
|
setTools([obj, ...tools]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const Tags = () => {
|
||||||
|
if (tools?.length) {
|
||||||
|
return (<>
|
||||||
|
<Divider style={{ height: 1, margin: '8px 0' }} />
|
||||||
|
<TweenOneGroup
|
||||||
|
appear={false}
|
||||||
|
style={tagGroupStyle}
|
||||||
|
enter={{ scale: 0.8, opacity: 0, type: 'from', duration: 100 }}
|
||||||
|
leave={{ opacity: 0, width: 0, scale: 0, duration: 200 }}
|
||||||
|
onEnd={(e) => {
|
||||||
|
if (e.type === 'appear' || e.type === 'enter') {
|
||||||
|
(e.target as any).style = 'display: inline-block';
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{tools?.map((e: any) => <Tag closable key={e.key} icon={e.icon} onClose={() => itemOnClick({ key: e.key })}>
|
||||||
|
{e.label}
|
||||||
|
</Tag>)}
|
||||||
|
|
||||||
|
</TweenOneGroup>
|
||||||
|
</>)
|
||||||
|
}
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='InputBar'>
|
||||||
|
<div className='CenterCard'>
|
||||||
|
<ProForm submitter={false}>
|
||||||
|
<Flex align='flex-end'>
|
||||||
|
<Dropdown menu={{ items: InputBarToolItems, onClick: itemOnClick }} placement='topCenter' arrow>
|
||||||
|
<Button icon={<PlusOutlined />} type='text'></Button>
|
||||||
|
</Dropdown>
|
||||||
|
<div className='Flex'>
|
||||||
|
<ProFormTextArea placeholder="按 Shift+Enter 进行提问" fieldProps={textAreaFieldProps} />
|
||||||
|
</div>
|
||||||
|
</Flex>
|
||||||
|
</ProForm>
|
||||||
|
<Tags />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default InputBar;
|
||||||
|
|
@ -1,129 +1,24 @@
|
||||||
import { PageContainer, ProForm, ProFormTextArea } from '@ant-design/pro-components';
|
import { PageContainer } from '@ant-design/pro-components';
|
||||||
import { useModel, history } from '@umijs/max';
|
import { useModel } from '@umijs/max';
|
||||||
import { Button, Divider, Dropdown, Flex, message, Tag, theme } from 'antd';
|
import { Flex, theme } from 'antd';
|
||||||
import React, { useRef, useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import './Welcome.less'
|
import './Welcome.less'
|
||||||
import { FileSearchOutlined, GlobalOutlined, PlusOutlined } from '@ant-design/icons';
|
|
||||||
import { TweenOneGroup } from 'rc-tween-one';
|
|
||||||
import HeaderModelSelect from '@/components/Custom/HeaderModelSelect';
|
import HeaderModelSelect from '@/components/Custom/HeaderModelSelect';
|
||||||
import type { ModelItem } from '@/components/Custom/HeaderModelSelect';
|
import type { ModelItem } from '@/components/Custom/HeaderModelSelect';
|
||||||
|
import InputBar from '@/components/Custom/InputBar';
|
||||||
const tagGroupStyle: React.CSSProperties = {
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
flexWrap: 'wrap',
|
|
||||||
gap: 8,
|
|
||||||
marginBottom: 8,
|
|
||||||
marginTop: 16,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
const Welcome: React.FC = () => {
|
const Welcome: React.FC = () => {
|
||||||
const { token } = theme.useToken();
|
const { token } = theme.useToken();
|
||||||
const { initialState } = useModel('@@initialState');
|
const { initialState } = useModel('@@initialState');
|
||||||
const textAreaRef = useRef<any>(null);
|
|
||||||
const [tools, setTools] = useState<any>([]);
|
|
||||||
const [model, setModel] = useState<ModelItem | undefined>(undefined);
|
const [model, setModel] = useState<ModelItem | undefined>(undefined);
|
||||||
const textAreaFieldProps: any = {
|
|
||||||
ref: textAreaRef,
|
|
||||||
autoSize: {
|
|
||||||
minRows: 1,
|
|
||||||
maxRows: 5,
|
|
||||||
},
|
|
||||||
variant: 'borderless',
|
|
||||||
onPressEnter: (e: any) => {
|
|
||||||
const { nativeEvent: { shiftKey, key } } = e;
|
|
||||||
if (shiftKey && key === "Enter") {
|
|
||||||
const { current: { resizableTextArea: { textArea } } } = textAreaRef;
|
|
||||||
e.preventDefault();
|
|
||||||
const text = textArea?.value;
|
|
||||||
console.log(text);
|
|
||||||
textArea.value = "";
|
|
||||||
history.push('/chat');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = [{
|
|
||||||
key: 'searchWeb',
|
|
||||||
label: '搜索网站',
|
|
||||||
color: '#55acee',
|
|
||||||
icon: <GlobalOutlined />,
|
|
||||||
}, {
|
|
||||||
key: 'searchKB',
|
|
||||||
label: '知识库',
|
|
||||||
color: '#55acee',
|
|
||||||
icon: <FileSearchOutlined />,
|
|
||||||
children: [
|
|
||||||
{ key: 'searchKB_kb1', label: '知识库1' },
|
|
||||||
{ key: 'searchKB_kb2', label: '知识库2' },
|
|
||||||
{ type: 'divider' },
|
|
||||||
{ key: 'searchKB_add', label: '新增知识库' },
|
|
||||||
],
|
|
||||||
}]
|
|
||||||
|
|
||||||
const itemOnClick = ({ key }: any) => {
|
|
||||||
if (key === 'searchKB_add') {
|
|
||||||
message.info("创建知识库")
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const toolsKeys: string[] = tools?.map((e: any) => e.key);
|
|
||||||
if (toolsKeys?.includes(key)) {
|
|
||||||
setTools(tools.filter((e: any) => e.key !== key));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (key.includes('kb')) {
|
|
||||||
const kbObj = items?.find(i => i.key === 'searchKB')
|
|
||||||
const obj = kbObj?.children?.find(i => i.key === key);
|
|
||||||
setTools([{ ...obj, icon: kbObj?.icon }, ...tools]);
|
|
||||||
} else {
|
|
||||||
const obj = items?.find(i => i.key === key);
|
|
||||||
setTools([obj, ...tools]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Tags = () => {
|
|
||||||
if (tools?.length) {
|
|
||||||
return (<>
|
|
||||||
<Divider style={{ height: 1, margin: '8px 0' }} />
|
|
||||||
<TweenOneGroup
|
|
||||||
appear={false}
|
|
||||||
style={tagGroupStyle}
|
|
||||||
enter={{ scale: 0.8, opacity: 0, type: 'from', duration: 100 }}
|
|
||||||
leave={{ opacity: 0, width: 0, scale: 0, duration: 200 }}
|
|
||||||
onEnd={(e) => {
|
|
||||||
if (e.type === 'appear' || e.type === 'enter') {
|
|
||||||
(e.target as any).style = 'display: inline-block';
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{tools?.map((e: any) => <Tag closable key={e.key} icon={e.icon} onClose={() => itemOnClick({ key: e.key })}>
|
|
||||||
{e.label}
|
|
||||||
</Tag>)}
|
|
||||||
|
|
||||||
</TweenOneGroup>
|
|
||||||
</>)
|
|
||||||
}
|
|
||||||
return <></>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageContainer title={<HeaderModelSelect value={model} onChange={e => setModel(e)}/>} className='WelcomePage'>
|
<PageContainer title={<HeaderModelSelect value={model} onChange={e => setModel(e)}/>} className='WelcomePage'>
|
||||||
<Flex align='center' justify='center' vertical>
|
<Flex align='center' justify='center' vertical>
|
||||||
<div className='PageHeader'></div>
|
<div className='PageHeader'></div>
|
||||||
<div className='CenterCard'>
|
<InputBar onSubmit={e => {
|
||||||
<ProForm submitter={false}>
|
console.log(e);
|
||||||
<Flex align='flex-end'>
|
}}/>
|
||||||
<Dropdown menu={{ items, onClick: itemOnClick }} placement='topCenter' arrow>
|
|
||||||
<Button icon={<PlusOutlined />} type='text'></Button>
|
|
||||||
</Dropdown>
|
|
||||||
<div className='Flex'>
|
|
||||||
<ProFormTextArea placeholder="按 Shift+Enter 进行提问" fieldProps={textAreaFieldProps} />
|
|
||||||
</div>
|
|
||||||
</Flex>
|
|
||||||
</ProForm>
|
|
||||||
<Tags />
|
|
||||||
</div>
|
|
||||||
</Flex>
|
</Flex>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,12 @@
|
||||||
.Flex {
|
.Flex {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.CenterCard {
|
||||||
|
width: 50vw;
|
||||||
|
padding: 18px 18px 0 18px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding-bottom: 24px,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,20 +1,22 @@
|
||||||
import { PageContainer } from '@ant-design/pro-components';
|
import { PageContainer, ProForm, ProFormTextArea } from '@ant-design/pro-components';
|
||||||
import type { ModelItem } from '@/components/Custom/HeaderModelSelect';
|
import type { ModelItem } from '@/components/Custom/HeaderModelSelect';
|
||||||
import './index.less';
|
import './index.less';
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import HeaderModelSelect from '@/components/Custom/HeaderModelSelect';
|
import HeaderModelSelect from '@/components/Custom/HeaderModelSelect';
|
||||||
import { Flex } from 'antd';
|
import { Button, Dropdown, Flex } from 'antd';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
const [model, setModel] = useState<ModelItem | undefined>(undefined);
|
const [model, setModel] = useState<ModelItem | undefined>(undefined);
|
||||||
|
|
||||||
|
|
||||||
return <PageContainer
|
return <PageContainer
|
||||||
className='ChatPage'
|
className='ChatPage'
|
||||||
title={<HeaderModelSelect value={model} onChange={val => setModel(val)} />}
|
title={<HeaderModelSelect value={model} onChange={val => setModel(val)} />}
|
||||||
>
|
>
|
||||||
<Flex vertical>
|
<Flex vertical>
|
||||||
<div className='Flex'></div>
|
<div className='Flex'></div>
|
||||||
|
<div className='CenterCard'>
|
||||||
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue