Files
kevisual-center-v1/src/pages/ai-chat/AiModule.tsx

323 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { useShallow } from 'zustand/react/shallow';
import { useAiStore } from './store/ai-store';
import { CloseOutlined, HistoryOutlined, PlusOutlined } from '@ant-design/icons';
import { Button, Dropdown, Form, Input, message, Modal, Tooltip } from 'antd';
import { useEffect } from 'react';
import { TextArea } from '../container/components/TextArea';
import clsx from 'clsx';
import { query } from '@/modules';
import { nanoid } from 'nanoid';
import { ChatMessage } from './module/ChatMessage';
import { isObjectNull } from '@/utils/is-null';
import { useNavigate } from 'react-router';
const testId = '60aca66b-4be9-4266-9568-6001032c7e13';
type FormModalProps = {
send?: any;
};
const FormModal = (props: FormModalProps) => {
const [form] = Form.useForm();
const aiStore = useAiStore(
useShallow((state) => {
return {
showEdit: state.showEdit,
setShowEdit: state.setShowEdit,
formData: state.formData,
setMessage: state.setMessage,
};
}),
);
useEffect(() => {
const open = aiStore.showEdit;
if (open) {
const isNull = isObjectNull(aiStore.formData);
if (isNull) {
form.setFieldsValue({});
} else form.setFieldsValue(aiStore.formData);
}
}, [aiStore.showEdit]);
const onFinish = async (values: any) => {
props.send({
type: 'changeSession',
data: {
id: testId,
},
});
aiStore.setMessage([]);
};
const onClose = () => {
aiStore.setShowEdit(false);
form.resetFields();
};
const isEdit = aiStore.formData.id;
return (
<Modal
title={isEdit ? 'Edit' : 'Add'}
open={aiStore.showEdit}
onClose={() => aiStore.setShowEdit(false)}
destroyOnClose
footer={false}
width={800}
onCancel={onClose}>
<Form
form={form}
onFinish={onFinish}
labelCol={{
span: 4,
}}
wrapperCol={{
span: 20,
}}>
<Form.Item name='id' hidden>
<Input />
</Form.Item>
<Form.Item name='sessionId' label='sessionId'>
<Input />
</Form.Item>
<Form.Item label=' ' colon={false}>
<Button type='primary' htmlType='submit'>
Submit
</Button>
<Button className='ml-2' htmlType='reset' onClick={onClose}>
Cancel
</Button>
</Form.Item>
</Form>
</Modal>
);
};
export const useListenQuery = () => {
const aiStore = useAiStore(
useShallow((state) => {
return {
open: state.open,
key: state.key,
onMessage: state.onMessage,
};
}),
);
// TODO: Implement useEffect
useEffect(() => {
if (!aiStore.open) {
return;
}
const open = aiStore.open;
send({
type: 'subscribe',
token: query.getToken(),
data: {
key: aiStore.key,
},
});
const close = query.qws.onMessage(onMessage, {
selector: (data) => {
const requestId = data.requestId;
return {
requestId,
...data.data,
};
},
});
return () => {
send({ type: 'unsubscribe' });
close();
};
}, [aiStore.open, query]);
const send = async (data: any) => {
query.qws.send(data, {
wrapper: (data) => ({
requestId: nanoid(16),
type: 'chat',
data: data,
}),
});
};
const onMessage = (data: any) => {
type MessageData = {
chatId: string;
chatPromptId: string;
role: string;
root: boolean;
show: boolean;
data: {
message: string;
};
};
if (data.code === 200 && data.type === 'messages') {
const messageData = data.data as MessageData;
aiStore.onMessage(messageData);
}
};
return {
send,
};
};
export const AiMoudle = () => {
const [form] = Form.useForm();
const navigate = useNavigate();
const aiStore = useAiStore(
useShallow((state) => {
return {
open: state.open,
setOpen: state.setOpen,
formData: state.formData,
setFormData: state.setFormData,
messages: state.messages,
setMessages: state.setMessage,
key: state.key,
root: state.root,
setRoot: state.setRoot,
title: state.title,
setShowEdit: state.setShowEdit,
list: state.list,
getList: state.getList,
};
}),
);
useEffect(() => {
if (!aiStore.open) {
return;
}
const isNull = JSON.stringify(aiStore.formData) === '{}';
if (!isNull) {
form.setFieldsValue(aiStore.formData);
} else {
form.setFieldsValue({ inputs: [] });
}
}, [aiStore.open, aiStore.formData]);
useEffect(() => {
if (!aiStore.open) {
aiStore.setMessages([]);
} else {
aiStore.getList();
}
}, [aiStore.open]);
const { send } = useListenQuery();
const onSend = () => {
const data = form.getFieldsValue();
send({ type: 'messages', data: { ...data, root: true } });
};
const inputs = form.getFieldValue('inputs');
const isNotShow = inputs?.length === 0 || !inputs;
const RootForm = (
<div className='flex flex-col relative'>
<div className={clsx('flex flex-col', isNotShow && 'invisible')}>
<div className='mt-1 ml-2'>?</div>
<div className='mt-4'>
<Form form={form}>
<Form.List name={'inputs'} initialValue={[]}>
{(fields, { add, remove }) => {
return (
<>
{fields.map((field, index) => {
const key = form.getFieldValue(['inputs', index, 'key']);
return (
<div key={field.name + '-' + index} className=''>
<Form.Item name={[field.name, 'key']} rules={[{ required: true, message: 'Missing name' }]} hidden noStyle>
<Input placeholder='name' />
</Form.Item>
<Form.Item className='!mb-0' name={[field.name, 'value']} rules={[{ required: true, message: 'Missing value' }]}>
<TextArea className='scrollbar' style={{ minHeight: 180 }} placeholder={key} />
</Form.Item>
</div>
);
})}
</>
);
}}
</Form.List>
</Form>
<div className='px-4'>
<Button type='primary' className='mt-4' onClick={onSend}>
</Button>
</div>
</div>
</div>
{isNotShow && (
<div className='mt-2 text-gray-400 flex absolute top-3'>
<div
className='text-blue-500 cursor-pointer'
onClick={(e) => {
navigate('/chat/chat-prompt/list');
}}>
</div>
</div>
)}
</div>
);
const ChatForm = (
<ChatMessage
messages={aiStore.messages}
onSend={(value) => {
send({ type: 'messages', data: { message: value } });
}}
/>
);
const items = aiStore.list.map((item) => {
return {
key: item.id,
label: item.title || item.id,
onClick: () => {
// aiStore.setKey(item.id);
// 选中具体的
send({
type: 'changeSession',
data: {
id: item.id,
},
});
aiStore.setMessages([]);
},
};
});
return (
<div className={clsx('w-[600px] flex-shrink-0 bg-gray-100 border-l-2 shadow-lg flex flex-col', !aiStore?.open && 'hidden')}>
<div className='flex justify-between p-2 border bg-white'>
<div className='flex gap-4'>
<Button className='position ml-4 ' onClick={() => aiStore.setOpen(false)} icon={<CloseOutlined />}></Button>
<h1 className=''>{aiStore.title || 'Ai Module'}</h1>
</div>
<div className='mr-4 flex'>
{!aiStore.root && (
<Button
className='mr-2'
onClick={() => {
aiStore.setMessages([]);
aiStore.setRoot(true);
send({
type: 'changeSession',
data: {
id: null,
},
});
}}
icon={<PlusOutlined />}></Button>
)}
<Dropdown
className='mr-2'
placement='bottomRight'
menu={{
items,
}}>
<Button className='' icon={<HistoryOutlined />}></Button>
</Dropdown>
{/* <Tooltip title='历史会话'>
<Button
className=''
onClick={() => {
aiStore.setShowEdit(true);
}}
icon={<HistoryOutlined />}></Button>
</Tooltip> */}
</div>
</div>
<div className='flex-grow p-2 overflow-hidden h-full flex flex-col'>{aiStore.root ? RootForm : ChatForm}</div>
<FormModal send={send} />
</div>
);
};