feat: 优化界面显示,对deck添加编辑功能
This commit is contained in:
		| @@ -27,6 +27,7 @@ | |||||||
|     "clsx": "^2.1.1", |     "clsx": "^2.1.1", | ||||||
|     "copy-to-clipboard": "^3.3.3", |     "copy-to-clipboard": "^3.3.3", | ||||||
|     "d3": "^7.9.0", |     "d3": "^7.9.0", | ||||||
|  |     "eventemitter3": "^5.0.1", | ||||||
|     "immer": "^10.1.1", |     "immer": "^10.1.1", | ||||||
|     "lodash-es": "^4.17.21", |     "lodash-es": "^4.17.21", | ||||||
|     "marked": "^14.1.2", |     "marked": "^14.1.2", | ||||||
| @@ -42,8 +43,10 @@ | |||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@eslint/js": "^9.11.0", |     "@eslint/js": "^9.11.0", | ||||||
|     "@tailwindcss/aspect-ratio": "^0.4.2", |     "@tailwindcss/aspect-ratio": "^0.4.2", | ||||||
|  |     "@tailwindcss/line-clamp": "^0.4.4", | ||||||
|     "@tailwindcss/typography": "^0.5.15", |     "@tailwindcss/typography": "^0.5.15", | ||||||
|     "@types/d3": "^7.4.3", |     "@types/d3": "^7.4.3", | ||||||
|  |     "@types/lodash-es": "^4.17.12", | ||||||
|     "@types/node": "^22.5.5", |     "@types/node": "^22.5.5", | ||||||
|     "@types/react": "^18.3.8", |     "@types/react": "^18.3.8", | ||||||
|     "@types/react-dom": "^18.3.0", |     "@types/react-dom": "^18.3.0", | ||||||
| @@ -53,6 +56,7 @@ | |||||||
|     "eslint-plugin-react-hooks": "^5.1.0-rc.0", |     "eslint-plugin-react-hooks": "^5.1.0-rc.0", | ||||||
|     "eslint-plugin-react-refresh": "^0.4.12", |     "eslint-plugin-react-refresh": "^0.4.12", | ||||||
|     "globals": "^15.9.0", |     "globals": "^15.9.0", | ||||||
|  |     "postcss-import": "^16.1.0", | ||||||
|     "react-is": "^18.3.1", |     "react-is": "^18.3.1", | ||||||
|     "tailwind-merge": "^2.5.2", |     "tailwind-merge": "^2.5.2", | ||||||
|     "tailwindcss": "^3.4.13", |     "tailwindcss": "^3.4.13", | ||||||
|   | |||||||
							
								
								
									
										25
									
								
								plugins/flex.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								plugins/flex.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | |||||||
|  | const plugin = require('tailwindcss/plugin'); | ||||||
|  |  | ||||||
|  | const flexCenterBaseStyles = { | ||||||
|  |   display: 'flex', | ||||||
|  |   'justify-content': 'center', | ||||||
|  |   'align-items': 'center', | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /** flex 居中 */ | ||||||
|  | const flexCenter = plugin(function ({ addUtilities }) { | ||||||
|  |   addUtilities({ | ||||||
|  |     /** flex 居中 */ | ||||||
|  |     '.flex-row-center': flexCenterBaseStyles, | ||||||
|  |     '.flex-col-center': { ...flexCenterBaseStyles, 'flex-direction': 'column' }, | ||||||
|  |     '.layout-menu': {}, | ||||||
|  |     '.scrollbar': {}, | ||||||
|  |     '.card': {}, | ||||||
|  |     '.card-title': {}, | ||||||
|  |     '.card-subtitle': {}, | ||||||
|  |     '.card-body': {}, | ||||||
|  |     '.card-footer': {}, | ||||||
|  |   }); | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | module.exports = flexCenter; | ||||||
							
								
								
									
										46
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										46
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							| @@ -53,6 +53,9 @@ importers: | |||||||
|       d3: |       d3: | ||||||
|         specifier: ^7.9.0 |         specifier: ^7.9.0 | ||||||
|         version: 7.9.0 |         version: 7.9.0 | ||||||
|  |       eventemitter3: | ||||||
|  |         specifier: ^5.0.1 | ||||||
|  |         version: 5.0.1 | ||||||
|       immer: |       immer: | ||||||
|         specifier: ^10.1.1 |         specifier: ^10.1.1 | ||||||
|         version: 10.1.1 |         version: 10.1.1 | ||||||
| @@ -93,12 +96,18 @@ importers: | |||||||
|       '@tailwindcss/aspect-ratio': |       '@tailwindcss/aspect-ratio': | ||||||
|         specifier: ^0.4.2 |         specifier: ^0.4.2 | ||||||
|         version: 0.4.2(tailwindcss@3.4.13) |         version: 0.4.2(tailwindcss@3.4.13) | ||||||
|  |       '@tailwindcss/line-clamp': | ||||||
|  |         specifier: ^0.4.4 | ||||||
|  |         version: 0.4.4(tailwindcss@3.4.13) | ||||||
|       '@tailwindcss/typography': |       '@tailwindcss/typography': | ||||||
|         specifier: ^0.5.15 |         specifier: ^0.5.15 | ||||||
|         version: 0.5.15(tailwindcss@3.4.13) |         version: 0.5.15(tailwindcss@3.4.13) | ||||||
|       '@types/d3': |       '@types/d3': | ||||||
|         specifier: ^7.4.3 |         specifier: ^7.4.3 | ||||||
|         version: 7.4.3 |         version: 7.4.3 | ||||||
|  |       '@types/lodash-es': | ||||||
|  |         specifier: ^4.17.12 | ||||||
|  |         version: 4.17.12 | ||||||
|       '@types/node': |       '@types/node': | ||||||
|         specifier: ^22.5.5 |         specifier: ^22.5.5 | ||||||
|         version: 22.5.5 |         version: 22.5.5 | ||||||
| @@ -126,6 +135,9 @@ importers: | |||||||
|       globals: |       globals: | ||||||
|         specifier: ^15.9.0 |         specifier: ^15.9.0 | ||||||
|         version: 15.9.0 |         version: 15.9.0 | ||||||
|  |       postcss-import: | ||||||
|  |         specifier: ^16.1.0 | ||||||
|  |         version: 16.1.0(postcss@8.4.47) | ||||||
|       react-is: |       react-is: | ||||||
|         specifier: ^18.3.1 |         specifier: ^18.3.1 | ||||||
|         version: 18.3.1 |         version: 18.3.1 | ||||||
| @@ -733,6 +745,11 @@ packages: | |||||||
|     peerDependencies: |     peerDependencies: | ||||||
|       tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1' |       tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1' | ||||||
|  |  | ||||||
|  |   '@tailwindcss/line-clamp@0.4.4': | ||||||
|  |     resolution: {integrity: sha512-5U6SY5z8N42VtrCrKlsTAA35gy2VSyYtHWCsg1H87NU1SXnEfekTVlrga9fzUDrrHcGi2Lb5KenUWb4lRQT5/g==} | ||||||
|  |     peerDependencies: | ||||||
|  |       tailwindcss: '>=2.0.0 || >=3.0.0 || >=3.0.0-alpha.1' | ||||||
|  |  | ||||||
|   '@tailwindcss/typography@0.5.15': |   '@tailwindcss/typography@0.5.15': | ||||||
|     resolution: {integrity: sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==} |     resolution: {integrity: sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==} | ||||||
|     peerDependencies: |     peerDependencies: | ||||||
| @@ -855,6 +872,12 @@ packages: | |||||||
|   '@types/hast@3.0.4': |   '@types/hast@3.0.4': | ||||||
|     resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} |     resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} | ||||||
|  |  | ||||||
|  |   '@types/lodash-es@4.17.12': | ||||||
|  |     resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} | ||||||
|  |  | ||||||
|  |   '@types/lodash@4.17.9': | ||||||
|  |     resolution: {integrity: sha512-w9iWudx1XWOHW5lQRS9iKpK/XuRhnN+0T7HvdCCd802FYkT1AMTnxndJHGrNJwRoRHkslGr4S29tjm1cT7x/7w==} | ||||||
|  |  | ||||||
|   '@types/mdast@4.0.4': |   '@types/mdast@4.0.4': | ||||||
|     resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} |     resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} | ||||||
|  |  | ||||||
| @@ -1861,6 +1884,12 @@ packages: | |||||||
|     peerDependencies: |     peerDependencies: | ||||||
|       postcss: ^8.0.0 |       postcss: ^8.0.0 | ||||||
|  |  | ||||||
|  |   postcss-import@16.1.0: | ||||||
|  |     resolution: {integrity: sha512-7hsAZ4xGXl4MW+OKEWCnF6T5jqBw80/EE9aXg1r2yyn1RsVEU8EtKXbijEODa+rg7iih4bKf7vlvTGYR4CnPNg==} | ||||||
|  |     engines: {node: '>=18.0.0'} | ||||||
|  |     peerDependencies: | ||||||
|  |       postcss: ^8.0.0 | ||||||
|  |  | ||||||
|   postcss-js@4.0.1: |   postcss-js@4.0.1: | ||||||
|     resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} |     resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} | ||||||
|     engines: {node: ^12 || ^14 || >= 16} |     engines: {node: ^12 || ^14 || >= 16} | ||||||
| @@ -3142,6 +3171,10 @@ snapshots: | |||||||
|     dependencies: |     dependencies: | ||||||
|       tailwindcss: 3.4.13 |       tailwindcss: 3.4.13 | ||||||
|  |  | ||||||
|  |   '@tailwindcss/line-clamp@0.4.4(tailwindcss@3.4.13)': | ||||||
|  |     dependencies: | ||||||
|  |       tailwindcss: 3.4.13 | ||||||
|  |  | ||||||
|   '@tailwindcss/typography@0.5.15(tailwindcss@3.4.13)': |   '@tailwindcss/typography@0.5.15(tailwindcss@3.4.13)': | ||||||
|     dependencies: |     dependencies: | ||||||
|       lodash.castarray: 4.4.0 |       lodash.castarray: 4.4.0 | ||||||
| @@ -3300,6 +3333,12 @@ snapshots: | |||||||
|     dependencies: |     dependencies: | ||||||
|       '@types/unist': 3.0.3 |       '@types/unist': 3.0.3 | ||||||
|  |  | ||||||
|  |   '@types/lodash-es@4.17.12': | ||||||
|  |     dependencies: | ||||||
|  |       '@types/lodash': 4.17.9 | ||||||
|  |  | ||||||
|  |   '@types/lodash@4.17.9': {} | ||||||
|  |  | ||||||
|   '@types/mdast@4.0.4': |   '@types/mdast@4.0.4': | ||||||
|     dependencies: |     dependencies: | ||||||
|       '@types/unist': 3.0.3 |       '@types/unist': 3.0.3 | ||||||
| @@ -4428,6 +4467,13 @@ snapshots: | |||||||
|       read-cache: 1.0.0 |       read-cache: 1.0.0 | ||||||
|       resolve: 1.22.8 |       resolve: 1.22.8 | ||||||
|  |  | ||||||
|  |   postcss-import@16.1.0(postcss@8.4.47): | ||||||
|  |     dependencies: | ||||||
|  |       postcss: 8.4.47 | ||||||
|  |       postcss-value-parser: 4.2.0 | ||||||
|  |       read-cache: 1.0.0 | ||||||
|  |       resolve: 1.22.8 | ||||||
|  |  | ||||||
|   postcss-js@4.0.1(postcss@8.4.47): |   postcss-js@4.0.1(postcss@8.4.47): | ||||||
|     dependencies: |     dependencies: | ||||||
|       camelcase-css: 2.0.1 |       camelcase-css: 2.0.1 | ||||||
|   | |||||||
| @@ -1,5 +1,5 @@ | |||||||
| import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom'; | import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom'; | ||||||
| import { ConfigProvider } from 'antd'; | import { ConfigProvider, App as AntApp } from 'antd'; | ||||||
| import { App as ContainerApp } from './pages/container'; | import { App as ContainerApp } from './pages/container'; | ||||||
| import { App as PanelApp } from './pages/panel'; | import { App as PanelApp } from './pages/panel'; | ||||||
| import { App as PublishApp } from './pages/publish'; | import { App as PublishApp } from './pages/publish'; | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								src/components/card/CardBlank.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/components/card/CardBlank.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | |||||||
|  | import clsx from 'clsx'; | ||||||
|  | import twMerge from 'tailwind-merge'; | ||||||
|  |  | ||||||
|  | type CardBlankProps = { | ||||||
|  |   number?: number; | ||||||
|  |   className?: string; | ||||||
|  | }; | ||||||
|  | export const CardBlank = (props: CardBlankProps) => { | ||||||
|  |   const { number = 4, className } = props; | ||||||
|  |   return ( | ||||||
|  |     <> | ||||||
|  |       {new Array(number).fill(0).map((_, index) => { | ||||||
|  |         return <div key={index} className={clsx('w-[300px] flex-shrink-0', className)}></div>; | ||||||
|  |       })} | ||||||
|  |     </> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
| @@ -3,6 +3,13 @@ | |||||||
| @tailwind utilities; | @tailwind utilities; | ||||||
|  |  | ||||||
| @layer base { | @layer base { | ||||||
|  |   html, | ||||||
|  |   body { | ||||||
|  |     width: 100%; | ||||||
|  |     height: 100%; | ||||||
|  |     font-size: 16px; | ||||||
|  |     font-family: 'Montserrat', sans-serif; | ||||||
|  |   } | ||||||
|   h1 { |   h1 { | ||||||
|     @apply text-2xl font-bold; |     @apply text-2xl font-bold; | ||||||
|   } |   } | ||||||
| @@ -12,7 +19,40 @@ | |||||||
|   h3 { |   h3 { | ||||||
|     @apply text-lg font-bold; |     @apply text-lg font-bold; | ||||||
|   } |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @layer components { | ||||||
|  |   .btn { | ||||||
|  |     @apply bg-blue-500 text-white font-bold py-2 px-4 rounded; | ||||||
|  |   } | ||||||
|  |   .card { | ||||||
|  |     @apply bg-white shadow-md rounded-lg p-4; | ||||||
|  |     .card-title { | ||||||
|  |       @apply text-lg font-bold; | ||||||
|  |     } | ||||||
|  |     .card-subtitle { | ||||||
|  |       @apply text-sm text-gray-500; | ||||||
|  |     } | ||||||
|  |     .card-description { | ||||||
|  |       @apply text-gray-700 break-words; | ||||||
|  |     } | ||||||
|  |     .card-code { | ||||||
|  |       @apply bg-gray-100 p-2; | ||||||
|  |     } | ||||||
|  |     .card-body { | ||||||
|  |       @apply text-gray-700; | ||||||
|  |     } | ||||||
|  |     .card-footer { | ||||||
|  |       @apply text-sm text-gray-500; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @layer utilities { | ||||||
|   .layout-menu { |   .layout-menu { | ||||||
|     @apply bg-gray-900 p-2 text-white flex justify-between h-12; |     @apply bg-gray-900 p-2 text-white flex justify-between h-12; | ||||||
|   } |   } | ||||||
|  |   .bg-custom-blue { | ||||||
|  |     background-color: #3490dc; | ||||||
|  |   } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								src/hooks/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/hooks/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | export * from './message'; | ||||||
							
								
								
									
										22
									
								
								src/hooks/message.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								src/hooks/message.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | import { App } from 'antd'; | ||||||
|  |  | ||||||
|  | export const useMessage = () => { | ||||||
|  |   const { message: antMessage, modal, notification } = App.useApp(); | ||||||
|  |   return { | ||||||
|  |     success: antMessage.success, | ||||||
|  |     error: antMessage.error, | ||||||
|  |     warning: antMessage.warning, | ||||||
|  |     info: antMessage.info, | ||||||
|  |     loading: antMessage.loading, | ||||||
|  |     open: antMessage.open, | ||||||
|  |     destroy: antMessage.destroy, | ||||||
|  |     modal: modal, | ||||||
|  |     notification: notification, | ||||||
|  |     message: antMessage, | ||||||
|  |     com: ( | ||||||
|  |       <> | ||||||
|  |         <App /> | ||||||
|  |       </> | ||||||
|  |     ), | ||||||
|  |   }; | ||||||
|  | }; | ||||||
| @@ -1,38 +1,112 @@ | |||||||
| import { useShallow } from 'zustand/react/shallow'; | import { useShallow } from 'zustand/react/shallow'; | ||||||
| import { useAiStore } from './store/ai-store'; | import { useAiStore } from './store/ai-store'; | ||||||
| import { CloseOutlined } from '@ant-design/icons'; | import { CloseOutlined } from '@ant-design/icons'; | ||||||
| import { Button } from 'antd'; | import { Button, Form, Input } from 'antd'; | ||||||
|  | import { useEffect } from 'react'; | ||||||
|  | import { TextArea } from '../container/components/TextArea'; | ||||||
|  | import clsx from 'clsx'; | ||||||
|  | import { marked } from 'marked'; | ||||||
|  |  | ||||||
| export const AiMoudle = () => { | export const AiMoudle = () => { | ||||||
|  |   const [form] = Form.useForm(); | ||||||
|   const aiStore = useAiStore( |   const aiStore = useAiStore( | ||||||
|     useShallow((state) => { |     useShallow((state) => { | ||||||
|       return { |       return { | ||||||
|         open: state.open, |         open: state.open, | ||||||
|         setOpen: state.setOpen, |         setOpen: state.setOpen, | ||||||
|         runAi: state.runAi, |         runAi: state.runAi, | ||||||
|  |         formData: state.formData, | ||||||
|  |         setFormData: state.setFormData, | ||||||
|  |         messages: state.messages, | ||||||
|  |         setMessages: state.setMessage, | ||||||
|       }; |       }; | ||||||
|     }), |     }), | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|  |   useEffect(() => { | ||||||
|     if (!aiStore.open) { |     if (!aiStore.open) { | ||||||
|     return null; |       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([]); | ||||||
|  |     } | ||||||
|  |   }, [aiStore.open]); | ||||||
|  |   const onSend = () => { | ||||||
|  |     const data = form.getFieldsValue(); | ||||||
|  |     aiStore.setFormData(data); | ||||||
|  |     aiStore.runAi(); | ||||||
|  |   }; | ||||||
|   return ( |   return ( | ||||||
|     <div className='w-96 flex-shrink-0 bg-gray-100 border-l-2 shadow-lg flex flex-col'> |     <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 gap-4 bg-slate-400 p-2'> |       <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> |         <Button className='position ml-4 !bg-slate-400 !border-black' onClick={() => aiStore.setOpen(false)} icon={<CloseOutlined />}></Button> | ||||||
|         <h1 className='ml-10'>Ai Moudle</h1> |         <h1 className='ml-10'>Ai Moudle</h1> | ||||||
|       </div> |       </div> | ||||||
|       <div className='flex-grow p-2'> |       <div className='flex-grow p-2 overflow-hidden h-full flex flex-col'> | ||||||
|         <div> chat message</div> |         <div className='flex flex-col'> | ||||||
|         <div> |           <div className='mb-3'> chat message</div> | ||||||
|           <Button |           {aiStore?.messages?.map((message, index) => { | ||||||
|             onClick={() => { |             const html = marked.parse(message?.content); | ||||||
|               aiStore.runAi(); |             return ( | ||||||
|             }}> |               <div key={index} className=' justify-between px-4 w-full'> | ||||||
|  |                 <div className='h3 font-bold'>{message?.role}</div> | ||||||
|  |                 <div className='p-4 text-xs border shadow-sm rounded-sm scrollbar max-h-[200px] w-full overflow-scroll' dangerouslySetInnerHTML={{ __html: html }}> | ||||||
|  |                 </div> | ||||||
|  |               </div> | ||||||
|  |             ); | ||||||
|  |           })} | ||||||
|  |         </div> | ||||||
|  |         <div className='mt-4'> | ||||||
|  |           <Form form={form}> | ||||||
|  |             <Form.Item hidden name='id'> | ||||||
|  |               <Input placeholder='message' /> | ||||||
|  |             </Form.Item> | ||||||
|  |             <Form.Item name='messages' hidden> | ||||||
|  |               <Input placeholder='message' /> | ||||||
|  |             </Form.Item> | ||||||
|  |             <Form.Item name='key' hidden> | ||||||
|  |               <Input placeholder='key' /> | ||||||
|  |             </Form.Item> | ||||||
|  |             <Form.List name={'inputs'} initialValue={[]}> | ||||||
|  |               {(fields, { add, remove }) => { | ||||||
|  |                 return ( | ||||||
|  |                   <> | ||||||
|  |                     {fields.map((field, index) => { | ||||||
|  |                       const key = form.getFieldValue(['inputs', index, 'key']); | ||||||
|  |                       console.log('key', key); | ||||||
|  |                       const isTitle = key === 'title'; | ||||||
|  |  | ||||||
|  |                       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: isTitle ? 40 : 300 }} placeholder={key} /> | ||||||
|  |                           </Form.Item> | ||||||
|  |                         </div> | ||||||
|  |                       ); | ||||||
|  |                     })} | ||||||
|  |                   </> | ||||||
|  |                 ); | ||||||
|  |               }} | ||||||
|  |             </Form.List> | ||||||
|  |           </Form> | ||||||
|  |           <div className='p-4'> | ||||||
|  |             <Button className='mt-4' onClick={onSend}> | ||||||
|               Send |               Send | ||||||
|             </Button> |             </Button> | ||||||
|           </div> |           </div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |     </div> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,6 +1,19 @@ | |||||||
| import { query } from '@/modules'; | import { query } from '@/modules'; | ||||||
| import { message } from 'antd'; | import { message } from 'antd'; | ||||||
| import { create } from 'zustand'; | import { create } from 'zustand'; | ||||||
|  | type ResData = { | ||||||
|  |   created_at: string; | ||||||
|  |   done?: boolean; | ||||||
|  |   done_reason?: string; | ||||||
|  |   eval_count?: number; | ||||||
|  |   eval_duration?: number; | ||||||
|  |   load_duration?: number; | ||||||
|  |   message?: { role?: string; content?: string }[]; | ||||||
|  |   model?: string; | ||||||
|  |   prompt_eval_count?: number; | ||||||
|  |   prompt_eval_duration?: number; | ||||||
|  |   total_duration?: number; | ||||||
|  | }; | ||||||
|  |  | ||||||
| export type AiStore = { | export type AiStore = { | ||||||
|   open: boolean; |   open: boolean; | ||||||
| @@ -12,9 +25,13 @@ export type AiStore = { | |||||||
|   sendMsg: (msg: string) => void; |   sendMsg: (msg: string) => void; | ||||||
|   formData: any; |   formData: any; | ||||||
|   setFormData: (data: any) => void; |   setFormData: (data: any) => void; | ||||||
|  |   data: any; | ||||||
|  |   setData: (data: any) => void; | ||||||
|   runAi: () => any; |   runAi: () => any; | ||||||
|   title: string; |   title: string; | ||||||
|   setTitle: (title: string) => void; |   setTitle: (title: string) => void; | ||||||
|  |   messages: { role: string; content: string }[]; | ||||||
|  |   setMessage: (message: { role: string; content: string }[]) => void; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const useAiStore = create<AiStore>((set, get) => { | export const useAiStore = create<AiStore>((set, get) => { | ||||||
| @@ -28,31 +45,60 @@ export const useAiStore = create<AiStore>((set, get) => { | |||||||
|     }, |     }, | ||||||
|     formData: {}, |     formData: {}, | ||||||
|     setFormData: (data) => set({ formData: data }), |     setFormData: (data) => set({ formData: data }), | ||||||
|  |     data: {}, | ||||||
|  |     setData: (data) => { | ||||||
|  |       const { key, presetData = {} } = data; | ||||||
|  |       console.log('key', presetData, data); | ||||||
|  |       if (!key) { | ||||||
|  |         console.error('key is required'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       const { inputs = [] } = presetData.data || {}; | ||||||
|  |       const formData = { | ||||||
|  |         key, | ||||||
|  |         inputs: inputs.map((input) => { | ||||||
|  |           return { | ||||||
|  |             key: input.key, | ||||||
|  |             value: input.value, | ||||||
|  |             type: 'string', | ||||||
|  |           }; | ||||||
|  |         }), | ||||||
|  |         messages: [], | ||||||
|  |       }; | ||||||
|  |       console.log('formData', formData); | ||||||
|  |       set({ key, data, formData }); | ||||||
|  |     }, | ||||||
|     runAi: async () => { |     runAi: async () => { | ||||||
|       const { formData } = get(); |       const { formData, messages } = get(); | ||||||
|  |       const loading = message.loading('loading'); | ||||||
|       const res = await query.post({ |       const res = await query.post({ | ||||||
|         path: 'ai', |         path: 'ai', | ||||||
|         key: 'run', |         key: 'run', | ||||||
|         data: { |         data: { | ||||||
|           key: formData.key, |           key: formData.key, | ||||||
|           inputs: [ |           inputs: [ | ||||||
|             { |             // { | ||||||
|               key: 'title', |             //   key: 'title', | ||||||
|               value: '根据描述生成代码', |             //   value: '根据描述生成代码', | ||||||
|             }, |             // }, | ||||||
|             { |             // { | ||||||
|               key: 'description', |             //   key: 'description', | ||||||
|               value: '我想获取一个card, 包含标题和内容,标题是evision,内容是这是一个测试', |             //   value: '我想获取一个card, 包含标题和内容,标题是evision,内容是这是一个测试', | ||||||
|             }, |             // }, | ||||||
|  |             ...formData.inputs, | ||||||
|           ], |           ], | ||||||
|         }, |         }, | ||||||
|       }); |       }); | ||||||
|  |       loading(); | ||||||
|       if (res.code === 200) { |       if (res.code === 200) { | ||||||
|         console.log(res.data); |         console.log(res.data); | ||||||
|         message.success('Success'); |         message.success('Success'); | ||||||
|  |         set({ messages: [...messages, res.data.message] }); | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     title: '', |     title: '', | ||||||
|     setTitle: (title) => set({ title }), |     setTitle: (title) => set({ title }), | ||||||
|  |     messages: [], | ||||||
|  |     setMessage: (messages) => set({ messages }), | ||||||
|   }; |   }; | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -4,13 +4,15 @@ import { useLocation, useNavigate } from 'react-router'; | |||||||
| import { useCodeEditorStore, ParseData } from './store'; | import { useCodeEditorStore, ParseData } from './store'; | ||||||
| import { useToCodeEditor } from './hooks/use-to-code-editor'; | import { useToCodeEditor } from './hooks/use-to-code-editor'; | ||||||
| export { useToCodeEditor }; | export { useToCodeEditor }; | ||||||
| import { Button, message } from 'antd'; | import { Button, message, Tooltip } from 'antd'; | ||||||
|  | import { LeftOutlined, SaveOutlined } from '@ant-design/icons'; | ||||||
| export const App = () => { | export const App = () => { | ||||||
|   const ref = useRef<HTMLDivElement>(null); |   const ref = useRef<HTMLDivElement>(null); | ||||||
|   const editorRef = useRef<typeof editor>(null); |   const editorRef = useRef<typeof editor>(null); | ||||||
|   const location = useLocation(); |   const location = useLocation(); | ||||||
|   const store = useCodeEditorStore(); |   const store = useCodeEditorStore(); | ||||||
|   const [mounted, setMounted] = useState(false); |   const [mounted, setMounted] = useState(false); | ||||||
|  |   const navigator = useNavigate(); | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     initEditor(); |     initEditor(); | ||||||
|     const state = location.state as ParseData; |     const state = location.state as ParseData; | ||||||
| @@ -63,13 +65,28 @@ export const App = () => { | |||||||
|     } |     } | ||||||
|     store.onUpdate(value); |     store.onUpdate(value); | ||||||
|   }, [store.dataType]); |   }, [store.dataType]); | ||||||
|  |  | ||||||
|   return ( |   return ( | ||||||
|     <div className='w-full h-full bg-gray-400'> |     <div className='w-full h-full bg-gray-400 flex flex-col'> | ||||||
|       <div className='px-2 bg-white mb-4'>Code Editor</div> |       <div className='layout-menu'>Code Editor</div> | ||||||
|       <Button onClick={onSave}>Save</Button> |       <div className='p-4 flex-grow'> | ||||||
|  |         <Button.Group className='mb-2'> | ||||||
|  |           <Tooltip title='Go Back' placement='bottom'> | ||||||
|  |             <Button | ||||||
|  |               className='' | ||||||
|  |               icon={<LeftOutlined />} | ||||||
|  |               onClick={() => { | ||||||
|  |                 navigator(-1); | ||||||
|  |               }}></Button> | ||||||
|  |           </Tooltip> | ||||||
|  |           <Tooltip title='Save' placement='bottom'> | ||||||
|  |             <Button className='' onClick={onSave} icon={<SaveOutlined />}></Button> | ||||||
|  |           </Tooltip> | ||||||
|  |         </Button.Group> | ||||||
|         <div className='w-full p-4 rounded-md bg-white' style={{ height: 'calc(100% - 130px)' }}> |         <div className='w-full p-4 rounded-md bg-white' style={{ height: 'calc(100% - 130px)' }}> | ||||||
|           <div className='w-full h-full' ref={ref}></div> |           <div className='w-full h-full' ref={ref}></div> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|  |     </div> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -58,6 +58,9 @@ const FormModal = () => { | |||||||
|         <Form.Item name='title' label='title'> |         <Form.Item name='title' label='title'> | ||||||
|           <Input /> |           <Input /> | ||||||
|         </Form.Item> |         </Form.Item> | ||||||
|  |         <Form.Item name='description' label='description'> | ||||||
|  |           <Input.TextArea rows={4} /> | ||||||
|  |         </Form.Item> | ||||||
|         <Form.Item name='code' label='code'> |         <Form.Item name='code' label='code'> | ||||||
|           <TextArea /> |           <TextArea /> | ||||||
|         </Form.Item> |         </Form.Item> | ||||||
| @@ -127,7 +130,7 @@ export const ContainerList = () => { | |||||||
|                           }}> |                           }}> | ||||||
|                           {item.title || '-'} |                           {item.title || '-'} | ||||||
|                         </div> |                         </div> | ||||||
|                         <div className='font-light'>{item.description ? item.description : '-'}</div> |                         <div className='font-light text-xs mt-2'>{item.description ? item.description : '-'}</div> | ||||||
|                       </div> |                       </div> | ||||||
|                       <div className='w-full text-xs'> |                       <div className='w-full text-xs'> | ||||||
|                         <TextArea className='max-h-[240px] scrollbar' value={item.code} readonly /> |                         <TextArea className='max-h-[240px] scrollbar' value={item.code} readonly /> | ||||||
|   | |||||||
| @@ -9,9 +9,10 @@ export const App = () => { | |||||||
|         <Route path='/' element={<Navigate to='/container/edit/list' />}></Route> |         <Route path='/' element={<Navigate to='/container/edit/list' />}></Route> | ||||||
|         <Route path='edit/list' element={<ContainerList />} /> |         <Route path='edit/list' element={<ContainerList />} /> | ||||||
|         <Route path='preview/:id/wrapper' element={<PreviewWrapper />} /> |         <Route path='preview/:id/wrapper' element={<PreviewWrapper />} /> | ||||||
|         <Route path='/' element={<div>Home</div>} /> |  | ||||||
|       </Route> |       </Route> | ||||||
|       <Route path='preview/:id' element={<Preview />} /> |       <Route path='preview/:id' element={<Preview />} /> | ||||||
|     </Routes> |     </Routes> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | export * from './module/Select'; | ||||||
|   | |||||||
							
								
								
									
										39
									
								
								src/pages/container/module/Select.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/pages/container/module/Select.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,39 @@ | |||||||
|  | import { query } from '@/modules'; | ||||||
|  | import { Select as AntSelect, message, SelectProps } from 'antd'; | ||||||
|  | import { useEffect, useState } from 'react'; | ||||||
|  |  | ||||||
|  | export const Select = (props: SelectProps) => { | ||||||
|  |   const [options, setOptions] = useState<{ value: string; id: string }[]>([]); | ||||||
|  |   useEffect(() => { | ||||||
|  |     fetch(); | ||||||
|  |   }, []); | ||||||
|  |   const fetch = async () => { | ||||||
|  |     const res = await query.post({ | ||||||
|  |       path: 'container', | ||||||
|  |       key: 'list', | ||||||
|  |     }); | ||||||
|  |     if (res.code !== 200) { | ||||||
|  |       message.error(res.message || '获取容器列表失败'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     const data = res.data || []; | ||||||
|  |     setOptions( | ||||||
|  |       data.map((item: any) => { | ||||||
|  |         return { | ||||||
|  |           label: item.title, | ||||||
|  |           value: item.id, | ||||||
|  |         }; | ||||||
|  |       }), | ||||||
|  |     ); | ||||||
|  |   }; | ||||||
|  |   return ( | ||||||
|  |     <AntSelect | ||||||
|  |       {...props} | ||||||
|  |       options={options} | ||||||
|  |       // onChange={(e) => { | ||||||
|  |       //   const labelValue = options.find((item) => item.value === e); | ||||||
|  |       //   props.onChange?.(e, options); | ||||||
|  |       // }} | ||||||
|  |     /> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
| @@ -1,5 +1,68 @@ | |||||||
| export const App = () => { | import clsx from 'clsx'; | ||||||
|   const serverList = ['container', 'panel', 'publish', 'code-editor', 'map']; | import { useNavigate } from 'react-router-dom'; | ||||||
|  | const serverList = ['container', 'panel', 'publish', 'code-editor', 'map', 'ai-chat']; | ||||||
|  | const serverPath = [ | ||||||
|  |   { | ||||||
|  |     path: 'container', | ||||||
|  |     links: ['edit/list', 'preview/:id', 'edit/:id'], | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     path: 'panel', | ||||||
|  |     links: ['edit/list', 'flow/:id', 'deck/:id'], | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     path: 'publish', | ||||||
|  |     links: ['edit/list'], | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     path: 'map', | ||||||
|  |     links: ['/'], | ||||||
|  |   }, | ||||||
|  |   { | ||||||
|  |     path: 'ai-chat', | ||||||
|  |     links: ['/'], | ||||||
|  |   }, | ||||||
|  | ]; | ||||||
|  | const ServerPath = () => { | ||||||
|  |   const navigate = useNavigate(); | ||||||
|  |   return ( | ||||||
|  |     <div className='p-2 w-full h-full bg-gray-200'> | ||||||
|  |       <h1 className='p-4 w-1/2 m-auto h1'>Map</h1> | ||||||
|  |       <div className='flex flex-col w-1/2 m-auto bg-white p-4 border rounded-md shadow-md'> | ||||||
|  |         {serverPath.map((item) => { | ||||||
|  |           const links = item.links.map((link) => { | ||||||
|  |             const hasId = link.includes(':id'); | ||||||
|  |             const _path = link === '/' ? item.path : item.path + '/' + link; | ||||||
|  |             return ( | ||||||
|  |               <div | ||||||
|  |                 key={link} | ||||||
|  |                 className={clsx('flex flex-col', !hasId && 'cursor-pointer')} | ||||||
|  |                 onClick={() => { | ||||||
|  |                   if (hasId) { | ||||||
|  |                     return; | ||||||
|  |                   } | ||||||
|  |                   if (link) { | ||||||
|  |                     navigate(`/${item.path}/${link}`); | ||||||
|  |                   } else { | ||||||
|  |                     navigate(`/${item.path}`); | ||||||
|  |                   } | ||||||
|  |                 }}> | ||||||
|  |                 <div className={clsx('border rounded-md p-2 m-2', hasId && 'bg-gray-200')}>{_path}</div> | ||||||
|  |               </div> | ||||||
|  |             ); | ||||||
|  |           }); | ||||||
|  |           return ( | ||||||
|  |             <div key={item.path} className='flex'> | ||||||
|  |               {links} | ||||||
|  |             </div> | ||||||
|  |           ); | ||||||
|  |         })} | ||||||
|  |       </div> | ||||||
|  |     </div> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  | export const App = ServerPath; | ||||||
|  | export const ServerList = () => { | ||||||
|   return ( |   return ( | ||||||
|     <div className='p-2 w-full h-full bg-gray-200'> |     <div className='p-2 w-full h-full bg-gray-200'> | ||||||
|       <div className='flex flex-col w-1/2 m-auto bg-white p-4 border rounded-md shadow-md'> |       <div className='flex flex-col w-1/2 m-auto bg-white p-4 border rounded-md shadow-md'> | ||||||
|   | |||||||
| @@ -1,12 +1,14 @@ | |||||||
| import { useEditStore } from '../store'; | import { useEditStore } from '../store'; | ||||||
| import { Button, Input, message, Modal, Table } from 'antd'; | import { Button, Input, message, Modal, Table, Tooltip } from 'antd'; | ||||||
| import { useEffect, useState } from 'react'; | import { useEffect, useState } from 'react'; | ||||||
| import { useShallow } from 'zustand/react/shallow'; | import { useShallow } from 'zustand/react/shallow'; | ||||||
| import { Form } from 'antd'; | import { Form } from 'antd'; | ||||||
| import copy from 'copy-to-clipboard'; | import copy from 'copy-to-clipboard'; | ||||||
| import { useNavigate } from 'react-router'; | import { useNavigate } from 'react-router'; | ||||||
| import { useToCodeEditor } from '@/pages/code-editor'; | import { useToCodeEditor } from '@/pages/code-editor'; | ||||||
|  | import { CardBlank } from '@/components/card/CardBlank'; | ||||||
|  | import { DeleteOutlined, EditOutlined, ForkOutlined, GoldOutlined, PlusOutlined, ToolOutlined } from '@ant-design/icons'; | ||||||
|  | import { isObjectNull } from '@/utils/is-null'; | ||||||
| const FormModal = () => { | const FormModal = () => { | ||||||
|   const [form] = Form.useForm(); |   const [form] = Form.useForm(); | ||||||
|   const editStore = useEditStore( |   const editStore = useEditStore( | ||||||
| @@ -15,6 +17,7 @@ const FormModal = () => { | |||||||
|         showEdit: state.showEditModal, |         showEdit: state.showEditModal, | ||||||
|         setShowEdit: state.setShowEditModal, |         setShowEdit: state.setShowEditModal, | ||||||
|         formData: state.formData, |         formData: state.formData, | ||||||
|  |         setFormData: state.setFormData, | ||||||
|         updateData: state.updateData, |         updateData: state.updateData, | ||||||
|       }; |       }; | ||||||
|     }), |     }), | ||||||
| @@ -22,17 +25,26 @@ const FormModal = () => { | |||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     const open = editStore.showEdit; |     const open = editStore.showEdit; | ||||||
|     if (open) { |     if (open) { | ||||||
|       form.setFieldsValue(editStore.formData || {}); |       if (isObjectNull(editStore.formData.data)) { | ||||||
|     } else { |         form.setFieldsValue({}); | ||||||
|       form.resetFields(); |       } else form.setFieldsValue(editStore.formData); | ||||||
|     } |     } | ||||||
|   }, [editStore.showEdit]); |   }, [editStore.showEdit]); | ||||||
|   const onFinish = async (values: any) => { |   const onFinish = async (values: any) => { | ||||||
|  |     let defaultData = { | ||||||
|  |       nodes: [], | ||||||
|  |       edges: [], | ||||||
|  |       viewport: {}, | ||||||
|  |     }; | ||||||
|  |     if (!isEdit) { | ||||||
|  |       values.data = defaultData; | ||||||
|  |     } | ||||||
|     editStore.updateData(values); |     editStore.updateData(values); | ||||||
|   }; |   }; | ||||||
|   const onClose = () => { |   const onClose = () => { | ||||||
|     editStore.setShowEdit(false); |     editStore.setShowEdit(false); | ||||||
|     form.resetFields(); |     form.resetFields(); | ||||||
|  |     editStore.setFormData({}); | ||||||
|   }; |   }; | ||||||
|   const isEdit = editStore.formData.id; |   const isEdit = editStore.formData.id; | ||||||
|   return ( |   return ( | ||||||
| @@ -52,9 +64,15 @@ const FormModal = () => { | |||||||
|         <Form.Item name='title' label='title'> |         <Form.Item name='title' label='title'> | ||||||
|           <Input /> |           <Input /> | ||||||
|         </Form.Item> |         </Form.Item> | ||||||
|         {/* <Form.Item name='code' label='code'> |         <Form.Item name='description' label='description'> | ||||||
|           <TextArea value={containerStore.formData.code} /> |           <Input.TextArea lang={'markdown'} /> | ||||||
|         </Form.Item> */} |         </Form.Item> | ||||||
|  |         <Form.Item name='type' label='type' noStyle hidden> | ||||||
|  |           <Input /> | ||||||
|  |         </Form.Item> | ||||||
|  |         <Form.Item name='data' label='data' noStyle hidden> | ||||||
|  |           <Input /> | ||||||
|  |         </Form.Item> | ||||||
|         <Form.Item label=' ' colon={false}> |         <Form.Item label=' ' colon={false}> | ||||||
|           <Button type='primary' htmlType='submit'> |           <Button type='primary' htmlType='submit'> | ||||||
|             Submit |             Submit | ||||||
| @@ -86,99 +104,80 @@ export const List = () => { | |||||||
|     editStore.getList(); |     editStore.getList(); | ||||||
|   }, []); |   }, []); | ||||||
|  |  | ||||||
|   const columns = [ |  | ||||||
|     { |  | ||||||
|       title: 'ID', |  | ||||||
|       dataIndex: 'id', |  | ||||||
|       render: (text: string) => { |  | ||||||
|   return ( |   return ( | ||||||
|           <div |     <div className='w-full h-full flex'> | ||||||
|             className='w-40 truncate cursor-pointer' |       <div className='p-2 bg-white rounded-r-lg'> | ||||||
|             title={text} |  | ||||||
|             onClick={() => { |  | ||||||
|               copy(text); |  | ||||||
|               message.success('copy success'); |  | ||||||
|             }}> |  | ||||||
|             {text} |  | ||||||
|           </div> |  | ||||||
|         ); |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|     { |  | ||||||
|       title: 'Title', |  | ||||||
|       dataIndex: 'title', |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     { |  | ||||||
|       title: 'Operation', |  | ||||||
|       dataIndex: 'operation', |  | ||||||
|       render: (text: string, record: any) => { |  | ||||||
|         return ( |  | ||||||
|           <div className='flex gap-2'> |  | ||||||
|         <Button |         <Button | ||||||
|  |           className='w-10 ' | ||||||
|           type='primary' |           type='primary' | ||||||
|               onClick={() => { |           icon={<PlusOutlined />} | ||||||
|                 editStore.setFormData(record); |  | ||||||
|                 editStore.setShowEdit(true); |  | ||||||
|               }}> |  | ||||||
|               Edit |  | ||||||
|             </Button> |  | ||||||
|             <Button |  | ||||||
|               onClick={() => { |  | ||||||
|                 navicate('/panel/flow/' + record.id); |  | ||||||
|               }}> |  | ||||||
|               Flow |  | ||||||
|             </Button> |  | ||||||
|             <Button |  | ||||||
|               onClick={() => { |  | ||||||
|                 navicate('/panel/deck/' + record.id); |  | ||||||
|               }}> |  | ||||||
|               Deck |  | ||||||
|             </Button> |  | ||||||
|             <Button |  | ||||||
|               onClick={() => { |  | ||||||
|                 toCodeEditor.toPagePage(record); |  | ||||||
|               }}> |  | ||||||
|               Source Data Editor |  | ||||||
|             </Button> |  | ||||||
|             <Button |  | ||||||
|               danger |  | ||||||
|               onClick={() => { |  | ||||||
|                 editStore.deleteData(record.id); |  | ||||||
|               }}> |  | ||||||
|               Delete |  | ||||||
|             </Button> |  | ||||||
|           </div> |  | ||||||
|         ); |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|   ]; |  | ||||||
|   return ( |  | ||||||
|     <div className='w-full h-full flex flex-col'> |  | ||||||
|       <div className='mb-2 w-full p-2 bg-white rounded-lg'> |  | ||||||
|         <Button |  | ||||||
|           className='w-20 ' |  | ||||||
|           type='primary' |  | ||||||
|           onClick={() => { |           onClick={() => { | ||||||
|             editStore.setFormData({}); |             editStore.setFormData({}); | ||||||
|             editStore.setShowEdit(true); |             editStore.setShowEdit(true); | ||||||
|           }}> |           }}></Button> | ||||||
|           Add |  | ||||||
|         </Button> |  | ||||||
|       </div> |       </div> | ||||||
|       <div className='flex-grow overflow-scroll'> |       <div className='flex-grow overflow-scroll scrollbar mt-4'> | ||||||
|         <Table |         <div className=''> | ||||||
|           pagination={false} |           <div className=' flex flex-wrap gap-10 justify-center h-full overflow-auto scrollbar'> | ||||||
|           scroll={{ |             {editStore.list.length > 0 && | ||||||
|             y: 600, |               editStore.list.map((item, index) => { | ||||||
|  |                 return ( | ||||||
|  |                   <div className='card w-[300px]' key={index}> | ||||||
|  |                     <div className='card-title'>{item.title}</div> | ||||||
|  |                     <div className='card-subtitle'> {item.description}</div> | ||||||
|  |                     <div className='mt-4'> | ||||||
|  |                       <Button.Group> | ||||||
|  |                         <Tooltip title='Edit'> | ||||||
|  |                           <Button | ||||||
|  |                             onClick={() => { | ||||||
|  |                               editStore.setFormData(item); | ||||||
|  |                               editStore.setShowEdit(true); | ||||||
|                             }} |                             }} | ||||||
|           loading={editStore.loading} |                             icon={<EditOutlined />} | ||||||
|           dataSource={editStore.list} |  | ||||||
|           rowKey='id' |  | ||||||
|           columns={columns} |  | ||||||
|                           /> |                           /> | ||||||
|  |                         </Tooltip> | ||||||
|  |                         <Tooltip title='to flow'> | ||||||
|  |                           <Button | ||||||
|  |                             onClick={() => { | ||||||
|  |                               navicate('/panel/flow/' + item.id); | ||||||
|  |                             }} | ||||||
|  |                             icon={<ForkOutlined />} | ||||||
|  |                           /> | ||||||
|  |                         </Tooltip> | ||||||
|  |                         <Tooltip title='to deck'> | ||||||
|  |                           <Button | ||||||
|  |                             onClick={() => { | ||||||
|  |                               navicate('/panel/deck/' + item.id); | ||||||
|  |                             }} | ||||||
|  |                             icon={<GoldOutlined />} | ||||||
|  |                           /> | ||||||
|  |                         </Tooltip> | ||||||
|  |                         <Tooltip title='to code editor'> | ||||||
|  |                           <Button | ||||||
|  |                             onClick={() => { | ||||||
|  |                               toCodeEditor.toPagePage(item); | ||||||
|  |                             }} | ||||||
|  |                             icon={<ToolOutlined />} | ||||||
|  |                           /> | ||||||
|  |                         </Tooltip> | ||||||
|  |                         <Tooltip title='delete'> | ||||||
|  |                           <Button | ||||||
|  |                             onClick={() => { | ||||||
|  |                               editStore.deleteData(item.id); | ||||||
|  |                             }} | ||||||
|  |                             icon={<DeleteOutlined />} | ||||||
|  |                           /> | ||||||
|  |                         </Tooltip> | ||||||
|  |                       </Button.Group> | ||||||
|  |                     </div> | ||||||
|  |                   </div> | ||||||
|  |                 ); | ||||||
|  |               })} | ||||||
|  |             <CardBlank className='w-[300px]' /> | ||||||
|  |             {editStore.list.length === 0 && <div className='text-center text-gray-500'>No data</div>} | ||||||
|  |           </div> | ||||||
|  |         </div> | ||||||
|       </div> |       </div> | ||||||
|       <div className='h-2'></div> |  | ||||||
|       <FormModal /> |       <FormModal /> | ||||||
|     </div> |     </div> | ||||||
|   ); |   ); | ||||||
|   | |||||||
| @@ -19,8 +19,13 @@ import { useShallow } from 'zustand/react/shallow'; | |||||||
| import { Container, useAddNode, ContainerMenusList, useMenuFlow } from '@abearxiong/flows'; | import { Container, useAddNode, ContainerMenusList, useMenuFlow } from '@abearxiong/flows'; | ||||||
| import { useMenuEmitter, ContainerMenusKeys } from '@abearxiong/flows'; | import { useMenuEmitter, ContainerMenusKeys } from '@abearxiong/flows'; | ||||||
| import { nanoid } from 'nanoid'; | import { nanoid } from 'nanoid'; | ||||||
| import { Button } from 'antd'; | import { Button, message, Tooltip } from 'antd'; | ||||||
| import { usePanelStore } from '../store'; | import { usePanelStore } from '../store'; | ||||||
|  | import { CompassOutlined, PlusOutlined, SaveOutlined } from '@ant-design/icons'; | ||||||
|  | import { generateId } from '@/utils/nanoid'; | ||||||
|  | import { useMessage } from '@/hooks'; | ||||||
|  | import { NodeProperties } from './properties/NodeProperties'; | ||||||
|  | import { emitter } from '@abearxiong/container'; | ||||||
| // router: Router | // router: Router | ||||||
| const nodeTypes = { | const nodeTypes = { | ||||||
|   container: Container, |   container: Container, | ||||||
| @@ -36,7 +41,6 @@ export const Flow = () => { | |||||||
| const ReactFlowApp = () => { | const ReactFlowApp = () => { | ||||||
|   const [nodes, setNodes, onNodesChange] = useNodesState([]); |   const [nodes, setNodes, onNodesChange] = useNodesState([]); | ||||||
|   const [edges, setEdges, onEdgesChange] = useEdgesState<any>([]); |   const [edges, setEdges, onEdgesChange] = useEdgesState<any>([]); | ||||||
|  |  | ||||||
|   const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), [setEdges]); |   const onConnect = useCallback((params) => setEdges((eds) => addEdge(params, eds)), [setEdges]); | ||||||
|   const panelStore = usePanelStore( |   const panelStore = usePanelStore( | ||||||
|     useShallow((state) => { |     useShallow((state) => { | ||||||
| @@ -49,15 +53,47 @@ const ReactFlowApp = () => { | |||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     if (panelStore.data?.id) { |     if (panelStore.data?.id) { | ||||||
|       const { data } = panelStore.data || {}; |       const { data } = panelStore.data || {}; | ||||||
|       const nodes = data.nodes || []; |       console.log('data', panelStore.data); | ||||||
|       const edges = data.edges || []; |       const nodes = [...data.nodes]; | ||||||
|       setNodes(nodes); |       const edges = [...data.edges]; | ||||||
|  |       console.log('nodes', nodes); | ||||||
|  |       if (nodes.length === 0) { | ||||||
|  |         nodes.push({ | ||||||
|  |           id: panelStore.data.id, | ||||||
|  |           data: { | ||||||
|  |             label: '容器', | ||||||
|  |             root: true, | ||||||
|  |           }, | ||||||
|  |           position: { | ||||||
|  |             x: 100, | ||||||
|  |             y: 100, | ||||||
|  |           }, | ||||||
|  |           type: 'container', | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |       console.log('nodes', nodes); | ||||||
|  |       setNodes(nodes as any); | ||||||
|       setEdges(edges); |       setEdges(edges); | ||||||
|     } else { |     } else { | ||||||
|       setNodes([]); |       setNodes([]); | ||||||
|       setEdges([]); |       setEdges([]); | ||||||
|     } |     } | ||||||
|   }, [panelStore.data]); |   }, [panelStore.data]); | ||||||
|  |   useEffect(() => { | ||||||
|  |     emitter.on('setNodes', _setNodes); | ||||||
|  |     emitter.on('setEdges', _setEdges); | ||||||
|  |     return () => { | ||||||
|  |       emitter.off('setNodes', _setNodes); | ||||||
|  |       emitter.off('setEdges', _setEdges); | ||||||
|  |     }; | ||||||
|  |   }, []); | ||||||
|  |   const _setNodes = (data: any) => { | ||||||
|  |     setNodes(data); | ||||||
|  |   }; | ||||||
|  |   const _setEdges = (data: any) => { | ||||||
|  |     setEdges(data); | ||||||
|  |   }; | ||||||
|  |  | ||||||
|   const { menuCom, onContextMenu, onClose } = useMenuFlow( |   const { menuCom, onContextMenu, onClose } = useMenuFlow( | ||||||
|     (item, cacheData) => { |     (item, cacheData) => { | ||||||
|       emit({ |       emit({ | ||||||
| @@ -72,11 +108,37 @@ const ReactFlowApp = () => { | |||||||
|  |  | ||||||
|   const { emit } = useMenuEmitter<ContainerMenusKeys>({ |   const { emit } = useMenuEmitter<ContainerMenusKeys>({ | ||||||
|     preview: ({ menu, data }) => { |     preview: ({ menu, data }) => { | ||||||
|       console.log('preview', data); |       console.log('preview', data, message); | ||||||
|       if (data?.data?.cid) { |       if (data?.data?.cid) { | ||||||
|         window.open(`/container/preview/${data.data.cid}`, '_blank'); |         window.open(`/container/preview/${data.data.cid}`, '_blank'); | ||||||
|  |       } else { | ||||||
|  |         message.error('未绑定容器'); | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     code: ({ menu, data }) => { | ||||||
|  |       // console.log('edit', data); | ||||||
|  |       const nodeData = data?.data; | ||||||
|  |       if (!nodeData.cid) { | ||||||
|  |         message.error('请先绑定容器'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       message.error('developing'); | ||||||
|  |     }, | ||||||
|  |     copy: ({ menu, data }) => { | ||||||
|  |       message.error('developing'); | ||||||
|  |     }, | ||||||
|  |     delete: ({ menu, data }) => { | ||||||
|  |       console.log('delete', data); | ||||||
|  |       const nodeData = data?.data; | ||||||
|  |       if (nodeData.root) { | ||||||
|  |         message.error('root node can not be deleted'); | ||||||
|  |         return; | ||||||
|  |       } | ||||||
|  |       setNodes((nodes) => nodes.filter((item: any) => item.id !== data.id)); | ||||||
|  |     }, | ||||||
|  |     internalData: ({ menu, data }) => { | ||||||
|  |       message.error('developing'); | ||||||
|  |     }, | ||||||
|   }); |   }); | ||||||
|   const { onNeedAdd, onAdd, onMouseMove, adding } = useAddNode(); |   const { onNeedAdd, onAdd, onMouseMove, adding } = useAddNode(); | ||||||
|   const onSave = useCallback(() => { |   const onSave = useCallback(() => { | ||||||
| @@ -124,26 +186,41 @@ const ReactFlowApp = () => { | |||||||
|       }}> |       }}> | ||||||
|       <MiniMap /> |       <MiniMap /> | ||||||
|       <Controls /> |       <Controls /> | ||||||
|       {/* <Background gap={[14, 14]} size={2} color='#E4E5E7' /> */} |  | ||||||
|       <Background color='#000' /> |       <Background color='#000' /> | ||||||
|       <Panel>{menuCom}</Panel> |       <Panel>{menuCom}</Panel> | ||||||
|       <Panel> |       <Panel> | ||||||
|         <div className='flex gap-2'> |         <div className='flex gap-2'> | ||||||
|  |           <Button.Group> | ||||||
|  |             <Tooltip title='添加节点'> | ||||||
|               <Button |               <Button | ||||||
|             type='primary' |                 icon={<PlusOutlined />} | ||||||
|                 onClick={(e) => { |                 onClick={(e) => { | ||||||
|               onNeedAdd({ id: nanoid(6), data: { label: '容器' }, type: 'container' }, e); |                   onNeedAdd({ id: 'flow' + generateId(), data: { label: '容器' }, type: 'container' }, e); | ||||||
|             }}> |                 }}></Button> | ||||||
|             测试添加按钮 |             </Tooltip> | ||||||
|           </Button> |             <Tooltip title='save'> | ||||||
|               <Button |               <Button | ||||||
|  |                 icon={<SaveOutlined />} | ||||||
|                 onClick={() => { |                 onClick={() => { | ||||||
|                   onSave(); |                   onSave(); | ||||||
|             }}> |                 }}></Button> | ||||||
|             保存 |             </Tooltip> | ||||||
|           </Button> |             <Tooltip title='preview'> | ||||||
|  |               <Button | ||||||
|  |                 icon={<CompassOutlined />} | ||||||
|  |                 onClick={() => { | ||||||
|  |                   const id = panelStore.data?.id; | ||||||
|  |                   if (!id) { | ||||||
|  |                     message.error('ID is required'); | ||||||
|  |                     return; | ||||||
|  |                   } | ||||||
|  |                   window.open(`/panel/deck/${id}`, '_blank'); | ||||||
|  |                 }}></Button> | ||||||
|  |             </Tooltip> | ||||||
|  |           </Button.Group> | ||||||
|         </div> |         </div> | ||||||
|       </Panel> |       </Panel> | ||||||
|  |       <NodeProperties /> | ||||||
|     </ReactFlow> |     </ReactFlow> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								src/pages/panel/flow/message.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								src/pages/panel/flow/message.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | |||||||
|  | import { EventEmitter } from 'eventemitter3'; | ||||||
|  | export const emitter = new EventEmitter(); | ||||||
							
								
								
									
										103
									
								
								src/pages/panel/flow/properties/NodeProperties.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										103
									
								
								src/pages/panel/flow/properties/NodeProperties.tsx
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,103 @@ | |||||||
|  | import { Panel, useReactFlow, useStore, useStoreApi } from '@xyflow/react'; | ||||||
|  | import clsx from 'clsx'; | ||||||
|  | import { useEffect, useState } from 'react'; | ||||||
|  | import { debounce } from 'lodash-es'; | ||||||
|  | import { Button, Form, Input, message } from 'antd'; | ||||||
|  | import { Select } from '@/pages/container/module/Select'; | ||||||
|  | import { SaveOutlined } from '@ant-design/icons'; | ||||||
|  | import { emitter } from '@abearxiong/container'; | ||||||
|  | import { usePanelStore } from '../../store'; | ||||||
|  | export const NodeProperties = () => { | ||||||
|  |   const reactflow = useReactFlow(); | ||||||
|  |   const [open, setOpen] = useState(false); | ||||||
|  |   const [form] = Form.useForm(); | ||||||
|  |   const panelStore = usePanelStore((state) => { | ||||||
|  |     return { | ||||||
|  |       updateNodeData: state.updateNodeData, | ||||||
|  |     }; | ||||||
|  |   }); | ||||||
|  |   const store = useStore((state) => { | ||||||
|  |     const setNode = (node: any) => { | ||||||
|  |       const newNodes = state.nodes.map((item) => { | ||||||
|  |         if (item.id === node.id) { | ||||||
|  |           // return { ...item, ...node }; | ||||||
|  |           return { ...node }; | ||||||
|  |         } | ||||||
|  |         return item; | ||||||
|  |       }); | ||||||
|  |       // console.log('newNodes', newNodes); | ||||||
|  |       // state.setNodes(newNodes); // 会丢失数据,因为最终没有调用context的setNodes方法 | ||||||
|  |       emitter.emit('setNodes', newNodes); | ||||||
|  |     }; | ||||||
|  |     return { | ||||||
|  |       nodesFocusable: state.nodesFocusable, | ||||||
|  |       selected: state.nodes.filter((node) => node.selected), | ||||||
|  |       setNode: setNode, | ||||||
|  |     }; | ||||||
|  |   }); | ||||||
|  |   const [nodeData] = store.selected as any[]; | ||||||
|  |   useEffect(() => { | ||||||
|  |     if (!nodeData) return; | ||||||
|  |     // console.log('nodeData', nodeData); | ||||||
|  |     const { data } = nodeData || {}; | ||||||
|  |     form.setFieldsValue({ | ||||||
|  |       cid: data.cid, | ||||||
|  |       title: data.title, | ||||||
|  |     }); | ||||||
|  |   }, [nodeData]); | ||||||
|  |   const onSave = () => { | ||||||
|  |     const values = form.getFieldsValue(); | ||||||
|  |     // console.log('values', values); | ||||||
|  |     const { cid, title } = values; | ||||||
|  |     if (!cid) { | ||||||
|  |       message.error('请选择容器'); | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     const { data, id, position, type } = nodeData || {}; | ||||||
|  |     // console.log('data', data, cid); | ||||||
|  |     const newNodeData = { | ||||||
|  |       id, | ||||||
|  |       position, | ||||||
|  |       selected: true, | ||||||
|  |       type, | ||||||
|  |       data: { | ||||||
|  |         ...data, | ||||||
|  |         cid, | ||||||
|  |         title, | ||||||
|  |       }, | ||||||
|  |     }; | ||||||
|  |     // console.log('newNodeData', newNodeData, nodeData); | ||||||
|  |     store.setNode(newNodeData); | ||||||
|  |     panelStore.updateNodeData(newNodeData); | ||||||
|  |   }; | ||||||
|  |   return ( | ||||||
|  |     <Panel title='节点属性' position='bottom-center' className='w-full'> | ||||||
|  |       <div className={clsx('w-full h-[200px]  card', store.selected.length > 0 ? '' : 'hidden')}> | ||||||
|  |         <div className='card-title'> | ||||||
|  |           {nodeData?.data?.label} | ||||||
|  |           <Button.Group className='ml-2'> | ||||||
|  |             <Button onClick={onSave} icon={<SaveOutlined />}></Button> | ||||||
|  |           </Button.Group> | ||||||
|  |         </div> | ||||||
|  |         <div className='p-4'> | ||||||
|  |           <Form form={form}> | ||||||
|  |             <Form.Item label='名称' name='cid'> | ||||||
|  |               <Select | ||||||
|  |                 onChange={(e, options) => { | ||||||
|  |                   if (Array.isArray(options)) { | ||||||
|  |                   } else { | ||||||
|  |                     const title = options?.label; | ||||||
|  |                     form.setFieldsValue({ title }); | ||||||
|  |                   } | ||||||
|  |                 }} | ||||||
|  |               /> | ||||||
|  |             </Form.Item> | ||||||
|  |             <Form.Item label='标题' name='title'> | ||||||
|  |               <Input /> | ||||||
|  |             </Form.Item> | ||||||
|  |           </Form> | ||||||
|  |         </div> | ||||||
|  |       </div> | ||||||
|  |     </Panel> | ||||||
|  |   ); | ||||||
|  | }; | ||||||
| @@ -3,18 +3,12 @@ import { Outlet } from 'react-router'; | |||||||
| export const Main = () => { | export const Main = () => { | ||||||
|   return ( |   return ( | ||||||
|     <div className='flex w-full h-full flex-col bg-gray-200'> |     <div className='flex w-full h-full flex-col bg-gray-200'> | ||||||
|       <div className='h-12 bg-white p-2 mb-2'>Deck And Flow</div> |       <div className='layout-menu'>Deck And Flow</div> | ||||||
|       <div |       <div className='flex-grow w-full'> | ||||||
|         className='flex' |         <div className='w-full h-full overflow-hidden'> | ||||||
|         style={{ |  | ||||||
|           height: 'calc(100vh - 4rem)', |  | ||||||
|         }}> |  | ||||||
|         <div className='flex-grow overflow-hidden mx-2'> |  | ||||||
|           <div className='w-full h-full rounded-lg'> |  | ||||||
|           <Outlet /> |           <Outlet /> | ||||||
|         </div> |         </div> | ||||||
|       </div> |       </div> | ||||||
|     </div> |     </div> | ||||||
|     </div> |  | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ type PanelStore = { | |||||||
|   setData: (data: any) => void; |   setData: (data: any) => void; | ||||||
|   getPanel: (id?: string) => Promise<void>; |   getPanel: (id?: string) => Promise<void>; | ||||||
|   saveNodesEdges: (data: { nodes?: any[]; edges?: any[]; viewport?: any }) => Promise<void>; |   saveNodesEdges: (data: { nodes?: any[]; edges?: any[]; viewport?: any }) => Promise<void>; | ||||||
|  |   updateNodeData: (data: any) => Promise<void>; | ||||||
| }; | }; | ||||||
| export const usePanelStore = create<PanelStore>((set, get) => { | export const usePanelStore = create<PanelStore>((set, get) => { | ||||||
|   return { |   return { | ||||||
| @@ -78,5 +79,23 @@ export const usePanelStore = create<PanelStore>((set, get) => { | |||||||
|         message.error(res.msg || 'Request failed'); |         message.error(res.msg || 'Request failed'); | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     updateNodeData: async (data) => { | ||||||
|  |       // const { getList } = get(); | ||||||
|  |       const { id, data: panelData } = get(); | ||||||
|  |       const res = await query.post({ | ||||||
|  |         path: 'page', | ||||||
|  |         key: 'updateNode', | ||||||
|  |         data: { | ||||||
|  |           id: id, | ||||||
|  |           nodeData: data, | ||||||
|  |         }, | ||||||
|  |       }); | ||||||
|  |       if (res.code === 200) { | ||||||
|  |         message.success('Success'); | ||||||
|  |         // getList(); | ||||||
|  |       } else { | ||||||
|  |         message.error(res.msg || 'Request failed'); | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|   }; |   }; | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -109,6 +109,7 @@ export const List = () => { | |||||||
|       return { |       return { | ||||||
|         open: state.open, |         open: state.open, | ||||||
|         setOpen: state.setOpen, |         setOpen: state.setOpen, | ||||||
|  |         setData: state.setData, | ||||||
|         key: state.key, |         key: state.key, | ||||||
|         setKey: state.setKey, |         setKey: state.setKey, | ||||||
|         sendMsg: state.sendMsg, |         sendMsg: state.sendMsg, | ||||||
| @@ -243,8 +244,9 @@ export const List = () => { | |||||||
|                             icon={<CaretRightOutlined />} |                             icon={<CaretRightOutlined />} | ||||||
|                             onClick={() => { |                             onClick={() => { | ||||||
|                               // navicate(`/prompt/${item.id}`); |                               // navicate(`/prompt/${item.id}`); | ||||||
|                               promptStore.setFormData(item); |                               // promptStore.setFormData(item); | ||||||
|                               // promptStore.runAi(); |                               // promptStore.runAi(); | ||||||
|  |                               aiStore.setData(item); | ||||||
|                               aiStore.setOpen(true); |                               aiStore.setOpen(true); | ||||||
|                             }} |                             }} | ||||||
|                           /> |                           /> | ||||||
|   | |||||||
| @@ -4,6 +4,8 @@ import { message } from 'antd'; | |||||||
| type PromptStore = { | type PromptStore = { | ||||||
|   showEdit: boolean; |   showEdit: boolean; | ||||||
|   setShowEdit: (showEdit: boolean) => void; |   setShowEdit: (showEdit: boolean) => void; | ||||||
|  |   data: any; | ||||||
|  |   setData: (data: any) => void; | ||||||
|   formData: any; |   formData: any; | ||||||
|   setFormData: (formData: any) => void; |   setFormData: (formData: any) => void; | ||||||
|   loading: boolean; |   loading: boolean; | ||||||
| @@ -19,7 +21,13 @@ export const usePromptStore = create<PromptStore>((set, get) => { | |||||||
|     showEdit: false, |     showEdit: false, | ||||||
|     setShowEdit: (showEdit) => set({ showEdit }), |     setShowEdit: (showEdit) => set({ showEdit }), | ||||||
|     formData: {}, |     formData: {}, | ||||||
|     setFormData: (formData) => set({ formData }), |     setFormData: (formData) => { | ||||||
|  |       set({ formData }); | ||||||
|  |     }, | ||||||
|  |     data: {}, | ||||||
|  |     setData: (data) => { | ||||||
|  |       set({ data }); | ||||||
|  |     }, | ||||||
|     loading: false, |     loading: false, | ||||||
|     setLoading: (loading) => set({ loading }), |     setLoading: (loading) => set({ loading }), | ||||||
|     list: [], |     list: [], | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								src/utils/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/utils/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | export * from './nanoid'; | ||||||
							
								
								
									
										9
									
								
								src/utils/is-null.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/utils/is-null.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | |||||||
|  | export const isObjectNull = (value: any) => { | ||||||
|  |   if (value === null || value === undefined) { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   if (JSON.stringify(value) === '{}') { | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  |   return false; | ||||||
|  | }; | ||||||
							
								
								
									
										5
									
								
								src/utils/nanoid.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/utils/nanoid.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | |||||||
|  | import { customAlphabet, nanoid } from 'nanoid'; | ||||||
|  | const number = '0123456789'; | ||||||
|  | const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; | ||||||
|  |  | ||||||
|  | export const generateId = customAlphabet(number + alphabet, 6); | ||||||
| @@ -1,10 +1,26 @@ | |||||||
| /** @type {import('tailwindcss').Config} */ | /** @type {import('tailwindcss').Config} */ | ||||||
| export default { | export default { | ||||||
|   darkMode: ['class'], |   darkMode: ['class'], | ||||||
|   content: ['./src/**/*.{ts,tsx}', './node_modules/@abearxiong/flows/**/*.{ts,tsx}'], |   mod: 'jit', | ||||||
|   plugins: [require('@tailwindcss/aspect-ratio'), require('@tailwindcss/typography')], |   content: ['./src/**/*.{ts,tsx}', './node_modules/@abearxiong/flows/**/*.{ts,tsx}', './src/**/*.css'], | ||||||
|  |   plugins: [ | ||||||
|  |     require('@tailwindcss/aspect-ratio'), // | ||||||
|  |     require('@tailwindcss/typography'), | ||||||
|  |     require('@tailwindcss/line-clamp'), | ||||||
|  |     require('tailwindcss-animate'), | ||||||
|  |     require('./plugins/flex'), | ||||||
|  |   ], | ||||||
|  |   safelist: ['layout-menu', 'bg-custom-blue'], | ||||||
|   theme: { |   theme: { | ||||||
|     extend: {}, |     extend: { | ||||||
|  |       fontFamily: { | ||||||
|  |         mon: ['Montserrat', 'sans-serif'], // 定义自定义字体族 | ||||||
|  |         rob: ['Roboto', 'sans-serif'], | ||||||
|  |         int: ['Inter', 'sans-serif'], | ||||||
|  |         orb: ['Orbitron', 'sans-serif'], | ||||||
|  |         din: ['DIN', 'sans-serif'], | ||||||
|  |       }, | ||||||
|  |     }, | ||||||
|     screen: { |     screen: { | ||||||
|       sm: '640px', |       sm: '640px', | ||||||
|       // => @media (min-width: 640px) { ... } |       // => @media (min-width: 640px) { ... } | ||||||
| @@ -26,5 +42,4 @@ export default { | |||||||
|       // => @media (min-width: 2560) { ... } |       // => @media (min-width: 2560) { ... } | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   plugins: [require('tailwindcss-animate')], |  | ||||||
| }; | }; | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								theme
									
									
									
									
									
								
							
							
								
								
								
								
								
							
						
						
									
										2
									
								
								theme
									
									
									
									
									
								
							 Submodule theme updated: a04e057119...cae4f74f6c
									
								
							| @@ -11,7 +11,11 @@ export default defineConfig({ | |||||||
|  |  | ||||||
|   css: { |   css: { | ||||||
|     postcss: { |     postcss: { | ||||||
|       plugins: [nesting, tailwindcss, autoprefixer], |       plugins: [ | ||||||
|  |         nesting, // 作用是可以使用@import导入css文件 | ||||||
|  |         tailwindcss, | ||||||
|  |         autoprefixer, | ||||||
|  |       ], | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
|   resolve: { |   resolve: { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user