From dd6dfd1b59aeadc182a0d3c209380bd13d25141f Mon Sep 17 00:00:00 2001 From: WenC <450292408@qq.com> Date: Fri, 19 Apr 2024 18:20:51 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AF=B9=E6=8E=A5AI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 6 +- src/components/ai/AiHelper.tsx | 211 +++++++++++++++++++++++++++++++++ src/context/GlobalContext.ts | 3 + src/route/proj/ProjEdit.tsx | 47 +++++++- src/route/proj/ProjNew.tsx | 12 -- src/util/AjaxUtils.ts | 1 + 6 files changed, 264 insertions(+), 16 deletions(-) create mode 100644 src/components/ai/AiHelper.tsx diff --git a/src/App.tsx b/src/App.tsx index 34f5b5e..8c2f450 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,14 +8,15 @@ import { GlobalDataActionType, GlobalDispatchContext, } from "./context/GlobalContext.ts"; -import {Reducer, useReducer} from "react"; +import React, {Reducer, useReducer} from "react"; const App: React.FC = () => { const globalDataReducer = (state: GlobalData, action: GlobalDataAction) => { if (action.type == GlobalDataActionType.REFRESH_SELF) { - if(action.user) { + if (action.user) { state.user.balance = action.user.balance; + state.user.userId = action.user.userId; state.user.nickname = action.user.nickname; state.user.username = action.user.username; state.user.hasUserInfo = action.user.hasUserInfo; @@ -28,6 +29,7 @@ const App: React.FC = () => { const [globalData, dispatch] = useReducer>(globalDataReducer, { user: { balance: '0', + userId: '', username: '', nickname: '', hasUserInfo: false diff --git a/src/components/ai/AiHelper.tsx b/src/components/ai/AiHelper.tsx new file mode 100644 index 0000000..73058fe --- /dev/null +++ b/src/components/ai/AiHelper.tsx @@ -0,0 +1,211 @@ +import {useContext, useEffect, useRef, useState} from "react"; +import {GlobalContext} from "../../context/GlobalContext.ts"; +import {put, WebSocketBaseUrl} from "../../util/AjaxUtils.ts"; +import {Button, Divider, Empty, Space, Spin} from "antd"; +import {CheckOutlined, ReloadOutlined} from "@ant-design/icons"; +import useMessage from "antd/es/message/useMessage"; + +type PropsType = { + projId: string; + projIntroduction?: string; + projDesc?: string; +} + +export default function AiHelper(props: PropsType) { + const globalContext = useContext(GlobalContext); + const pingTimeout = useRef(-1); + const ws = useRef(null); + const [messageApi, messageApiHolder] = useMessage(); + const [projIntroduction, setProjIntroduction] = useState(props.projIntroduction ? props.projIntroduction : ''); + const [newProjIntroduction, setNewProjIntroduction] = useState(''); + const [isProjIntroductionLoading, setIsProjIntroductionLoading] = useState(false); + const [projDesc, setProjDesc] = useState(props.projDesc ? props.projDesc : ''); + const [newProjDesc, setNewProjDesc] = useState(''); + const [isProjDescLoading, setIsProjDescLoading] = useState(false); + + const ping = () => { + clearTimeout(pingTimeout.current); + pingTimeout.current = setTimeout(() => { + if (!ws.current) { + return; + } + ws.current.send("PING"); + ping(); + }, 3000); + } + + const websocket = () => { + ws.current = new WebSocket(`${WebSocketBaseUrl}/ws/ai/${globalContext.user.userId}`); + ws.current.onopen = (event) => { + console.log('打开', event); + ping(); + } + ws.current.onmessage = (event) => { + if (event.data == 'PONE') { + return; + } + const data = JSON.parse(event.data); + if (data.type == 'REFRESH_PROJ_INTRODUCTION') { + if (data.projId != props.projId) { + return; + } + setIsProjIntroductionLoading(false); + setNewProjIntroduction(data.content); + return; + } + if (data.type == 'REFRESH_PROJ_DESC') { + if (data.projId != props.projId) { + return; + } + setIsProjDescLoading(false); + setNewProjDesc(data.content); + return; + } + } + ws.current.onerror = (event) => { + console.log('error', event); + } + ws.current.onclose = (event) => { + console.log('close', event); + websocket() + } + } + + const generateProjIntroduction = () => { + ws.current?.send(JSON.stringify({ + type: 'REFRESH_PROJ_INTRODUCTION', + projId: props.projId + })); + ping(); + setIsProjIntroductionLoading(true); + } + + const generateProjDesc = () => { + ws.current?.send(JSON.stringify({ + type: 'REFRESH_PROJ_DESC', + projId: props.projId + })); + ping(); + setIsProjDescLoading(true); + } + + /** + * 保存简介 + */ + const updateProjIntroduction = () => { + put({ + messageApi, + url: `/api/proj/update-introduction/${props.projId}`, + body: { + content: newProjIntroduction + }, + onBefore() { + setIsProjIntroductionLoading(true); + }, + onSuccess() { + messageApi.success('保存成功'); + setProjIntroduction(newProjIntroduction); + setNewProjIntroduction(''); + }, + onFinally() { + setIsProjIntroductionLoading(false) + } + }) + } + + /** + * 保存详情 + */ + const updateProjDesc = () => { + put({ + messageApi, + url: `/api/proj/update-desc/${props.projId}`, + body: { + content: newProjDesc + }, + onBefore() { + setIsProjDescLoading(true); + }, + onSuccess() { + messageApi.success('保存成功').then(); + setProjDesc(newProjDesc); + setNewProjDesc(''); + }, + onFinally() { + setIsProjDescLoading(false) + } + }) + } + + useEffect(() => { + if (!props.projId) { + return; + } + websocket(); + }, [globalContext.user.userId, props.projId]); + + return ( + <> + {messageApiHolder} +
项目简介
+ +
+ {newProjIntroduction ? 原简介 : <>} + {projIntroduction ?
{projIntroduction}
: } + { + newProjIntroduction ? ( + <> + 新简介 +
{newProjIntroduction}
+ + ) : <> + } +
+
+ { + newProjIntroduction ? ( + + + + + ) : + } +
+
+ +
项目详情
+ +
+ {newProjDesc ? 原详情 : <>} + {projDesc ?
{projDesc}
: } + { + newProjDesc ? ( + <> + 新详情 +
{newProjDesc}
+ + ) : <> + } +
+
+
+ { + newProjDesc ? ( + + + + + ) : + } +
+
+
+ +) +} \ No newline at end of file diff --git a/src/context/GlobalContext.ts b/src/context/GlobalContext.ts index db834cc..3cd82b0 100644 --- a/src/context/GlobalContext.ts +++ b/src/context/GlobalContext.ts @@ -9,6 +9,7 @@ export enum GlobalDataActionType { export interface User { balance: string; nickname: string; + userId: string; username: string; hasUserInfo: boolean; } @@ -24,6 +25,7 @@ export function reloadUser(messageApi: MessageInstance, globalDispatchContext: D user: { balance: (Math.floor(data.accountMoney) / 100).toFixed(2), nickname: data.nickname, + userId: data.userId, username: data.username, hasUserInfo: data.hasUserInfo, } @@ -47,6 +49,7 @@ export const GlobalContext = createContext({ user: { balance: '0', nickname: '', + userId: '', username: '', hasUserInfo: false } diff --git a/src/route/proj/ProjEdit.tsx b/src/route/proj/ProjEdit.tsx index b3dae78..a3d5ae2 100644 --- a/src/route/proj/ProjEdit.tsx +++ b/src/route/proj/ProjEdit.tsx @@ -1,6 +1,6 @@ import './proj-edit.css'; import {Link, useNavigate, useParams} from "react-router-dom"; -import {Breadcrumb, Button, message, Modal} from "antd"; +import {Breadcrumb, Button, FloatButton, message, Modal} from "antd"; import StepProjEdit from "../../components/step/StepProjEdit.tsx"; import CardProjEdit from "../../components/card/CardProjEdit.tsx"; import {Process} from "../../interfaces/step/IStepProj.ts"; @@ -13,6 +13,13 @@ import {Axios, get, post} from "../../util/AjaxUtils.ts"; import {EditStepEnum, IProjEdit} from "../../interfaces/card/ICardProj.ts"; import {MAX_MOD_SIZE} from "./edit/ProjConfigModList.tsx"; import {GenerateStatus} from "../../interfaces/proj/IProj.ts"; +import AiHelper from "../../components/ai/AiHelper.tsx"; + +type AiHelperType = { + projId: string; + projIntroduction: string; + projDesc: string; +} export default function ProjEdit() { const nav = useNavigate(); @@ -30,6 +37,13 @@ export default function ProjEdit() { const [previewUrl, setPreviewUrl] = useState(''); const [generateErrorModal, setGenerateErrorModal] = useState(false); const [generateErrorMsg, setGenerateErrorMsg] = useState(''); + const [aiHelperModalOpen, setAiHelperModalOpen] = useState(false); + const [aiHelper, setAiHelper] = useState({ + projId: '', + projIntroduction: '', + projDesc: '' + }); + const height = window.innerHeight - 240; @@ -196,6 +210,13 @@ export default function ProjEdit() { setPreviewUrl(data.previewUrl); setGenerateEmainingTime(data.generate.generateEmainingTime); setGenerateErrorMsg(data.generate.generateMsg); + setAiHelper({ + projId: data.projId, + projIntroduction: data.projIntroduction, + projDesc: data.projDesc + }) + setAiHelperModalOpen(!data.projIntroduction || !data.projDesc) + } }) } @@ -367,6 +388,13 @@ export default function ProjEdit() { nav(-1); }}>返回 + + {setAiHelperModalOpen(true)}} + description={AI} + >AI助手 + + {setGenerateErrorModal(false)}} + onCancel={() => { + setGenerateErrorModal(false) + }} >

{generateErrorMsg}

+ { + setAiHelperModalOpen(false); + }} + > + + ) } \ No newline at end of file diff --git a/src/route/proj/ProjNew.tsx b/src/route/proj/ProjNew.tsx index e34dd71..9595a9d 100644 --- a/src/route/proj/ProjNew.tsx +++ b/src/route/proj/ProjNew.tsx @@ -6,8 +6,6 @@ import {get, post} from "../../util/AjaxUtils.ts"; import {IProjCharge, ProjAdditionalType, ProjChargeType} from "../../interfaces/proj/IProj.ts"; import {GlobalDispatchContext, reloadUser} from "../../context/GlobalContext.ts"; -const {TextArea} = Input; - type ProjInfo = { projName: string; projIntroduction: string; @@ -104,21 +102,12 @@ export default function ProjNew() { autoComplete="off" > - label="系统标题" name="projName" rules={[{required: true, message: '请输入系统标题'}]} > - - label="简单描述" - name="projIntroduction" - rules={[{required: true, message: '请输入简单描述'}]} - > -