对接AI
This commit is contained in:
parent
94d3198c1a
commit
dd6dfd1b59
@ -8,14 +8,15 @@ import {
|
|||||||
GlobalDataActionType,
|
GlobalDataActionType,
|
||||||
GlobalDispatchContext,
|
GlobalDispatchContext,
|
||||||
} from "./context/GlobalContext.ts";
|
} from "./context/GlobalContext.ts";
|
||||||
import {Reducer, useReducer} from "react";
|
import React, {Reducer, useReducer} from "react";
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
|
|
||||||
const globalDataReducer = (state: GlobalData, action: GlobalDataAction) => {
|
const globalDataReducer = (state: GlobalData, action: GlobalDataAction) => {
|
||||||
if (action.type == GlobalDataActionType.REFRESH_SELF) {
|
if (action.type == GlobalDataActionType.REFRESH_SELF) {
|
||||||
if(action.user) {
|
if (action.user) {
|
||||||
state.user.balance = action.user.balance;
|
state.user.balance = action.user.balance;
|
||||||
|
state.user.userId = action.user.userId;
|
||||||
state.user.nickname = action.user.nickname;
|
state.user.nickname = action.user.nickname;
|
||||||
state.user.username = action.user.username;
|
state.user.username = action.user.username;
|
||||||
state.user.hasUserInfo = action.user.hasUserInfo;
|
state.user.hasUserInfo = action.user.hasUserInfo;
|
||||||
@ -28,6 +29,7 @@ const App: React.FC = () => {
|
|||||||
const [globalData, dispatch] = useReducer<Reducer<GlobalData, GlobalDataAction>>(globalDataReducer, {
|
const [globalData, dispatch] = useReducer<Reducer<GlobalData, GlobalDataAction>>(globalDataReducer, {
|
||||||
user: {
|
user: {
|
||||||
balance: '0',
|
balance: '0',
|
||||||
|
userId: '',
|
||||||
username: '',
|
username: '',
|
||||||
nickname: '',
|
nickname: '',
|
||||||
hasUserInfo: false
|
hasUserInfo: false
|
||||||
|
211
src/components/ai/AiHelper.tsx
Normal file
211
src/components/ai/AiHelper.tsx
Normal file
@ -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<WebSocket | null>(null);
|
||||||
|
const [messageApi, messageApiHolder] = useMessage();
|
||||||
|
const [projIntroduction, setProjIntroduction] = useState<string>(props.projIntroduction ? props.projIntroduction : '');
|
||||||
|
const [newProjIntroduction, setNewProjIntroduction] = useState<string>('');
|
||||||
|
const [isProjIntroductionLoading, setIsProjIntroductionLoading] = useState(false);
|
||||||
|
const [projDesc, setProjDesc] = useState<string>(props.projDesc ? props.projDesc : '');
|
||||||
|
const [newProjDesc, setNewProjDesc] = useState<string>('');
|
||||||
|
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<any>({
|
||||||
|
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<any>({
|
||||||
|
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}
|
||||||
|
<div style={{padding: '5px 0 0 0', fontWeight: 'bold'}}>项目简介</div>
|
||||||
|
<Spin tip="正在处理,请稍后..." size="small" spinning={isProjIntroductionLoading}>
|
||||||
|
<div style={{padding: '5px 0 0 0'}}>
|
||||||
|
{newProjIntroduction ? <Divider orientation="right" plain>原简介</Divider> : <></>}
|
||||||
|
{projIntroduction ? <div>{projIntroduction}</div> : <Empty description="暂无内容"/>}
|
||||||
|
{
|
||||||
|
newProjIntroduction ? (
|
||||||
|
<>
|
||||||
|
<Divider orientation="right" plain>新简介</Divider>
|
||||||
|
<div>{newProjIntroduction}</div>
|
||||||
|
</>
|
||||||
|
) : <></>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div style={{padding: '5px 0 0 0', textAlign: 'center'}}>
|
||||||
|
{
|
||||||
|
newProjIntroduction ? (
|
||||||
|
<Space>
|
||||||
|
<Button type="link" style={{cursor: 'pointer'}}
|
||||||
|
onClick={updateProjIntroduction}><CheckOutlined/> 保存结果</Button>
|
||||||
|
<Button type="link" style={{cursor: 'pointer'}}
|
||||||
|
onClick={generateProjIntroduction}><ReloadOutlined/> 重新生成</Button>
|
||||||
|
</Space>
|
||||||
|
) : <Button type="link" style={{cursor: 'pointer'}}
|
||||||
|
onClick={generateProjIntroduction}>AI生成</Button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
|
<Divider dashed/>
|
||||||
|
<div style={{padding: '5px 0 0 0', fontWeight: 'bold'}}>项目详情</div>
|
||||||
|
<Spin tip="正在处理,请稍后..." size="small" spinning={isProjDescLoading}>
|
||||||
|
<div style={{padding: '5px 0 0 0'}}>
|
||||||
|
{newProjDesc ? <Divider orientation="right" plain>原详情</Divider> : <></>}
|
||||||
|
{projDesc ? <div>{projDesc}</div> : <Empty description="暂无内容"/>}
|
||||||
|
{
|
||||||
|
newProjDesc ? (
|
||||||
|
<>
|
||||||
|
<Divider orientation="right" plain>新详情</Divider>
|
||||||
|
<div>{newProjDesc}</div>
|
||||||
|
</>
|
||||||
|
) : <></>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<div style={{padding: '5px 0 0 0', textAlign: 'center'}}>
|
||||||
|
<div style={{padding: '5px 0 0 0', textAlign: 'center'}}>
|
||||||
|
{
|
||||||
|
newProjDesc ? (
|
||||||
|
<Space>
|
||||||
|
<Button type="link" style={{cursor: 'pointer'}}
|
||||||
|
onClick={updateProjDesc}><CheckOutlined/> 保存结果</Button>
|
||||||
|
<Button type="link" style={{cursor: 'pointer'}}
|
||||||
|
onClick={generateProjDesc}><ReloadOutlined/> 重新生成</Button>
|
||||||
|
</Space>
|
||||||
|
) : <Button type="link" style={{cursor: 'pointer'}}
|
||||||
|
onClick={generateProjDesc}>AI生成</Button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Spin>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -9,6 +9,7 @@ export enum GlobalDataActionType {
|
|||||||
export interface User {
|
export interface User {
|
||||||
balance: string;
|
balance: string;
|
||||||
nickname: string;
|
nickname: string;
|
||||||
|
userId: string;
|
||||||
username: string;
|
username: string;
|
||||||
hasUserInfo: boolean;
|
hasUserInfo: boolean;
|
||||||
}
|
}
|
||||||
@ -24,6 +25,7 @@ export function reloadUser(messageApi: MessageInstance, globalDispatchContext: D
|
|||||||
user: {
|
user: {
|
||||||
balance: (Math.floor(data.accountMoney) / 100).toFixed(2),
|
balance: (Math.floor(data.accountMoney) / 100).toFixed(2),
|
||||||
nickname: data.nickname,
|
nickname: data.nickname,
|
||||||
|
userId: data.userId,
|
||||||
username: data.username,
|
username: data.username,
|
||||||
hasUserInfo: data.hasUserInfo,
|
hasUserInfo: data.hasUserInfo,
|
||||||
}
|
}
|
||||||
@ -47,6 +49,7 @@ export const GlobalContext = createContext<GlobalData>({
|
|||||||
user: {
|
user: {
|
||||||
balance: '0',
|
balance: '0',
|
||||||
nickname: '',
|
nickname: '',
|
||||||
|
userId: '',
|
||||||
username: '',
|
username: '',
|
||||||
hasUserInfo: false
|
hasUserInfo: false
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import './proj-edit.css';
|
import './proj-edit.css';
|
||||||
import {Link, useNavigate, useParams} from "react-router-dom";
|
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 StepProjEdit from "../../components/step/StepProjEdit.tsx";
|
||||||
import CardProjEdit from "../../components/card/CardProjEdit.tsx";
|
import CardProjEdit from "../../components/card/CardProjEdit.tsx";
|
||||||
import {Process} from "../../interfaces/step/IStepProj.ts";
|
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 {EditStepEnum, IProjEdit} from "../../interfaces/card/ICardProj.ts";
|
||||||
import {MAX_MOD_SIZE} from "./edit/ProjConfigModList.tsx";
|
import {MAX_MOD_SIZE} from "./edit/ProjConfigModList.tsx";
|
||||||
import {GenerateStatus} from "../../interfaces/proj/IProj.ts";
|
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() {
|
export default function ProjEdit() {
|
||||||
const nav = useNavigate();
|
const nav = useNavigate();
|
||||||
@ -30,6 +37,13 @@ export default function ProjEdit() {
|
|||||||
const [previewUrl, setPreviewUrl] = useState('');
|
const [previewUrl, setPreviewUrl] = useState('');
|
||||||
const [generateErrorModal, setGenerateErrorModal] = useState(false);
|
const [generateErrorModal, setGenerateErrorModal] = useState(false);
|
||||||
const [generateErrorMsg, setGenerateErrorMsg] = useState('');
|
const [generateErrorMsg, setGenerateErrorMsg] = useState('');
|
||||||
|
const [aiHelperModalOpen, setAiHelperModalOpen] = useState(false);
|
||||||
|
const [aiHelper, setAiHelper] = useState<AiHelperType>({
|
||||||
|
projId: '',
|
||||||
|
projIntroduction: '',
|
||||||
|
projDesc: ''
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
const height = window.innerHeight - 240;
|
const height = window.innerHeight - 240;
|
||||||
|
|
||||||
@ -196,6 +210,13 @@ export default function ProjEdit() {
|
|||||||
setPreviewUrl(data.previewUrl);
|
setPreviewUrl(data.previewUrl);
|
||||||
setGenerateEmainingTime(data.generate.generateEmainingTime);
|
setGenerateEmainingTime(data.generate.generateEmainingTime);
|
||||||
setGenerateErrorMsg(data.generate.generateMsg);
|
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);
|
nav(-1);
|
||||||
}}>返回</Button>
|
}}>返回</Button>
|
||||||
</div>
|
</div>
|
||||||
|
<FloatButton.Group>
|
||||||
|
<FloatButton
|
||||||
|
onClick={() => {setAiHelperModalOpen(true)}}
|
||||||
|
description={<span style={{fontWeight: 'bold'}}>AI</span>}
|
||||||
|
>AI助手</FloatButton>
|
||||||
|
</FloatButton.Group>
|
||||||
|
|
||||||
<Modal title="提示"
|
<Modal title="提示"
|
||||||
okText="确定"
|
okText="确定"
|
||||||
cancelText="取消"
|
cancelText="取消"
|
||||||
@ -398,10 +426,25 @@ export default function ProjEdit() {
|
|||||||
backgroundColor: 'var(--color-primary)',
|
backgroundColor: 'var(--color-primary)',
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onCancel={() => {setGenerateErrorModal(false)}}
|
onCancel={() => {
|
||||||
|
setGenerateErrorModal(false)
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<p style={{color: 'var(--color-red)'}}>{generateErrorMsg}</p>
|
<p style={{color: 'var(--color-red)'}}>{generateErrorMsg}</p>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
<Modal open={aiHelperModalOpen}
|
||||||
|
title="AI助手"
|
||||||
|
footer={false}
|
||||||
|
onCancel={() => {
|
||||||
|
setAiHelperModalOpen(false);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AiHelper
|
||||||
|
projId={aiHelper.projId}
|
||||||
|
projIntroduction={aiHelper.projIntroduction}
|
||||||
|
projDesc={aiHelper.projDesc}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -6,8 +6,6 @@ import {get, post} from "../../util/AjaxUtils.ts";
|
|||||||
import {IProjCharge, ProjAdditionalType, ProjChargeType} from "../../interfaces/proj/IProj.ts";
|
import {IProjCharge, ProjAdditionalType, ProjChargeType} from "../../interfaces/proj/IProj.ts";
|
||||||
import {GlobalDispatchContext, reloadUser} from "../../context/GlobalContext.ts";
|
import {GlobalDispatchContext, reloadUser} from "../../context/GlobalContext.ts";
|
||||||
|
|
||||||
const {TextArea} = Input;
|
|
||||||
|
|
||||||
type ProjInfo = {
|
type ProjInfo = {
|
||||||
projName: string;
|
projName: string;
|
||||||
projIntroduction: string;
|
projIntroduction: string;
|
||||||
@ -104,21 +102,12 @@ export default function ProjNew() {
|
|||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
>
|
>
|
||||||
<Form.Item<ProjInfo>
|
<Form.Item<ProjInfo>
|
||||||
label="系统标题"
|
|
||||||
name="projName"
|
name="projName"
|
||||||
rules={[{required: true, message: '请输入系统标题'}]}
|
rules={[{required: true, message: '请输入系统标题'}]}
|
||||||
>
|
>
|
||||||
<Input placeholder="请输入系统标题"/>
|
<Input placeholder="请输入系统标题"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item<ProjInfo>
|
|
||||||
label="简单描述"
|
|
||||||
name="projIntroduction"
|
|
||||||
rules={[{required: true, message: '请输入简单描述'}]}
|
|
||||||
>
|
|
||||||
<TextArea rows={6} placeholder="请用一段话简单描述系统"/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Flex align="center" justify="center" gap="large">
|
<Flex align="center" justify="center" gap="large">
|
||||||
<Button type="primary" htmlType="submit"
|
<Button type="primary" htmlType="submit"
|
||||||
@ -147,7 +136,6 @@ export default function ProjNew() {
|
|||||||
url: '/api/proj/create',
|
url: '/api/proj/create',
|
||||||
body: {
|
body: {
|
||||||
projName: projInfo.projName,
|
projName: projInfo.projName,
|
||||||
projIntroduction: projInfo.projIntroduction,
|
|
||||||
projChargeType: pathParams.projChargeType,
|
projChargeType: pathParams.projChargeType,
|
||||||
listProjChargeAdditional: listProjChargeAdditional
|
listProjChargeAdditional: listProjChargeAdditional
|
||||||
},
|
},
|
||||||
|
@ -5,6 +5,7 @@ export const Axios = axios;
|
|||||||
|
|
||||||
axios.defaults.baseURL = 'http://127.0.0.1:7025/copyright';
|
axios.defaults.baseURL = 'http://127.0.0.1:7025/copyright';
|
||||||
// axios.defaults.baseURL = '/copyright';
|
// axios.defaults.baseURL = '/copyright';
|
||||||
|
export const WebSocketBaseUrl: string = "ws://127.0.0.1:7025/copyright"
|
||||||
export const DevUserId: string = '80d3365e-0597-4988-979e-18ef1c3ec671'; // 18634604067
|
export const DevUserId: string = '80d3365e-0597-4988-979e-18ef1c3ec671'; // 18634604067
|
||||||
// export const DevUserId: string = 'c2438eb8-2685-49a9-bf02-5111a5192d96'; // 18647109157
|
// export const DevUserId: string = 'c2438eb8-2685-49a9-bf02-5111a5192d96'; // 18647109157
|
||||||
// export const DevUserId: string = '';
|
// export const DevUserId: string = '';
|
||||||
|
Loading…
Reference in New Issue
Block a user