feat: 抽离 inputbar

This commit is contained in:
李如威 2026-01-04 13:26:12 +08:00
parent 2e12e2d93b
commit 40766106d8
5 changed files with 178 additions and 116 deletions

View File

@ -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;
}
}
}

View File

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

View File

@ -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>
); );

View File

@ -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,
}
} }

View File

@ -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>
} }