feat: 暂存添加ai chat and prompt generate
This commit is contained in:
		
							
								
								
									
										38
									
								
								src/pages/ai-chat/AiModule.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/pages/ai-chat/AiModule.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,38 @@ | ||||
| import { useShallow } from 'zustand/react/shallow'; | ||||
| import { useAiStore } from './store/ai-store'; | ||||
| import { CloseOutlined } from '@ant-design/icons'; | ||||
| import { Button } from 'antd'; | ||||
|  | ||||
| export const AiMoudle = () => { | ||||
|   const aiStore = useAiStore( | ||||
|     useShallow((state) => { | ||||
|       return { | ||||
|         open: state.open, | ||||
|         setOpen: state.setOpen, | ||||
|         runAi: state.runAi, | ||||
|       }; | ||||
|     }), | ||||
|   ); | ||||
|   if (!aiStore.open) { | ||||
|     return null; | ||||
|   } | ||||
|   return ( | ||||
|     <div className='w-96 flex-shrink-0 bg-gray-100 border-l-2 shadow-lg flex flex-col'> | ||||
|       <div className='flex gap-4 bg-slate-400 p-2'> | ||||
|         <Button className='position ml-4 !bg-slate-400 !border-black' onClick={() => aiStore.setOpen(false)} icon={<CloseOutlined />}></Button> | ||||
|         <h1 className='ml-10'>Ai Moudle</h1> | ||||
|       </div> | ||||
|       <div className='flex-grow p-2'> | ||||
|         <div> chat message</div> | ||||
|         <div> | ||||
|           <Button | ||||
|             onClick={() => { | ||||
|               aiStore.runAi(); | ||||
|             }}> | ||||
|             Send | ||||
|           </Button> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
							
								
								
									
										7
									
								
								src/pages/ai-chat/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/pages/ai-chat/index.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| import { AiMoudle } from './AiModule'; | ||||
| import { useAiStore } from './store/ai-store'; | ||||
| export { AiMoudle, useAiStore }; | ||||
|  | ||||
| export const App = () => { | ||||
|   return <div>AI Chat</div>; | ||||
| }; | ||||
							
								
								
									
										58
									
								
								src/pages/ai-chat/store/ai-store.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								src/pages/ai-chat/store/ai-store.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,58 @@ | ||||
| import { query } from '@/modules'; | ||||
| import { message } from 'antd'; | ||||
| import { create } from 'zustand'; | ||||
|  | ||||
| export type AiStore = { | ||||
|   open: boolean; | ||||
|   setOpen: (open: boolean) => void; | ||||
|   type?: string; | ||||
|   key: string; | ||||
|   setKey: (key: string) => void; | ||||
|   setType?: (type: string) => void; | ||||
|   sendMsg: (msg: string) => void; | ||||
|   formData: any; | ||||
|   setFormData: (data: any) => void; | ||||
|   runAi: () => any; | ||||
|   title: string; | ||||
|   setTitle: (title: string) => void; | ||||
| }; | ||||
|  | ||||
| export const useAiStore = create<AiStore>((set, get) => { | ||||
|   return { | ||||
|     open: false, | ||||
|     setOpen: (open) => set({ open }), | ||||
|     key: '', | ||||
|     setKey: (key) => set({ key }), | ||||
|     sendMsg: (msg) => { | ||||
|       console.log(msg); | ||||
|     }, | ||||
|     formData: {}, | ||||
|     setFormData: (data) => set({ formData: data }), | ||||
|     runAi: async () => { | ||||
|       const { formData } = get(); | ||||
|       const res = await query.post({ | ||||
|         path: 'ai', | ||||
|         key: 'run', | ||||
|         data: { | ||||
|           key: formData.key, | ||||
|           inputs: [ | ||||
|             { | ||||
|               key: 'title', | ||||
|               value: '根据描述生成代码', | ||||
|             }, | ||||
|             { | ||||
|               key: 'description', | ||||
|               value: '我想获取一个card, 包含标题和内容,标题是evision,内容是这是一个测试', | ||||
|             }, | ||||
|           ], | ||||
|         }, | ||||
|       }); | ||||
|       if (res.code === 200) { | ||||
|         console.log(res.data); | ||||
|         message.success('Success'); | ||||
|       } | ||||
|     }, | ||||
|     title: '', | ||||
|     setTitle: (title) => set({ title }), | ||||
|   }; | ||||
| }); | ||||
| @@ -11,6 +11,8 @@ type Props = { | ||||
|   style?: React.CSSProperties; | ||||
|   language?: string; | ||||
|   listen?: boolean; | ||||
|   placeholder?: string; | ||||
|   onBlur?: () => void; | ||||
| }; | ||||
| export const TextArea = (props: Props) => { | ||||
|   const [code, setCode] = useState<string>(''); | ||||
| @@ -27,12 +29,13 @@ export const TextArea = (props: Props) => { | ||||
|     <div className={clsx('min-h-16 max-h-52 overflow-scroll scrollbar p-1 ', props.className)}> | ||||
|       <CodeEditor | ||||
|         value={code} | ||||
|         language='js' | ||||
|         language={props.language || 'js'} | ||||
|         className='border rounded-sm ' | ||||
|         readOnly={props.readonly} | ||||
|         placeholder='Please enter JS code.' | ||||
|         placeholder={props.placeholder || 'Please enter JS code.'} | ||||
|         onChange={(evn) => _onChange(evn.target.value)} | ||||
|         padding={10} | ||||
|         onBlur={props.onBlur} | ||||
|         style={{ | ||||
|           // backgroundColor: '#f5f5f5', | ||||
|           fontFamily: 'ui-monospace,SFMono-Regular,SF Mono,Consolas,Liberation Mono,Menlo,monospace', | ||||
|   | ||||
| @@ -59,12 +59,7 @@ const FormModal = () => { | ||||
|           <Input /> | ||||
|         </Form.Item> | ||||
|         <Form.Item name='code' label='code'> | ||||
|           <TextArea | ||||
|             value={containerStore.formData.code} | ||||
|             style={{ | ||||
|               height: '200px', | ||||
|             }} | ||||
|           /> | ||||
|           <TextArea /> | ||||
|         </Form.Item> | ||||
|         <Form.Item label=' ' colon={false}> | ||||
|           <Button type='primary' htmlType='submit'> | ||||
|   | ||||
							
								
								
									
										41
									
								
								src/pages/prompt/D3.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/pages/prompt/D3.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,41 @@ | ||||
| import { query } from '@/modules'; | ||||
| import { useEffect } from 'react'; | ||||
| import { drawGraph } from './graph/d3'; | ||||
| export const D3Grahp = () => { | ||||
|   const get = async () => { | ||||
|     const res = await query.post({ | ||||
|       path: 'prompt', | ||||
|       key: 'list', | ||||
|     }); | ||||
|     console.log(res); | ||||
|     const grahpData = { | ||||
|       nodes: res.data.map((item) => { | ||||
|         return { | ||||
|           id: item.id, | ||||
|           label: item.title, | ||||
|           type: 'prompt', | ||||
|         }; | ||||
|       }), | ||||
|       links: [], | ||||
|     }; | ||||
|     drawGraph(grahpData); | ||||
|   }; | ||||
|   const getD3 = async () => { | ||||
|     const res = await query.post({ | ||||
|       path: 'prompt', | ||||
|       key: 'getD3', | ||||
|     }); | ||||
|     drawGraph(res.data); | ||||
|   }; | ||||
|   useEffect(() => { | ||||
|     getD3(); | ||||
|     return () => { | ||||
|       document.querySelector('.ai-graph')!.innerHTML = ''; | ||||
|     }; | ||||
|   }, []); | ||||
|   return ( | ||||
|     <div className='w-full h-full'> | ||||
|       <svg className='ai-graph border shadow-sm p-2 mx-auto mt-10 ' width='960' height='600' ></svg> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
							
								
								
									
										57
									
								
								src/pages/prompt/edit/Edit.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										57
									
								
								src/pages/prompt/edit/Edit.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,57 @@ | ||||
| import { Button, Form, Input } from 'antd'; | ||||
| import { TextArea } from '../../container/components/TextArea'; | ||||
| import clsx from 'clsx'; | ||||
|  | ||||
| export const Edit = () => { | ||||
|   const [form] = Form.useForm(); | ||||
|   const onFinish = (values: any) => { | ||||
|     console.log('Success:', values); | ||||
|   }; | ||||
|   const onSave = () => { | ||||
|     // | ||||
|   }; | ||||
|   const isEdit = form.getFieldValue('id'); | ||||
|   return ( | ||||
|     <div className='w-full h-full felx flex-col bg-gray-200'> | ||||
|       <h1 className='text-center py-4'>Prompt JS Code Generate</h1> | ||||
|       <div className='py-2 px-4 w-3/4 min-w-[600px] mx-auto border shadow rounded bg-white'> | ||||
|         <Form form={form} onFinish={onFinish} className='mt-4' labelCol={{ span: 4 }}> | ||||
|           <Form.Item name='id' hidden> | ||||
|             <Input /> | ||||
|           </Form.Item> | ||||
|           <Form.Item name='title' label='Title'> | ||||
|             <Input /> | ||||
|           </Form.Item> | ||||
|           <Form.Item name='description' label='Description'> | ||||
|             <Input.TextArea rows={4} /> | ||||
|           </Form.Item> | ||||
|           <Form.Item name='code' label='Code'> | ||||
|             <TextArea className='max-h-full' style={{ minHeight: 300 }} /> | ||||
|           </Form.Item> | ||||
|           <Form.Item label=' ' colon={false}> | ||||
|             <div className='flex gap-2'> | ||||
|               <Button type='primary' htmlType='submit'> | ||||
|                 Generate | ||||
|               </Button> | ||||
|               <Button htmlType='reset'>Reset</Button> | ||||
|               <Button | ||||
|                 type='primary' | ||||
|                 onClick={() => { | ||||
|                   // | ||||
|                 }}> | ||||
|                 Save | ||||
|               </Button> | ||||
|               <Button | ||||
|                 className={clsx(isEdit ? 'block' : 'hidden')} | ||||
|                 onClick={() => { | ||||
|                   // | ||||
|                 }}> | ||||
|                 Preview | ||||
|               </Button> | ||||
|             </div> | ||||
|           </Form.Item> | ||||
|         </Form> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
							
								
								
									
										342
									
								
								src/pages/prompt/edit/List.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										342
									
								
								src/pages/prompt/edit/List.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,342 @@ | ||||
| import { Button, Input, message, Modal, Table } from 'antd'; | ||||
| import { Fragment, useEffect, useMemo, useState } from 'react'; | ||||
| import { usePromptStore } from '../store/prompt'; | ||||
| import { useShallow } from 'zustand/react/shallow'; | ||||
| import { Form } from 'antd'; | ||||
| import copy from 'copy-to-clipboard'; | ||||
| import { useNavigate } from 'react-router'; | ||||
| import { EditOutlined, SettingOutlined, LinkOutlined, SaveOutlined, DeleteOutlined, LeftOutlined, CaretRightOutlined, PlusOutlined } from '@ant-design/icons'; | ||||
| import clsx from 'clsx'; | ||||
| import { TextArea } from '@/pages/container/components/TextArea'; | ||||
|  | ||||
| import { marked } from 'marked'; | ||||
| import { extractKeysFromBraces } from '@/utils/extra'; | ||||
| import { useAiStore } from '@/pages/ai-chat'; | ||||
|  | ||||
| const FormModal = () => { | ||||
|   const [form] = Form.useForm(); | ||||
|   const promptStore = usePromptStore( | ||||
|     useShallow((state) => { | ||||
|       return { | ||||
|         showEdit: state.showEdit, | ||||
|         setShowEdit: state.setShowEdit, | ||||
|         formData: state.formData, | ||||
|         updateData: state.updateData, | ||||
|         runAi: state.runAi, | ||||
|       }; | ||||
|     }), | ||||
|   ); | ||||
|   useEffect(() => { | ||||
|     const open = promptStore.showEdit; | ||||
|     if (open) { | ||||
|       const isNull = JSON.stringify(promptStore.formData) === '{}'; | ||||
|       if (isNull) { | ||||
|         form.resetFields(); | ||||
|       } else { | ||||
|         form.setFieldsValue(promptStore.formData || {}); | ||||
|       } | ||||
|     } | ||||
|   }, [promptStore.showEdit]); | ||||
|   const onFinish = async (values: any) => { | ||||
|     let other = {}; | ||||
|     if (!values.id) { | ||||
|       other = { | ||||
|         presetData: { | ||||
|           validator: {}, | ||||
|           data: { | ||||
|             prompt: '', | ||||
|             inputs: [], | ||||
|           }, | ||||
|         }, | ||||
|       }; | ||||
|     } | ||||
|     promptStore.updateData({ | ||||
|       ...values, | ||||
|       ...other, | ||||
|     }); | ||||
|   }; | ||||
|   const onClose = () => { | ||||
|     promptStore.setShowEdit(false); | ||||
|     form.resetFields(); | ||||
|   }; | ||||
|   const isEdit = promptStore.formData.id; | ||||
|   return ( | ||||
|     <Modal | ||||
|       title={isEdit ? 'Edit' : 'Add'} | ||||
|       open={promptStore.showEdit} | ||||
|       onClose={() => promptStore.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='title' label='title'> | ||||
|           <Input /> | ||||
|         </Form.Item> | ||||
|         <Form.Item name='description' label='description'> | ||||
|           <Input.TextArea rows={4} /> | ||||
|         </Form.Item> | ||||
|         <Form.Item name='key' label='key'> | ||||
|           <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 List = () => { | ||||
|   const navicate = useNavigate(); | ||||
|   const aiStore = useAiStore( | ||||
|     useShallow((state) => { | ||||
|       return { | ||||
|         open: state.open, | ||||
|         setOpen: state.setOpen, | ||||
|         key: state.key, | ||||
|         setKey: state.setKey, | ||||
|         sendMsg: state.sendMsg, | ||||
|       }; | ||||
|     }), | ||||
|   ); | ||||
|   const promptStore = usePromptStore( | ||||
|     useShallow((state) => { | ||||
|       return { | ||||
|         setFormData: state.setFormData, | ||||
|         setShowEdit: state.setShowEdit, | ||||
|         list: state.list, | ||||
|         deleteData: state.deleteData, | ||||
|         getList: state.getList, | ||||
|         loading: state.loading, | ||||
|         updateData: state.updateData, | ||||
|         formData: state.formData, | ||||
|         runAi: state.runAi, | ||||
|       }; | ||||
|     }), | ||||
|   ); | ||||
|   const [codeEdit, setCodeEdit] = useState(false); | ||||
|   const [code, setCode] = useState(''); | ||||
|   const [form] = Form.useForm<{ inputs: any[] }>(); | ||||
|   useEffect(() => { | ||||
|     promptStore.getList(); | ||||
|   }, []); | ||||
|   useEffect(() => { | ||||
|     if (!codeEdit) { | ||||
|       form.setFieldsValue({ inputs: [] }); | ||||
|     } | ||||
|   }, [codeEdit]); | ||||
|   const onAdd = () => { | ||||
|     promptStore.setFormData({}); | ||||
|     promptStore.setShowEdit(true); | ||||
|     setCodeEdit(false); | ||||
|   }; | ||||
|   const getFormInputs = () => { | ||||
|     if (!codeEdit) return; | ||||
|     console.log('blur getFormInputs'); | ||||
|  | ||||
|     const keys = extractKeysFromBraces(code); | ||||
|     const inputs = form.getFieldValue('inputs') || []; | ||||
|     const newInputs = keys | ||||
|       .map((key) => { | ||||
|         const has = inputs.some((item: any) => item.key === key); | ||||
|         if (!has) { | ||||
|           return { | ||||
|             key, | ||||
|             value: '', | ||||
|           }; | ||||
|         } | ||||
|         return null; | ||||
|       }) | ||||
|       .filter(Boolean); | ||||
|     if (newInputs.length > 0) { | ||||
|       form.setFieldsValue({ inputs: [...inputs, ...newInputs] }); | ||||
|     } | ||||
|   }; | ||||
|   const len = form.getFieldValue('inputs')?.length || 0; | ||||
|   return ( | ||||
|     <div className='w-full h-full flex flex-col'> | ||||
|       <div className='flex flex-grow overflow-hidden h-full'> | ||||
|         <Button onClick={onAdd} type='primary' className='m-4 w-64 flex-shrink-0' icon={<PlusOutlined />}></Button> | ||||
|         <div className='flex-grow overflow-auto scrollbar bg-gray-100'> | ||||
|           <div className='flex  flex-wrap   gap-x-10 gap-y-4 rounded pt-10  justify-center'> | ||||
|             {promptStore.list.length > 0 && | ||||
|               promptStore.list.map((item) => { | ||||
|                 const { presetData } = item; | ||||
|                 const md = presetData?.data?.prompt || ''; | ||||
|                 const inputs = presetData?.data?.inputs || []; | ||||
|                 const html = marked.parse(md); | ||||
|                 return ( | ||||
|                   <Fragment key={item.id}> | ||||
|                     <div className='flex text-sm gap flex-col w-[600px] max-h-[400px] bg-white p-4 rounded-lg' key={item.id} onClick={() => {}}> | ||||
|                       <div | ||||
|                         className='px-4 cursor-pointer' | ||||
|                         onClick={() => { | ||||
|                           setCode(md); | ||||
|                           promptStore.setFormData(item); | ||||
|                           form.setFieldsValue({ | ||||
|                             inputs: inputs.map((item) => { | ||||
|                               return { key: item.key, value: item.value }; | ||||
|                             }), | ||||
|                           }); | ||||
|                           setCodeEdit(true); | ||||
|                         }}> | ||||
|                         <div | ||||
|                           className='font-bold flex' | ||||
|                           onClick={(e) => { | ||||
|                             // copy(item.code); | ||||
|                             // e.stopPropagation(); | ||||
|                             // message.success('copy code success'); | ||||
|                           }}> | ||||
|                           {item.title || '-'} | ||||
|                           <div | ||||
|                             className=' ml-3 text-xs text-gray-400' | ||||
|                             style={{ | ||||
|                               fontFamily: 'D-DIN', | ||||
|                             }}> | ||||
|                             {item?.key ? item.key : '-'} | ||||
|                           </div> | ||||
|                         </div> | ||||
|                         <div className='font-light mt-2'>{item.description ? item.description : '-'}</div> | ||||
|                       </div> | ||||
|                       {/* <div className='w-full text-xs'> | ||||
|                         <TextArea className='max-h-[240px] scrollbar' value={item.code} readonly /> | ||||
|                       </div> */} | ||||
|                       <div className='px-4 mt-2'>{md ? 'Prompt' : ''}</div> | ||||
|                       <div className='px-4'> | ||||
|                         <div className='max-h-52 overflow-scroll scrollbar p-4 border shadow-sm mt-1'> | ||||
|                           <div dangerouslySetInnerHTML={{ __html: html }}></div> | ||||
|                         </div> | ||||
|                       </div> | ||||
|                       <div className='flex mt-4 ml-4'> | ||||
|                         <Button.Group> | ||||
|                           <Button | ||||
|                             onClick={(e) => { | ||||
|                               promptStore.setFormData(item); | ||||
|                               promptStore.setShowEdit(true); | ||||
|                               setCodeEdit(false); | ||||
|                               e.stopPropagation(); | ||||
|                             }} | ||||
|                             icon={<EditOutlined />}></Button> | ||||
|                           <Button | ||||
|                             onClick={(e) => { | ||||
|                               promptStore.deleteData(item.id); | ||||
|                               e.stopPropagation(); | ||||
|                             }} | ||||
|                             icon={<DeleteOutlined />}></Button> | ||||
|                           <Button | ||||
|                             icon={<CaretRightOutlined />} | ||||
|                             onClick={() => { | ||||
|                               // navicate(`/prompt/${item.id}`); | ||||
|                               promptStore.setFormData(item); | ||||
|                               // promptStore.runAi(); | ||||
|                               aiStore.setOpen(true); | ||||
|                             }} | ||||
|                           /> | ||||
|                         </Button.Group> | ||||
|                       </div> | ||||
|                     </div> | ||||
|                   </Fragment> | ||||
|                 ); | ||||
|               })} | ||||
|             {new Array(4).fill(0).map((_, index) => { | ||||
|               return <div key={index} className='w-[600px]'></div>; | ||||
|             })} | ||||
|             {promptStore.list.length == 0 && ( | ||||
|               <div className='text-center' key={'no-data'}> | ||||
|                 No Data | ||||
|               </div> | ||||
|             )} | ||||
|           </div> | ||||
|         </div> | ||||
|         <div className={clsx('bg-gray-100 border-l flex flex-col border-bg-slate-300 w-[600px] flex-shrink-0', !codeEdit && 'hidden')}> | ||||
|           <div className='bg-white  p-2'> | ||||
|             <div className='mt-2 ml-2 flex gap-2'> | ||||
|               <Button | ||||
|                 onClick={() => { | ||||
|                   setCodeEdit(false); | ||||
|                   promptStore.setFormData({}); | ||||
|                 }} | ||||
|                 icon={<LeftOutlined />}></Button> | ||||
|               <Button | ||||
|                 onClick={() => { | ||||
|                   // console.log('save', promptStore.formData); | ||||
|                   const { presetData } = promptStore.formData; | ||||
|                   const inputs = form.getFieldValue('inputs') || []; | ||||
|                   promptStore.updateData({ ...promptStore.formData, presetData: { ...presetData, data: { ...presetData.data, prompt: code, inputs } } }); | ||||
|                 }} | ||||
|                 icon={<SaveOutlined />}></Button> | ||||
|             </div> | ||||
|           </div> | ||||
|           <div className='flex-grow p-2 rounded-2 shadow-sm overflow-hidden'> | ||||
|             <TextArea | ||||
|               value={code} | ||||
|               language='markdown' | ||||
|               placeholder='Please enter markdown code.' | ||||
|               onChange={(value) => { | ||||
|                 setCode(value); | ||||
|               }} | ||||
|               onBlur={() => { | ||||
|                 console.log('blur'); | ||||
|                 setTimeout(() => { | ||||
|                   getFormInputs(); | ||||
|                 }, 400); | ||||
|               }} | ||||
|               className='h-full max-h-full scrollbar' | ||||
|               style={{ | ||||
|                 overflow: 'auto', | ||||
|                 minHeight: '200px', | ||||
|                 //  height: '100%', // height: '100%' 有bug | ||||
|               }} | ||||
|             /> | ||||
|           </div> | ||||
|           <div className='flex-shrink-0 px-4 mt-4'> | ||||
|             <h1 className={clsx('mb-2', len === 0 && 'hidden')}>Match Keys</h1> | ||||
|             <Form form={form}> | ||||
|               <Form.List name='inputs'> | ||||
|                 {(fields, { add, remove }) => { | ||||
|                   return ( | ||||
|                     <> | ||||
|                       {fields.map((field, index) => { | ||||
|                         return ( | ||||
|                           <div key={field.name + '-' + index} className='flex gap-2'> | ||||
|                             <Form.Item name={[field.name, 'key']} rules={[{ required: true, message: 'Missing name' }]}> | ||||
|                               <Input placeholder='name' /> | ||||
|                             </Form.Item> | ||||
|                             <Form.Item name={[field.name, 'value']} rules={[{ required: true, message: 'Missing value' }]}> | ||||
|                               <Input placeholder='value' /> | ||||
|                             </Form.Item> | ||||
|                             {/* <Button onClick={() => add()} className='flex items-center'> | ||||
|                               + | ||||
|                             </Button> */} | ||||
|                             <Button onClick={() => remove(field.name)}>-</Button> | ||||
|                           </div> | ||||
|                         ); | ||||
|                       })} | ||||
|                     </> | ||||
|                   ); | ||||
|                 }} | ||||
|               </Form.List> | ||||
|             </Form> | ||||
|           </div> | ||||
|         </div> | ||||
|       </div> | ||||
|       <FormModal /> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
							
								
								
									
										18
									
								
								src/pages/prompt/graph/d3.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/pages/prompt/graph/d3.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| .node circle { | ||||
|   fill: #69b3a2; | ||||
|   stroke: #333; | ||||
|   stroke-width: 1.5px; | ||||
| } | ||||
|  | ||||
| .link { | ||||
|   fill: none; | ||||
|   stroke: #999; | ||||
|   stroke-opacity: 0.6; | ||||
|   stroke-width: 1.5px; | ||||
| } | ||||
|  | ||||
| text { | ||||
|   font-family: Arial, sans-serif; | ||||
|   font-size: 12px; | ||||
|   pointer-events: none; | ||||
| } | ||||
							
								
								
									
										122
									
								
								src/pages/prompt/graph/d3.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								src/pages/prompt/graph/d3.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,122 @@ | ||||
| // @ts-nocheck | ||||
| import * as d3 from 'd3'; | ||||
| import './d3.css'; | ||||
|  | ||||
| export const drawGraph = (graphData) => { | ||||
|   // 初始配置:宽度和高度通过容器自适应 | ||||
|   const svg = d3.select('.ai-graph'); | ||||
|    | ||||
|   const margin = { top: 20, right: 20, bottom: 20, left: 20 }; | ||||
|    | ||||
|   const updateChartSize = () => { | ||||
|     const width = svg.node().clientWidth - margin.left - margin.right; | ||||
|     const height = svg.node().clientHeight - margin.top - margin.bottom; | ||||
|      | ||||
|     svg.attr('viewBox', `0 0 ${width} ${height}`); | ||||
|      | ||||
|     return { width, height }; | ||||
|   }; | ||||
|  | ||||
|   let { width, height } = updateChartSize(); | ||||
|  | ||||
|   // 使用力导向布局 | ||||
|   const simulation = d3 | ||||
|     .forceSimulation(graphData.nodes) | ||||
|     .force( | ||||
|       'link', | ||||
|       d3 | ||||
|         .forceLink(graphData.links) | ||||
|         .id((d) => d.id) | ||||
|         .distance(100) | ||||
|     ) | ||||
|     .force('charge', d3.forceManyBody().strength(-300)) | ||||
|     .force('center', d3.forceCenter(width / 2, height / 2)) | ||||
|     .force('collide', d3.forceCollide(20));  // 防止节点重叠,半径设置为20 | ||||
|  | ||||
|   // 绘制连线 | ||||
|   const link = svg.append('g') | ||||
|     .selectAll('line') | ||||
|     .data(graphData.links) | ||||
|     .enter() | ||||
|     .append('line') | ||||
|     .attr('class', 'link'); | ||||
|  | ||||
|   // 绘制节点 | ||||
|   const node = svg.append('g') | ||||
|     .selectAll('g') | ||||
|     .data(graphData.nodes) | ||||
|     .enter() | ||||
|     .append('g') | ||||
|     .attr('class', 'node'); | ||||
|  | ||||
|   node.append('circle').attr('r', 10); | ||||
|  | ||||
|   // 添加节点标签 | ||||
|   node | ||||
|     .append('text') | ||||
|     .attr('dx', 12) | ||||
|     .attr('dy', '.35em') | ||||
|     .text((d) => d.label); | ||||
|  | ||||
|   // 限制节点在SVG范围内 | ||||
|   const clampPosition = (d, width, height) => { | ||||
|     d.x = Math.max(10, Math.min(width - 10, d.x));  // 10为节点半径 | ||||
|     d.y = Math.max(10, Math.min(height - 10, d.y)); | ||||
|   }; | ||||
|  | ||||
|   // 更新节点和连线位置 | ||||
|   simulation.on('tick', () => { | ||||
|     link | ||||
|       .attr('x1', (d) => d.source.x) | ||||
|       .attr('y1', (d) => d.source.y) | ||||
|       .attr('x2', (d) => d.target.x) | ||||
|       .attr('y2', (d) => d.target.y); | ||||
|  | ||||
|     node.attr('transform', (d) => { | ||||
|       // 限制节点在SVG内部 | ||||
|       clampPosition(d, width, height); | ||||
|       return `translate(${d.x},${d.y})`; | ||||
|     }); | ||||
|   }); | ||||
|  | ||||
|   // 添加拖拽事件 | ||||
|   node.call( | ||||
|     d3 | ||||
|       .drag() | ||||
|       .on('start', (event, d) => { | ||||
|         if (!event.active) simulation.alphaTarget(0.3).restart(); | ||||
|         d.fx = d.x; | ||||
|         d.fy = d.y; | ||||
|       }) | ||||
|       .on('drag', (event, d) => { | ||||
|         d.fx = event.x; | ||||
|         d.fy = event.y; | ||||
|       }) | ||||
|       .on('end', (event, d) => { | ||||
|         if (!event.active) simulation.alphaTarget(0); | ||||
|         d.fx = null; | ||||
|         d.fy = null; | ||||
|       }) | ||||
|   ); | ||||
|  | ||||
|   // 添加双击事件 | ||||
|   node.on('dblclick', (event, d) => { | ||||
|     d.fx = null; | ||||
|     d.fy = null; | ||||
|     console.log('dblclick', d); | ||||
|   }); | ||||
|  | ||||
|   // 监听窗口大小变化时更新图表 | ||||
|   const resize = () => { | ||||
|     const { width, height } = updateChartSize(); | ||||
|     simulation.force('center', d3.forceCenter(width / 2, height / 2)); | ||||
|     simulation.alpha(1).restart(); // 重启仿真以更新布局 | ||||
|   }; | ||||
|  | ||||
|   window.addEventListener('resize', resize); | ||||
|  | ||||
|   // 在需要的时候手动调用,销毁图表时可以移除监听 | ||||
|   return () => { | ||||
|     window.removeEventListener('resize', resize); | ||||
|   }; | ||||
| }; | ||||
| @@ -1,57 +1,18 @@ | ||||
| import { Button, Form, Input } from 'antd'; | ||||
| import { TextArea } from '../container/components/TextArea'; | ||||
| import clsx from 'clsx'; | ||||
| import { Routes, Route, Navigate } from 'react-router-dom'; | ||||
| import { Edit } from './edit/Edit'; | ||||
| import { List } from './edit/List'; | ||||
| import { D3Grahp } from './D3'; | ||||
| import { Main } from './layout/Main'; | ||||
|  | ||||
| export const App = () => { | ||||
|   const [form] = Form.useForm(); | ||||
|   const onFinish = (values: any) => { | ||||
|     console.log('Success:', values); | ||||
|   }; | ||||
|   const onSave = () => { | ||||
|     // | ||||
|   }; | ||||
|   const isEdit = form.getFieldValue('id'); | ||||
|   return ( | ||||
|     <div className='w-full h-full felx flex-col bg-gray-200'> | ||||
|       <h1 className='text-center py-4'>Prompt JS Code Generate</h1> | ||||
|       <div className='py-2 px-4 w-3/4 min-w-[600px] mx-auto border shadow rounded bg-white'> | ||||
|         <Form form={form} onFinish={onFinish} className='mt-4' labelCol={{ span: 4 }}> | ||||
|           <Form.Item name='id' hidden> | ||||
|             <Input /> | ||||
|           </Form.Item> | ||||
|           <Form.Item name='title' label='Title'> | ||||
|             <Input /> | ||||
|           </Form.Item> | ||||
|           <Form.Item name='description' label='Description'> | ||||
|             <Input.TextArea rows={4} /> | ||||
|           </Form.Item> | ||||
|           <Form.Item name='code' label='Code'> | ||||
|             <TextArea className='max-h-full' style={{ minHeight: 300 }} /> | ||||
|           </Form.Item> | ||||
|           <Form.Item label=' ' colon={false}> | ||||
|             <div className='flex gap-2'> | ||||
|               <Button type='primary' htmlType='submit'> | ||||
|                 Generate | ||||
|               </Button> | ||||
|               <Button htmlType='reset'>Reset</Button> | ||||
|               <Button | ||||
|                 type='primary' | ||||
|                 onClick={() => { | ||||
|                   // | ||||
|                 }}> | ||||
|                 Save | ||||
|               </Button> | ||||
|               <Button | ||||
|                 className={clsx(isEdit ? 'block' : 'hidden')} | ||||
|                 onClick={() => { | ||||
|                   // | ||||
|                 }}> | ||||
|                 Preview | ||||
|               </Button> | ||||
|             </div> | ||||
|           </Form.Item> | ||||
|         </Form> | ||||
|       </div> | ||||
|     </div> | ||||
|     <Routes> | ||||
|       <Route element={<Main />}> | ||||
|         <Route path='/' element={<Navigate to='/prompt/list' />} /> | ||||
|         <Route path='/graph' element={<D3Grahp />} /> | ||||
|         <Route path='/edit' element={<Edit />} /> | ||||
|         <Route path='/list' element={<List />} /> | ||||
|       </Route> | ||||
|     </Routes> | ||||
|   ); | ||||
| }; | ||||
|   | ||||
							
								
								
									
										13
									
								
								src/pages/prompt/layout/Main.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								src/pages/prompt/layout/Main.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| import { AiMoudle } from '@/pages/ai-chat'; | ||||
| import { Outlet } from 'react-router'; | ||||
|  | ||||
| export const Main = () => { | ||||
|   return ( | ||||
|     <div className='w-full h-full flex'> | ||||
|       <div className='flex-grow h-full'> | ||||
|         <Outlet /> | ||||
|       </div> | ||||
|       <AiMoudle /> | ||||
|     </div> | ||||
|   ); | ||||
| }; | ||||
							
								
								
									
										97
									
								
								src/pages/prompt/store/prompt.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								src/pages/prompt/store/prompt.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,97 @@ | ||||
| import { create } from 'zustand'; | ||||
| import { query } from '@/modules'; | ||||
| import { message } from 'antd'; | ||||
| type PromptStore = { | ||||
|   showEdit: boolean; | ||||
|   setShowEdit: (showEdit: boolean) => void; | ||||
|   formData: any; | ||||
|   setFormData: (formData: any) => void; | ||||
|   loading: boolean; | ||||
|   setLoading: (loading: boolean) => void; | ||||
|   list: any[]; | ||||
|   getList: () => Promise<void>; | ||||
|   updateData: (data: any) => Promise<void>; | ||||
|   deleteData: (id: string) => Promise<void>; | ||||
|   runAi: () => any; | ||||
| }; | ||||
| export const usePromptStore = create<PromptStore>((set, get) => { | ||||
|   return { | ||||
|     showEdit: false, | ||||
|     setShowEdit: (showEdit) => set({ showEdit }), | ||||
|     formData: {}, | ||||
|     setFormData: (formData) => set({ formData }), | ||||
|     loading: false, | ||||
|     setLoading: (loading) => set({ loading }), | ||||
|     list: [], | ||||
|     getList: async () => { | ||||
|       set({ loading: true }); | ||||
|       const res = await query.post({ | ||||
|         path: 'prompt', | ||||
|         key: 'list', | ||||
|       }); | ||||
|       set({ loading: false }); | ||||
|       if (res.code === 200) { | ||||
|         set({ list: res.data }); | ||||
|       } else { | ||||
|         message.error(res.msg || 'Request failed'); | ||||
|       } | ||||
|     }, | ||||
|     updateData: async (data) => { | ||||
|       const { getList } = get(); | ||||
|       const res = await query.post({ | ||||
|         path: 'prompt', | ||||
|         key: 'update', | ||||
|         data, | ||||
|       }); | ||||
|       if (res.code === 200) { | ||||
|         message.success('Success'); | ||||
|         set({ showEdit: false, formData: [] }); | ||||
|         getList(); | ||||
|       } else { | ||||
|         message.error(res.msg || 'Request failed'); | ||||
|       } | ||||
|     }, | ||||
|     deleteData: async (id) => { | ||||
|       const { getList } = get(); | ||||
|       const res = await query.post({ | ||||
|         path: 'prompt', | ||||
|         key: 'delete', | ||||
|         id, | ||||
|       }); | ||||
|       if (res.code === 200) { | ||||
|         getList(); | ||||
|         message.success('Success'); | ||||
|       } else { | ||||
|         message.error(res.msg || 'Request failed'); | ||||
|       } | ||||
|     }, | ||||
|     runAi: async () => { | ||||
|       const { formData } = get(); | ||||
|       const res = await query.post({ | ||||
|         path: 'ai', | ||||
|         key: 'run', | ||||
|         data: { | ||||
|           key: formData.key, | ||||
|           inputs: [ | ||||
|             { | ||||
|               key: 'title', | ||||
|               value: '根据描述生成代码', | ||||
|             }, | ||||
|             { | ||||
|               key: 'description', | ||||
|               value: '我想获取一个card, 包含标题和内容,标题是evision,内容是这是一个测试', | ||||
|             }, | ||||
|           ], | ||||
|           data: { | ||||
|             title: formData.title, | ||||
|             description: formData.description, | ||||
|           }, | ||||
|         }, | ||||
|       }); | ||||
|       if (res.code === 200) { | ||||
|         console.log(res.data); | ||||
|         message.success('Success'); | ||||
|       } | ||||
|     }, | ||||
|   }; | ||||
| }); | ||||
		Reference in New Issue
	
	Block a user