对接AI
This commit is contained in:
parent
94d3198c1a
commit
dd6dfd1b59
@ -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<Reducer<GlobalData, GlobalDataAction>>(globalDataReducer, {
|
||||
user: {
|
||||
balance: '0',
|
||||
userId: '',
|
||||
username: '',
|
||||
nickname: '',
|
||||
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 {
|
||||
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<GlobalData>({
|
||||
user: {
|
||||
balance: '0',
|
||||
nickname: '',
|
||||
userId: '',
|
||||
username: '',
|
||||
hasUserInfo: false
|
||||
}
|
||||
|
@ -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<AiHelperType>({
|
||||
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);
|
||||
}}>返回</Button>
|
||||
</div>
|
||||
<FloatButton.Group>
|
||||
<FloatButton
|
||||
onClick={() => {setAiHelperModalOpen(true)}}
|
||||
description={<span style={{fontWeight: 'bold'}}>AI</span>}
|
||||
>AI助手</FloatButton>
|
||||
</FloatButton.Group>
|
||||
|
||||
<Modal title="提示"
|
||||
okText="确定"
|
||||
cancelText="取消"
|
||||
@ -398,10 +426,25 @@ export default function ProjEdit() {
|
||||
backgroundColor: 'var(--color-primary)',
|
||||
}
|
||||
}}
|
||||
onCancel={() => {setGenerateErrorModal(false)}}
|
||||
onCancel={() => {
|
||||
setGenerateErrorModal(false)
|
||||
}}
|
||||
>
|
||||
<p style={{color: 'var(--color-red)'}}>{generateErrorMsg}</p>
|
||||
</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 {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"
|
||||
>
|
||||
<Form.Item<ProjInfo>
|
||||
label="系统标题"
|
||||
name="projName"
|
||||
rules={[{required: true, message: '请输入系统标题'}]}
|
||||
>
|
||||
<Input placeholder="请输入系统标题"/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item<ProjInfo>
|
||||
label="简单描述"
|
||||
name="projIntroduction"
|
||||
rules={[{required: true, message: '请输入简单描述'}]}
|
||||
>
|
||||
<TextArea rows={6} placeholder="请用一段话简单描述系统"/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Flex align="center" justify="center" gap="large">
|
||||
<Button type="primary" htmlType="submit"
|
||||
@ -147,7 +136,6 @@ export default function ProjNew() {
|
||||
url: '/api/proj/create',
|
||||
body: {
|
||||
projName: projInfo.projName,
|
||||
projIntroduction: projInfo.projIntroduction,
|
||||
projChargeType: pathParams.projChargeType,
|
||||
listProjChargeAdditional: listProjChargeAdditional
|
||||
},
|
||||
|
@ -5,6 +5,7 @@ export const Axios = axios;
|
||||
|
||||
axios.defaults.baseURL = 'http://127.0.0.1:7025/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 = 'c2438eb8-2685-49a9-bf02-5111a5192d96'; // 18647109157
|
||||
// export const DevUserId: string = '';
|
||||
|
Loading…
Reference in New Issue
Block a user