2024-04-19 18:20:51 +08:00
|
|
|
import {useContext, useEffect, useRef, useState} from "react";
|
|
|
|
import {GlobalContext} from "../../context/GlobalContext.ts";
|
|
|
|
import {put, WebSocketBaseUrl} from "../../util/AjaxUtils.ts";
|
2024-04-21 16:11:13 +08:00
|
|
|
import {Button, Col, Divider, Empty, Row, Space, Spin, Table, TableProps} from "antd";
|
2024-04-22 18:20:26 +08:00
|
|
|
import {CheckOutlined, EditOutlined, ReloadOutlined} from "@ant-design/icons";
|
2024-04-19 18:20:51 +08:00
|
|
|
import useMessage from "antd/es/message/useMessage";
|
2024-04-22 18:20:26 +08:00
|
|
|
import TextArea from "antd/es/input/TextArea";
|
2024-04-19 18:20:51 +08:00
|
|
|
|
|
|
|
type PropsType = {
|
|
|
|
projId: string;
|
|
|
|
projIntroduction?: string;
|
|
|
|
projDesc?: string;
|
2024-04-22 18:20:26 +08:00
|
|
|
projMods?: ProjModType[];
|
2024-04-19 18:20:51 +08:00
|
|
|
}
|
|
|
|
|
2024-04-19 21:09:09 +08:00
|
|
|
type ProjModType = {
|
|
|
|
name: string,
|
|
|
|
desc: string
|
|
|
|
}
|
|
|
|
|
2024-04-19 18:20:51 +08:00
|
|
|
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>('');
|
2024-04-22 18:20:26 +08:00
|
|
|
const [isProjIntroductionEdit, setIsProjIntroductionEdit] = useState<boolean>(false);
|
|
|
|
const [isNewProjIntroductionEdit, setIsNewProjIntroductionEdit] = useState<boolean>(false);
|
2024-04-19 18:20:51 +08:00
|
|
|
const [isProjIntroductionLoading, setIsProjIntroductionLoading] = useState(false);
|
2024-04-22 18:20:26 +08:00
|
|
|
|
2024-04-19 18:20:51 +08:00
|
|
|
const [projDesc, setProjDesc] = useState<string>(props.projDesc ? props.projDesc : '');
|
|
|
|
const [newProjDesc, setNewProjDesc] = useState<string>('');
|
2024-04-22 18:20:26 +08:00
|
|
|
const [isProjDescEdit, setIsProjDescEdit] = useState<boolean>(false);
|
|
|
|
const [isNewProjDescEdit, setIsNewProjDescEdit] = useState<boolean>(false);
|
2024-04-19 18:20:51 +08:00
|
|
|
const [isProjDescLoading, setIsProjDescLoading] = useState(false);
|
2024-04-22 18:20:26 +08:00
|
|
|
|
|
|
|
const [projModArray, setProjModArray] = useState<ProjModType[]>(props.projMods ? props.projMods : []);
|
2024-04-19 21:09:09 +08:00
|
|
|
const [newProjModArray, setNewProjModArray] = useState<ProjModType[]>([]);
|
|
|
|
const [isProjModArrayLoading, setIsProjModArrayLoading] = useState(false);
|
2024-04-19 18:20:51 +08:00
|
|
|
|
|
|
|
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);
|
2024-04-19 21:09:09 +08:00
|
|
|
if (data.projId != props.projId) {
|
|
|
|
return;
|
|
|
|
}
|
2024-04-19 18:20:51 +08:00
|
|
|
if (data.type == 'REFRESH_PROJ_INTRODUCTION') {
|
|
|
|
setIsProjIntroductionLoading(false);
|
|
|
|
setNewProjIntroduction(data.content);
|
2024-04-19 21:09:09 +08:00
|
|
|
} else if (data.type == 'REFRESH_PROJ_DESC') {
|
2024-04-19 18:20:51 +08:00
|
|
|
setIsProjDescLoading(false);
|
|
|
|
setNewProjDesc(data.content);
|
2024-04-19 21:09:09 +08:00
|
|
|
} else if (data.type == 'REFRESH_PROJ_MODS') {
|
|
|
|
setIsProjModArrayLoading(false);
|
|
|
|
const projMods = JSON.parse(data.content) as ProjModType[];
|
|
|
|
setNewProjModArray(projMods);
|
2024-04-19 18:20:51 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
ws.current.onerror = (event) => {
|
|
|
|
console.log('error', event);
|
|
|
|
}
|
|
|
|
ws.current.onclose = (event) => {
|
|
|
|
console.log('close', event);
|
|
|
|
websocket()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-04-19 21:09:09 +08:00
|
|
|
const projModColumnArray: TableProps<ProjModType>['columns'] = [
|
2024-04-22 18:20:26 +08:00
|
|
|
{
|
|
|
|
title: '序号',
|
|
|
|
dataIndex: 'index',
|
|
|
|
key: 'index',
|
|
|
|
width: 60,
|
|
|
|
align: 'center',
|
|
|
|
render: (_value, _record, index) => {
|
|
|
|
return index + 1
|
|
|
|
}
|
|
|
|
},
|
2024-04-21 16:11:13 +08:00
|
|
|
{title: '模块名称', dataIndex: 'name', key: 'name', width: 200, align: 'center'},
|
2024-04-19 21:09:09 +08:00
|
|
|
{title: '模块描述', dataIndex: 'desc', key: 'desc', align: 'center'},
|
|
|
|
];
|
|
|
|
|
2024-04-19 18:20:51 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2024-04-19 21:09:09 +08:00
|
|
|
const generateProjModArray = () => {
|
|
|
|
ws.current?.send(JSON.stringify({
|
|
|
|
type: 'REFRESH_PROJ_MODS',
|
|
|
|
projId: props.projId
|
|
|
|
}));
|
|
|
|
ping();
|
|
|
|
setIsProjModArrayLoading(true);
|
|
|
|
}
|
|
|
|
|
2024-04-19 18:20:51 +08:00
|
|
|
/**
|
|
|
|
* 保存简介
|
|
|
|
*/
|
2024-04-22 18:20:26 +08:00
|
|
|
const updateProjIntroduction = (content: string) => {
|
2024-04-19 18:20:51 +08:00
|
|
|
put<any>({
|
|
|
|
messageApi,
|
|
|
|
url: `/api/proj/update-introduction/${props.projId}`,
|
|
|
|
body: {
|
2024-04-22 18:20:26 +08:00
|
|
|
content: content
|
2024-04-19 18:20:51 +08:00
|
|
|
},
|
|
|
|
onBefore() {
|
|
|
|
setIsProjIntroductionLoading(true);
|
|
|
|
},
|
|
|
|
onSuccess() {
|
|
|
|
messageApi.success('保存成功');
|
2024-04-22 18:20:26 +08:00
|
|
|
setProjIntroduction(content);
|
2024-04-19 18:20:51 +08:00
|
|
|
setNewProjIntroduction('');
|
|
|
|
},
|
|
|
|
onFinally() {
|
|
|
|
setIsProjIntroductionLoading(false)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 保存详情
|
|
|
|
*/
|
2024-04-22 18:20:26 +08:00
|
|
|
const updateProjDesc = (content: string) => {
|
2024-04-19 18:20:51 +08:00
|
|
|
put<any>({
|
|
|
|
messageApi,
|
|
|
|
url: `/api/proj/update-desc/${props.projId}`,
|
|
|
|
body: {
|
2024-04-22 18:20:26 +08:00
|
|
|
content: content
|
2024-04-19 18:20:51 +08:00
|
|
|
},
|
|
|
|
onBefore() {
|
|
|
|
setIsProjDescLoading(true);
|
|
|
|
},
|
|
|
|
onSuccess() {
|
|
|
|
messageApi.success('保存成功').then();
|
2024-04-22 18:20:26 +08:00
|
|
|
setProjDesc(content);
|
2024-04-19 18:20:51 +08:00
|
|
|
setNewProjDesc('');
|
|
|
|
},
|
|
|
|
onFinally() {
|
|
|
|
setIsProjDescLoading(false)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2024-04-22 18:20:26 +08:00
|
|
|
/**
|
|
|
|
* 保存模块
|
|
|
|
*/
|
|
|
|
const updateProjModArray = () => {
|
|
|
|
put<any>({
|
|
|
|
messageApi,
|
|
|
|
url: `/api/proj-mod/update-mods/${props.projId}`,
|
|
|
|
body: {
|
|
|
|
list: newProjModArray
|
|
|
|
},
|
|
|
|
onBefore() {
|
|
|
|
setIsProjModArrayLoading(true);
|
|
|
|
},
|
|
|
|
onSuccess() {
|
|
|
|
messageApi.success('保存成功').then();
|
|
|
|
setProjModArray(newProjModArray);
|
|
|
|
setNewProjModArray([]);
|
|
|
|
},
|
|
|
|
onFinally() {
|
|
|
|
setIsProjModArrayLoading(false)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2024-04-19 18:20:51 +08:00
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (!props.projId) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
websocket();
|
|
|
|
}, [globalContext.user.userId, props.projId]);
|
|
|
|
|
2024-04-22 18:20:26 +08:00
|
|
|
/**
|
|
|
|
* 项目简介按钮
|
|
|
|
*/
|
|
|
|
const renderNewProjIntroductionBtn = () => {
|
|
|
|
if(!newProjIntroduction) {
|
|
|
|
if(!isProjIntroductionEdit) {
|
|
|
|
return (
|
|
|
|
<Space>
|
|
|
|
<Button type="link" style={{cursor: 'pointer'}} onClick={generateProjIntroduction}>AI生成</Button>
|
|
|
|
{
|
|
|
|
projIntroduction ? (
|
|
|
|
<Button type="link" style={{cursor: 'pointer'}} onClick={() => {
|
|
|
|
setIsProjIntroductionEdit(true);
|
|
|
|
}}><EditOutlined /> 编辑</Button>
|
|
|
|
) : <></>
|
|
|
|
}
|
|
|
|
</Space>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<Space>
|
|
|
|
<Button type="link" style={{cursor: 'pointer'}} onClick={() => {
|
|
|
|
setIsProjIntroductionEdit(false);
|
|
|
|
updateProjIntroduction(projIntroduction);
|
|
|
|
}}><CheckOutlined/> 保存结果</Button>
|
|
|
|
</Space>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if(!isNewProjIntroductionEdit) {
|
|
|
|
return (
|
|
|
|
<Space>
|
|
|
|
<Button type="link" style={{cursor: 'pointer'}} onClick={() => {
|
|
|
|
updateProjIntroduction(newProjIntroduction);
|
|
|
|
}}><CheckOutlined/> 保存结果</Button>
|
|
|
|
<Button type="link" style={{cursor: 'pointer'}} onClick={generateProjIntroduction}><ReloadOutlined/> 重新生成</Button>
|
|
|
|
<Button type="link" style={{cursor: 'pointer'}} onClick={() => {
|
|
|
|
setIsNewProjIntroductionEdit(true);
|
|
|
|
}}><EditOutlined /> 编辑</Button>
|
|
|
|
</Space>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<Space>
|
|
|
|
<Button type="link" style={{cursor: 'pointer'}} onClick={() => {
|
|
|
|
setIsNewProjIntroductionEdit(false);
|
|
|
|
}}><CheckOutlined/> 确定</Button>
|
|
|
|
</Space>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
/**
|
|
|
|
* 项目简介按钮
|
|
|
|
*/
|
|
|
|
const renderNewProjDescBtn = () => {
|
|
|
|
if(!newProjDesc) {
|
|
|
|
if(!isProjDescEdit) {
|
|
|
|
return (
|
|
|
|
<Space>
|
|
|
|
<Button type="link" style={{cursor: 'pointer'}} onClick={generateProjDesc}>AI生成</Button>
|
|
|
|
{
|
|
|
|
projIntroduction ? (
|
|
|
|
<Button type="link" style={{cursor: 'pointer'}} onClick={() => {
|
|
|
|
setIsProjDescEdit(true);
|
|
|
|
}}><EditOutlined /> 编辑</Button>
|
|
|
|
) : <></>
|
|
|
|
}
|
|
|
|
</Space>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<Space>
|
|
|
|
<Button type="link" style={{cursor: 'pointer'}} onClick={() => {
|
|
|
|
setIsProjDescEdit(false);
|
|
|
|
updateProjDesc(projDesc);
|
|
|
|
}}><CheckOutlined/> 保存结果</Button>
|
|
|
|
</Space>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
if(!isNewProjDescEdit) {
|
|
|
|
return (
|
|
|
|
<Space>
|
|
|
|
<Button type="link" style={{cursor: 'pointer'}} onClick={() => {
|
|
|
|
updateProjDesc(newProjDesc);
|
|
|
|
}}><CheckOutlined/> 保存结果</Button>
|
|
|
|
<Button type="link" style={{cursor: 'pointer'}} onClick={generateProjDesc}><ReloadOutlined/> 重新生成</Button>
|
|
|
|
<Button type="link" style={{cursor: 'pointer'}} onClick={() => {
|
|
|
|
setIsNewProjDescEdit(true);
|
|
|
|
}}><EditOutlined /> 编辑</Button>
|
|
|
|
</Space>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return (
|
|
|
|
<Space>
|
|
|
|
<Button type="link" style={{cursor: 'pointer'}} onClick={() => {
|
|
|
|
setIsNewProjDescEdit(false);
|
|
|
|
}}><CheckOutlined/> 确定</Button>
|
|
|
|
</Space>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2024-04-19 18:20:51 +08:00
|
|
|
return (
|
|
|
|
<>
|
|
|
|
{messageApiHolder}
|
2024-04-21 16:11:13 +08:00
|
|
|
<Row>
|
|
|
|
<Col span={24}>
|
|
|
|
<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> : <></>}
|
2024-04-22 18:20:26 +08:00
|
|
|
{projIntroduction ? (
|
|
|
|
isProjIntroductionEdit ? <TextArea rows={10} value={projIntroduction} placeholder="请编辑简介" maxLength={1500} onChange={(e) => {
|
|
|
|
setProjIntroduction(e.currentTarget.value);
|
|
|
|
}}/>
|
|
|
|
: <div>{projIntroduction}</div>
|
|
|
|
) : <Empty description="暂无内容"/>}
|
2024-04-21 16:11:13 +08:00
|
|
|
{
|
|
|
|
newProjIntroduction ? (
|
|
|
|
<>
|
|
|
|
<Divider orientation="right" plain>新简介</Divider>
|
2024-04-22 18:20:26 +08:00
|
|
|
{
|
|
|
|
isNewProjIntroductionEdit ? (
|
|
|
|
<TextArea rows={10} value={newProjIntroduction} placeholder="请编辑简介" maxLength={1500} onChange={(e) => {
|
|
|
|
setNewProjIntroduction(e.currentTarget.value);
|
|
|
|
}}/>
|
|
|
|
) : <div>{newProjIntroduction}</div>
|
|
|
|
}
|
2024-04-21 16:11:13 +08:00
|
|
|
</>
|
|
|
|
) : <></>
|
|
|
|
}
|
|
|
|
</div>
|
|
|
|
<div style={{padding: '5px 0 0 0', textAlign: 'center'}}>
|
2024-04-22 18:20:26 +08:00
|
|
|
{renderNewProjIntroductionBtn()}
|
2024-04-21 16:11:13 +08:00
|
|
|
</div>
|
|
|
|
</Spin>
|
|
|
|
</Col>
|
|
|
|
<Col span={24}>
|
|
|
|
<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> : <></>}
|
2024-04-22 18:20:26 +08:00
|
|
|
{projDesc ? (
|
|
|
|
isProjDescEdit ?
|
|
|
|
<TextArea rows={10} value={projDesc} placeholder="请编辑详情"
|
|
|
|
maxLength={1500} onChange={(e) => {
|
|
|
|
setProjDesc(e.currentTarget.value);
|
|
|
|
}}/>
|
|
|
|
: <div>{projDesc}</div>
|
|
|
|
) : <Empty description="暂无内容"/>}
|
2024-04-21 16:11:13 +08:00
|
|
|
{
|
|
|
|
newProjDesc ? (
|
|
|
|
<>
|
|
|
|
<Divider orientation="right" plain>新详情</Divider>
|
2024-04-22 18:20:26 +08:00
|
|
|
{
|
|
|
|
isNewProjDescEdit ? (
|
|
|
|
<TextArea rows={10} value={newProjDesc} placeholder="请编辑简介"
|
|
|
|
maxLength={1500} onChange={(e) => {
|
|
|
|
setNewProjDesc(e.currentTarget.value);
|
|
|
|
}}/>
|
|
|
|
) : <div>{newProjDesc}</div>
|
|
|
|
}
|
2024-04-21 16:11:13 +08:00
|
|
|
</>
|
|
|
|
) : <></>
|
|
|
|
}
|
|
|
|
</div>
|
|
|
|
<div style={{padding: '5px 0 0 0', textAlign: 'center'}}>
|
2024-04-22 18:20:26 +08:00
|
|
|
{renderNewProjDescBtn()}
|
2024-04-21 16:11:13 +08:00
|
|
|
</div>
|
|
|
|
</Spin>
|
|
|
|
</Col>
|
|
|
|
<Col span={24}>
|
|
|
|
<Divider dashed/>
|
|
|
|
<div style={{padding: '5px 0 0 0', fontWeight: 'bold'}}>项目模块</div>
|
|
|
|
<Spin tip="正在处理,请稍后..." size="small" spinning={isProjModArrayLoading}>
|
|
|
|
<div style={{padding: '5px 0 0 0'}}>
|
|
|
|
{newProjModArray.length > 0 ? <Divider orientation="right" plain>原模块</Divider> : <></>}
|
2024-04-22 18:20:26 +08:00
|
|
|
<Table columns={projModColumnArray} dataSource={projModArray} size="small" bordered={true}
|
|
|
|
scroll={{y: 240}} pagination={{pageSize: 20}}/>
|
2024-04-21 16:11:13 +08:00
|
|
|
{
|
|
|
|
newProjModArray.length > 0 ? (
|
|
|
|
<>
|
|
|
|
<Divider orientation="right" plain>新模块</Divider>
|
2024-04-22 18:20:26 +08:00
|
|
|
<Table columns={projModColumnArray} dataSource={newProjModArray} size="small"
|
|
|
|
bordered={true} scroll={{y: 240}} pagination={{pageSize: 20}}/>
|
2024-04-21 16:11:13 +08:00
|
|
|
</>
|
|
|
|
) : <></>
|
|
|
|
}
|
|
|
|
</div>
|
|
|
|
<div style={{padding: '5px 0 0 0', textAlign: 'center'}}>
|
|
|
|
{
|
|
|
|
newProjModArray.length > 0 ? (
|
|
|
|
<Space>
|
|
|
|
<Button type="link" style={{cursor: 'pointer'}}
|
2024-04-22 18:20:26 +08:00
|
|
|
onClick={updateProjModArray}><CheckOutlined/> 保存结果</Button>
|
2024-04-21 16:11:13 +08:00
|
|
|
<Button type="link" style={{cursor: 'pointer'}}
|
|
|
|
onClick={generateProjModArray}><ReloadOutlined/> 重新生成</Button>
|
|
|
|
</Space>
|
|
|
|
) : <Button type="link" style={{cursor: 'pointer'}}
|
|
|
|
onClick={generateProjModArray}>AI生成</Button>
|
|
|
|
}
|
|
|
|
</div>
|
|
|
|
</Spin>
|
|
|
|
</Col>
|
|
|
|
</Row>
|
2024-04-19 18:20:51 +08:00
|
|
|
</>
|
2024-04-21 16:11:13 +08:00
|
|
|
)
|
2024-04-19 18:20:51 +08:00
|
|
|
}
|