|
@@ -0,0 +1,609 @@
|
|
|
+import { useState, useEffect, useRef, useMemo, Key, ReactNode } from "react";
|
|
|
+import {
|
|
|
+ Card,
|
|
|
+ Menu,
|
|
|
+ Steps,
|
|
|
+ Tabs,
|
|
|
+ Tooltip,
|
|
|
+ TreeDataNode,
|
|
|
+ Tree,
|
|
|
+ Button,
|
|
|
+ Popover,
|
|
|
+ Result,
|
|
|
+ Row,
|
|
|
+ Col,
|
|
|
+} from "antd";
|
|
|
+import { Bubble } from "@ant-design/x";
|
|
|
+import { MessageItem } from "@/hooks/useChat";
|
|
|
+import { GetProp, GetProps, GetRef } from "antd";
|
|
|
+import type { StepsProps } from "antd/es/steps";
|
|
|
+import {
|
|
|
+ ApartmentOutlined,
|
|
|
+ AppstoreOutlined,
|
|
|
+ CarryOutOutlined,
|
|
|
+ CloudServerOutlined,
|
|
|
+ CodeOutlined,
|
|
|
+ DatabaseOutlined,
|
|
|
+ EditFilled,
|
|
|
+ LoadingOutlined,
|
|
|
+ OrderedListOutlined,
|
|
|
+ ReloadOutlined,
|
|
|
+} from "@ant-design/icons";
|
|
|
+import Flowchart from "./Flowchart";
|
|
|
+import { MenuProps } from "antd/lib";
|
|
|
+
|
|
|
+import step4Img from "@/assets/images/step4.png";
|
|
|
+import step3Img from "@/assets/images/step3.png";
|
|
|
+import flowImg from "@/assets/images/flow.png";
|
|
|
+import mindmapImg from "@/assets/images/mindmap.png";
|
|
|
+import MarkdownViewer from "@/components/ai/MarkdownViewer";
|
|
|
+import BubbleFooter from "./BubleFooter";
|
|
|
+
|
|
|
+const items = [
|
|
|
+ {
|
|
|
+ title: "生成需求",
|
|
|
+ icon: <OrderedListOutlined />,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "确认需求",
|
|
|
+ icon: <CarryOutOutlined />,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "系统设计",
|
|
|
+ icon: <AppstoreOutlined />,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "数据模型",
|
|
|
+ icon: <DatabaseOutlined />,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "页面生成",
|
|
|
+ icon: <ApartmentOutlined />,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "代码生成",
|
|
|
+ icon: <CodeOutlined />,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "发布部署",
|
|
|
+ icon: <CloudServerOutlined />,
|
|
|
+ },
|
|
|
+];
|
|
|
+
|
|
|
+export default function GenerateApp({
|
|
|
+ roles,
|
|
|
+ messages,
|
|
|
+ sender,
|
|
|
+}: {
|
|
|
+ roles: GetProp<typeof Bubble.List, "roles">;
|
|
|
+ messages: MessageItem[];
|
|
|
+ sender: React.ReactNode;
|
|
|
+}) {
|
|
|
+ const [contentHeight, setContentHeight] = useState(0);
|
|
|
+ const contentRef = useRef<HTMLDivElement>(null);
|
|
|
+ const listRef = useRef<GetRef<typeof Bubble.List>>(null);
|
|
|
+ const setHeight = () => {
|
|
|
+ setContentHeight(contentRef.current?.clientHeight || 0);
|
|
|
+ };
|
|
|
+
|
|
|
+ const [step, setStep] = useState(0);
|
|
|
+ // const stepItems = useMemo(() => {
|
|
|
+ // return items.map((item, index) => {
|
|
|
+ // if (index < step) {
|
|
|
+ // return {
|
|
|
+ // ...item,
|
|
|
+ // status: "finish",
|
|
|
+ // icon: null,
|
|
|
+ // };
|
|
|
+ // }
|
|
|
+ // if (index === step) {
|
|
|
+ // return {
|
|
|
+ // ...item,
|
|
|
+ // status: "process",
|
|
|
+ // icon: <LoadingOutlined />,
|
|
|
+ // };
|
|
|
+ // }
|
|
|
+ // if (index > step) {
|
|
|
+ // return {
|
|
|
+ // ...item,
|
|
|
+ // status: "wait",
|
|
|
+ // icon: null,
|
|
|
+ // };
|
|
|
+ // }
|
|
|
+ // return item;
|
|
|
+ // }) as StepsProps["items"];
|
|
|
+ // }, [step]);
|
|
|
+
|
|
|
+ // 需求阶段
|
|
|
+ const Step1Comp = () => {
|
|
|
+ useEffect(() => {
|
|
|
+ setHeight();
|
|
|
+ const resizeObserver = new ResizeObserver(() => {
|
|
|
+ setHeight();
|
|
|
+ });
|
|
|
+ resizeObserver.observe(contentRef.current!);
|
|
|
+ return () => {
|
|
|
+ resizeObserver.disconnect();
|
|
|
+ };
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ const getMessages = useMemo(() => {
|
|
|
+ return messages.map((item, index) => {
|
|
|
+ // 设置底部内容
|
|
|
+ if (
|
|
|
+ (index === messages.length - 1,
|
|
|
+ item.status === "done" && item.role === "assistant")
|
|
|
+ ) {
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ footer: (
|
|
|
+ <BubbleFooter
|
|
|
+ content={item.content as string}
|
|
|
+ query={item.content as string}
|
|
|
+ onNext={() => {
|
|
|
+ setStep(step + 1);
|
|
|
+ }}
|
|
|
+ />
|
|
|
+ ),
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return item;
|
|
|
+ });
|
|
|
+ }, [messages]);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <div className="overflow-hidden flex-1 mb-20px" ref={contentRef}>
|
|
|
+ <Bubble.List
|
|
|
+ ref={listRef}
|
|
|
+ style={{
|
|
|
+ maxHeight: contentHeight,
|
|
|
+ padding: "0 20px",
|
|
|
+ paddingBottom: "20px",
|
|
|
+ }}
|
|
|
+ autoScroll
|
|
|
+ roles={roles}
|
|
|
+ items={getMessages}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div className="flex justify-end">{sender}</div>
|
|
|
+ </>
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ // 确认需求
|
|
|
+ const Step2Comp = () => {
|
|
|
+ const getMessages = useMemo(() => {
|
|
|
+ return messages.map((item, index) => {
|
|
|
+ // 设置底部内容
|
|
|
+ if (
|
|
|
+ (index === messages.length - 1,
|
|
|
+ item.status === "done" && item.role === "assistant")
|
|
|
+ ) {
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ footer: (
|
|
|
+ <Button className="mb-12px" onClick={() => setStep(step + 1)}>
|
|
|
+ 确定需求
|
|
|
+ </Button>
|
|
|
+ ),
|
|
|
+ };
|
|
|
+ }
|
|
|
+ return item;
|
|
|
+ });
|
|
|
+ }, [messages]);
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <div className="overflow-hidden flex-1 mb-20px" ref={contentRef}>
|
|
|
+ <Bubble.List
|
|
|
+ ref={listRef}
|
|
|
+ style={{ maxHeight: contentHeight, padding: "0 20px" }}
|
|
|
+ autoScroll
|
|
|
+ roles={roles}
|
|
|
+ items={getMessages}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div className="flex justify-end">{sender}</div>
|
|
|
+ </>
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ // 系统设计
|
|
|
+ const Step3Comp = () => {
|
|
|
+ const [active, setActive] = useState("1");
|
|
|
+ return (
|
|
|
+ <div className="w-full h-full">
|
|
|
+ <Tabs
|
|
|
+ activeKey={active}
|
|
|
+ onChange={(key) => setActive(key)}
|
|
|
+ items={[
|
|
|
+ {
|
|
|
+ key: "1",
|
|
|
+ label: "功能模块图",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ key: "2",
|
|
|
+ label: "思维导图",
|
|
|
+ },
|
|
|
+ ]}
|
|
|
+ />
|
|
|
+ {/* <Flowchart data={messages[0].content} /> */}
|
|
|
+ <Row gutter={[8, 16]}>
|
|
|
+ {new Array(5).fill({}).map((item, index) => {
|
|
|
+ return (
|
|
|
+ <Col xs={24} sm={12} md={8} lg={6} xl={6} xxl={6} key={index}>
|
|
|
+ <Card
|
|
|
+ hoverable
|
|
|
+ cover={
|
|
|
+ <img
|
|
|
+ style={{
|
|
|
+ height: 120,
|
|
|
+ objectFit: "cover",
|
|
|
+ border: "solid 1px #f5f5f5",
|
|
|
+ }}
|
|
|
+ src={active === "1" ? flowImg : mindmapImg}
|
|
|
+ />
|
|
|
+ }
|
|
|
+ // onClick={() => handleToEdit()}
|
|
|
+ >
|
|
|
+ <Card.Meta
|
|
|
+ title={
|
|
|
+ <span>
|
|
|
+ {active === "1" ? (
|
|
|
+ <i
|
|
|
+ className={`iconfont icon-siweidaotu_huaban1 mr-8px`}
|
|
|
+ style={{ color: "#9ae396" }}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <i
|
|
|
+ className={`iconfont icon-liuchengtu color-#067bef mr-8px`}
|
|
|
+ />
|
|
|
+ )}
|
|
|
+ {active === "1" ? "功能模块图" : "思维导图"}
|
|
|
+ </span>
|
|
|
+ }
|
|
|
+ // description={`更新于: ${record.updatedTime}`}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ </Col>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </Row>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ // 数据模型
|
|
|
+ const Step4Comp = () => {
|
|
|
+ return (
|
|
|
+ <div>
|
|
|
+ <Tabs items={[{ key: "1", label: "数据模型" }]} />
|
|
|
+ <img className="w-full" src={step3Img} />
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ // 页面生成
|
|
|
+ const Step5Comp = () => {
|
|
|
+ const [collapsed, setCollapsed] = useState(false);
|
|
|
+ const toggleCollapsed = () => {
|
|
|
+ setCollapsed(!collapsed);
|
|
|
+ };
|
|
|
+ type MenuItem = Required<MenuProps>["items"][number];
|
|
|
+
|
|
|
+ const items: MenuItem[] = [
|
|
|
+ { key: "1", label: "客户信息管理" },
|
|
|
+ { key: "2", label: "客户分类管理" },
|
|
|
+ { key: "3", label: "客户根据管理" },
|
|
|
+ {
|
|
|
+ key: "sub1",
|
|
|
+ label: "客户统计分析",
|
|
|
+ children: [
|
|
|
+ { key: "5", label: "分析设置" },
|
|
|
+ { key: "6", label: "分析结果" },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="w-full h-full flex gap-12px">
|
|
|
+ <Card
|
|
|
+ styles={{
|
|
|
+ body: {
|
|
|
+ height: "100%",
|
|
|
+ width: "100%",
|
|
|
+ flexShrink: 0,
|
|
|
+ },
|
|
|
+ }}
|
|
|
+ title="应用菜单"
|
|
|
+ extra={
|
|
|
+ <Tooltip title="重新生成">
|
|
|
+ <ReloadOutlined className="cursor-pointer" />
|
|
|
+ </Tooltip>
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <Menu
|
|
|
+ defaultSelectedKeys={["1"]}
|
|
|
+ defaultOpenKeys={["sub1"]}
|
|
|
+ mode="inline"
|
|
|
+ inlineCollapsed={collapsed}
|
|
|
+ items={items}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ <Card title="页面预览">
|
|
|
+ <img className="w-full" src={step4Img} />
|
|
|
+ </Card>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ // 代码生成
|
|
|
+ const Step6Comp = () => {
|
|
|
+ type DirectoryTreeProps = GetProps<typeof Tree.DirectoryTree>;
|
|
|
+
|
|
|
+ const { DirectoryTree } = Tree;
|
|
|
+
|
|
|
+ const treeData: TreeDataNode[] = [
|
|
|
+ {
|
|
|
+ key: "0",
|
|
|
+ title: "页面代码",
|
|
|
+ children: [
|
|
|
+ {
|
|
|
+ title: "customer-info.html",
|
|
|
+ key: "0-0",
|
|
|
+ isLeaf: true,
|
|
|
+ style: {
|
|
|
+ overflow: "hidden",
|
|
|
+ textOverflow: "ellipsis",
|
|
|
+ whiteSpace: "nowrap",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "customer-category.html",
|
|
|
+ key: "0-1",
|
|
|
+ isLeaf: true,
|
|
|
+ style: {
|
|
|
+ overflow: "hidden",
|
|
|
+ textOverflow: "ellipsis",
|
|
|
+ whiteSpace: "nowrap",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "customer-producer.html",
|
|
|
+ key: "0-2",
|
|
|
+ isLeaf: true,
|
|
|
+ style: {
|
|
|
+ overflow: "hidden",
|
|
|
+ textOverflow: "ellipsis",
|
|
|
+ whiteSpace: "nowrap",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "customer-statistics.html",
|
|
|
+ key: "0-3",
|
|
|
+ isLeaf: true,
|
|
|
+ style: {
|
|
|
+ overflow: "hidden",
|
|
|
+ textOverflow: "ellipsis",
|
|
|
+ whiteSpace: "nowrap",
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ const [activeKey, setActiveKey] = useState<Key[]>(["0-0"]);
|
|
|
+ const onSelect: DirectoryTreeProps["onSelect"] = (keys, info) => {
|
|
|
+ console.log("Trigger Select", keys, info);
|
|
|
+ setActiveKey(keys);
|
|
|
+ };
|
|
|
+
|
|
|
+ const onExpand: DirectoryTreeProps["onExpand"] = (keys, info) => {
|
|
|
+ console.log("Trigger Expand", keys, info);
|
|
|
+ };
|
|
|
+
|
|
|
+ const htmlText = `<!DOCTYPE html>
|
|
|
+<html lang="en">
|
|
|
+ <head>
|
|
|
+ <meta charset="UTF-8">
|
|
|
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
+ <title>Document</title>
|
|
|
+ </head>
|
|
|
+ <body>
|
|
|
+ 页面代码...
|
|
|
+ </body>
|
|
|
+</html>`;
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="w-full h-full flex gap-12px">
|
|
|
+ <Card
|
|
|
+ styles={{
|
|
|
+ body: {
|
|
|
+ height: "100%",
|
|
|
+ width: "280px",
|
|
|
+ },
|
|
|
+ }}
|
|
|
+ title="文件列表"
|
|
|
+ extra={
|
|
|
+ <Tooltip title="重新生成">
|
|
|
+ <ReloadOutlined className="cursor-pointer" />
|
|
|
+ </Tooltip>
|
|
|
+ }
|
|
|
+ >
|
|
|
+ <DirectoryTree
|
|
|
+ multiple
|
|
|
+ defaultExpandAll
|
|
|
+ selectedKeys={activeKey}
|
|
|
+ onSelect={onSelect}
|
|
|
+ onExpand={onExpand}
|
|
|
+ treeData={treeData}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ <Card
|
|
|
+ className="flex-1"
|
|
|
+ title="代码内容"
|
|
|
+ styles={{
|
|
|
+ body: { padding: 0, height: "100%", overflow: "hidden" },
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div className="overflow-auto">
|
|
|
+ <MarkdownViewer content={"```html\n" + htmlText + "\n```"} />
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ // 发布部署
|
|
|
+ const Step7Comp = () => {
|
|
|
+ const [step, setStep] = useState(0);
|
|
|
+ const customDot: StepsProps["progressDot"] = (dot, { status, index }) => (
|
|
|
+ <Popover
|
|
|
+ content={
|
|
|
+ <span>
|
|
|
+ {status === "process" && "正在处理..."}
|
|
|
+ {status === "finish" && "处理成功"}
|
|
|
+ {status === "wait" && "等待处理"}
|
|
|
+ {status === "error" && "处理失败"}
|
|
|
+ </span>
|
|
|
+ }
|
|
|
+ >
|
|
|
+ {dot}
|
|
|
+ </Popover>
|
|
|
+ );
|
|
|
+
|
|
|
+ const [consoleText, setConsoleText] = useState<ReactNode[]>([]);
|
|
|
+ const timer = useRef<NodeJS.Timeout | null>(null);
|
|
|
+ const start = () => {
|
|
|
+ clearTimeout(timer.current as NodeJS.Timeout);
|
|
|
+ setConsoleText([]);
|
|
|
+ let index = 0;
|
|
|
+ const getArr = (time: string) => {
|
|
|
+ return [
|
|
|
+ `[${time}] 正在创建应用数据...\n`,
|
|
|
+ `[${time}] 初始化数据库连接...`,
|
|
|
+ `[${time}] 创建数据表结构...`,
|
|
|
+ `[${time}] 导入基础配置数据...`,
|
|
|
+ `[${time}] 生成模块配置文件...`,
|
|
|
+ `[${time}] 创建用户权限系统...`,
|
|
|
+ `[${time}] 设置系统参数...`,
|
|
|
+ `[${time}] 初始化缓存服务...`,
|
|
|
+ `[${time}] 注册系统服务...`,
|
|
|
+ `[${time}] 应用数据创建完成!`,
|
|
|
+ ]
|
|
|
+ }
|
|
|
+
|
|
|
+ timer.current = setInterval(() => {
|
|
|
+ if(index >= 9) {
|
|
|
+ clearTimeout(timer.current as NodeJS.Timeout);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const log = getArr(new Date().toLocaleString())[index];
|
|
|
+ setConsoleText((prev) => {
|
|
|
+ return [...prev, <div key={index}>{log}</div>];
|
|
|
+ });
|
|
|
+ index++;
|
|
|
+ }, 1000);
|
|
|
+ };
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ start();
|
|
|
+ }, [step]);
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="w-full h-full flex flex-col gap-12px">
|
|
|
+ <Steps
|
|
|
+ current={step}
|
|
|
+ progressDot={customDot}
|
|
|
+ onChange={setStep}
|
|
|
+ items={[
|
|
|
+ {
|
|
|
+ title: "创建应用数据",
|
|
|
+ description: "根据上面的结果创建应用",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "发布模块设计",
|
|
|
+ description: "创建相应模块数据",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "编译项目文件",
|
|
|
+ description: "编译整个项目文件",
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: "部署安装包",
|
|
|
+ description: "部署成功!",
|
|
|
+ },
|
|
|
+ ]}
|
|
|
+ />
|
|
|
+ {step === 3 ? (
|
|
|
+ <>
|
|
|
+ <Result status="success" title="部署成功!">
|
|
|
+ <div className="text-center">
|
|
|
+ <span>
|
|
|
+ 点击<a>沙鲁客户管理系统</a>查看访问系统!
|
|
|
+ </span>
|
|
|
+ <span>
|
|
|
+ 或不满意,点击<a className="text-red hover:text-red">删除</a>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </Result>
|
|
|
+ </>
|
|
|
+ ) : (
|
|
|
+ <Card
|
|
|
+ className="flex-1"
|
|
|
+ title="控制台"
|
|
|
+ styles={{
|
|
|
+ body: {
|
|
|
+ height: "calc(100% - 56px)",
|
|
|
+ padding: 0,
|
|
|
+ paddingBottom: 12,
|
|
|
+ position: "relative",
|
|
|
+ },
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div className="w-full h-full bg-#000 overflow-auto text-12px text-#fff p-12px rounded-8px">
|
|
|
+ {consoleText}
|
|
|
+ </div>
|
|
|
+ </Card>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ };
|
|
|
+
|
|
|
+ return (
|
|
|
+ <>
|
|
|
+ <div className="absolute h-56px left-1/2 -translate-x-1/2 text-18px text-#666">
|
|
|
+ <span>沙鲁客户管理系统</span>
|
|
|
+ <Button type="text" icon={<EditFilled className="text-#666" />} />
|
|
|
+ </div>
|
|
|
+ <div
|
|
|
+ className="w-full mt-44px flex gap-12px"
|
|
|
+ style={{ height: "calc(100% - 44px)" }}
|
|
|
+ >
|
|
|
+ <Card className="w-300px h-full pl-30px pt-30px">
|
|
|
+ <Steps
|
|
|
+ className="h-500px"
|
|
|
+ direction="vertical"
|
|
|
+ items={items}
|
|
|
+ current={step}
|
|
|
+ onChange={setStep}
|
|
|
+ />
|
|
|
+ </Card>
|
|
|
+ <Card
|
|
|
+ className="flex-1"
|
|
|
+ styles={{
|
|
|
+ body: { height: "100%", display: "flex", flexDirection: "column" },
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ {step === 0 && <Step1Comp />}
|
|
|
+ {step === 1 && <Step2Comp />}
|
|
|
+ {step === 2 && <Step3Comp />}
|
|
|
+ {step === 3 && <Step4Comp />}
|
|
|
+ {step === 4 && <Step5Comp />}
|
|
|
+ {step === 5 && <Step6Comp />}
|
|
|
+ {step === 6 && <Step7Comp />}
|
|
|
+ </Card>
|
|
|
+ </div>
|
|
|
+ </>
|
|
|
+ );
|
|
|
+}
|