Explorar el Código

feat: 添加AI生成应用原型页面

liaojiaxing hace 2 semanas
padre
commit
7ce10e2ab9

+ 2 - 2
apps/ai-easy-builder/.gitignore

@@ -6,6 +6,6 @@
 /src/.umi-production
 /src/.umi-test
 /dist
-/dataModel
-/dataModel.zip
+/appGenerate
+/appGenerate.zip
 .swc

+ 1 - 1
apps/ai-easy-builder/.umirc.ts

@@ -3,7 +3,7 @@ import { defineConfig } from "umi";
 export default defineConfig({
   base: '/',
   publicPath: process.env.NODE_ENV === 'production' ? './' : '/',
-  outputPath: 'easyBuilder',
+  outputPath: 'appGenerate',
   esbuildMinifyIIFE: true,
   favicons: [
     '/favicon.ico'

+ 1 - 1
apps/ai-easy-builder/README.md

@@ -1,4 +1,4 @@
-# AI易搭
+# AI应用创建
 
 ## 依赖安装
 ```

+ 25 - 3
apps/ai-easy-builder/src/api/index.ts

@@ -1,7 +1,5 @@
 import { request } from "umi";
 
-export * from "./dataModel";
-
 export type commonParams = {
   currentPage: number;
   pageSize: number;
@@ -119,4 +117,28 @@ export const GetSystemVariable = () => {
   return request("/api/workflow/GetAllWorkflowVariable", {
     method: "POST",
   });
-};
+};
+
+/**
+ * 获取当前用户信息
+ * @param
+ * @returns
+ */
+export const GetUserInfo = () => {
+  return request("/api/account/curUserInfo", {
+    method: "POST",
+    skipErrorHandler: true
+  });
+};
+
+/**
+ * 获取退出登陆
+ * @param
+ * @returns
+ */
+export const Logout = () => {
+  return request("/api/account/doLogout", {
+    method: "POST",
+    skipErrorHandler: true
+  });
+};

BIN
apps/ai-easy-builder/src/assets/images/flow.png


BIN
apps/ai-easy-builder/src/assets/images/mindmap.png


BIN
apps/ai-easy-builder/src/assets/images/step2.png


BIN
apps/ai-easy-builder/src/assets/images/step3.png


BIN
apps/ai-easy-builder/src/assets/images/step4.png


BIN
apps/ai-easy-builder/src/assets/shalu-new1.png


+ 3 - 3
apps/ai-easy-builder/src/hooks/useChat.ts

@@ -7,7 +7,7 @@ import type { ConversationsProps } from "@ant-design/x";
 import type { ReactNode } from "react";
 
 // 消息格式
-type MessageItem = {
+export type MessageItem = {
   id: string;
   content: string | ReactNode;
   role: "user" | "assistant" | "system";
@@ -268,10 +268,10 @@ export function useChat({ app_name, onSuccess, onUpdate, onError }: ChatProps) {
         } else {
           // 接口异常处理
           response.json().then(res => {
-            if(res.code === 0 ) {
+            // if(res.code === 0 ) {
               onError?.(Error(res?.error || '请求失败'));
               cancel();
-            }
+            // }
           });
         }
       } catch (error) {

+ 1 - 0
apps/ai-easy-builder/src/layouts/index.less

@@ -1,5 +1,6 @@
 body {
   margin: 0;
+  padding: 0;
 }
 
 // 滚动条

+ 123 - 17
apps/ai-easy-builder/src/layouts/index.tsx

@@ -1,26 +1,132 @@
-import { Outlet } from 'umi';
-import './index.less';
-import { useSearchParams } from 'umi';
-import { useEffect } from 'react';
-import zhCN from 'antd/locale/zh_CN';
-import dayjs from 'dayjs';
-import { ConfigProvider } from "antd";
-
-dayjs.locale('zh-cn')
+import { Link, Outlet, useLocation } from "umi";
+import logo from "@/assets/shalu-new1.png";
+import { Avatar, ConfigProvider, Button, Dropdown } from "antd";
+import zhCN from "antd/locale/zh_CN";
+import "dayjs/locale/zh-cn";
+import { DownOutlined, UserOutlined } from "@ant-design/icons";
+import { useModel } from "umi";
+import { useEffect } from "react";
+import "./index.less";
 
 export default function Layout() {
-  const [searchParams] = useSearchParams();
+  const location = useLocation();
+  const { userInfo, handleLogout, checkUserInfo } = useModel("userModel");
+
+  const handleLogin = () => {
+    window.open(
+      `/Views/Account/Index.html?ReturnUrl=${window.location.href}`,
+      "_self"
+    );
+  };
 
   useEffect(() => {
-    const enterpriseCode = searchParams.get('enterpriseCode');
-  
-    if(enterpriseCode) {
-      sessionStorage.setItem('enterpriseCode', enterpriseCode);
-    }
-  }, [searchParams]);
+    checkUserInfo();
+  }, []);
+
+  const handleToManagement = () => {
+    window.open(`/Views/Home/Index.html`);
+  };
+
+  const handleToSchool = () => {
+    const url =
+      "https://college.shalu.com/Views/Account/SSOIndex.html?system=design&token=" +
+      localStorage.getItem("token_a");
+    window.open(url);
+  };
+
   return (
     <ConfigProvider locale={zhCN}>
-      <Outlet />
+      <div className="relative">
+        <div
+          className="
+        absolute 
+        box-border
+        left-0 
+        top-0 
+        w-full 
+        header 
+        h-56px 
+        flex 
+        items-center 
+        justify-between 
+        px-8
+        z-10"
+        >
+          <img src={logo} alt="logo" className="h-48px w-170px" />
+
+          {/* <ul className="menu flex items-center pl-0">
+            <Link to="/" className="decoration-none">
+              <li
+                className={`nav-button ${location.pathname.includes("application") ? "nav-button-active" : ""} `}
+              >
+                <i className="iconfont icon-yingyong text-12px mr-4px" />
+                应用市场
+              </li>
+            </Link>
+            <Link to="/template" className="decoration-none">
+              <li
+                className={`nav-button ${location.pathname.includes("/template") ? "nav-button-active" : ""} `}
+              >
+                <i className="iconfont icon-mokuai text-12px mr-4px" />
+                模版市场
+              </li>
+            </Link>
+            <li className={`nav-button`} onClick={handleToSchool}>
+              <i className="iconfont icon-shuben-book3 text-12px mr-4px" />
+              沙鲁学院
+            </li>
+            <Link to="/ai" target="_blank" className="decoration-none">
+              <li className={`nav-button`}>
+                <i className="iconfont icon-AI1 text-12px mr-4px" />
+                AI助手
+              </li>
+            </Link>
+          </ul> */}
+
+          <div className="right w-170px text-right">
+            {userInfo ? (
+              <Dropdown
+                menu={{
+                  items: [
+                    {
+                      key: "1",
+                      label: <span>管理后台</span>,
+                      onClick: handleToManagement,
+                    },
+                    {
+                      key: "2",
+                      label: <span>退出</span>,
+                      onClick: handleLogout,
+                    },
+                  ],
+                }}
+                trigger={["click"]}
+                placement="bottomRight"
+                arrow
+              >
+                <span>
+                  <Avatar size={32} icon={<UserOutlined />} />
+                  <span className="ml-4px text-12px text-text-secondary cursor-pointer">
+                    {userInfo.account}
+                  </span>
+                  <DownOutlined className="ml-4px text-12px text-text-secondary" />
+                </span>
+              </Dropdown>
+            ) : (
+              <Button
+                className="ml-4px"
+                size="small"
+                onClick={handleLogin}
+                shape="round"
+              >
+                登录
+              </Button>
+            )}
+          </div>
+        </div>
+
+        <Outlet />
+      </div>
     </ConfigProvider>
   );
 }

+ 35 - 0
apps/ai-easy-builder/src/models/userModel.ts

@@ -0,0 +1,35 @@
+import { useState, useEffect } from "react";
+import { GetUserInfo, Logout } from "@/api";
+import { message } from "antd";
+
+export type UserInfo = {
+  account: string;
+  cellPhone: string;
+  id: string;
+  isActive: boolean;
+  langName: string;
+  language: string;
+  partnerInfoId: string;
+};
+export default function userModel(){
+  const [userInfo, setUserInfo] = useState<UserInfo>();
+
+  const checkUserInfo = () => {
+    GetUserInfo().then((res) => {
+      setUserInfo(res?.result);
+    });
+  }
+
+  const handleLogout = () => {
+    Logout().then((res) => {
+      message.success("退出成功");
+      setUserInfo(undefined);
+    });
+  };
+
+  return {
+    userInfo,
+    handleLogout,
+    checkUserInfo
+  };
+};

+ 38 - 0
apps/ai-easy-builder/src/pages/home/components/BubleFooter.tsx

@@ -0,0 +1,38 @@
+import { message, Button, Space } from "antd";
+
+// 底部组件
+const BubbleFooter = (props: { content: string; query: string, onNext: () => void }) => {
+  const handleCopy = () => {
+    navigator.clipboard.writeText(props.content);
+    message.success("复制成功");
+  };
+
+  const handleGenerateApp = () => {
+    // todo 生成应用
+    // message.info("正在开发中,敬请期待...");
+    // setMessages((arr) => {
+    //   return [
+    //     ...arr,
+    //     {
+    //       id: arr.length + "",
+    //       content: <GenerateBubble />,
+    //       status: "done",
+    //       role: "assistant",
+    //     },
+    //   ];
+    // });
+  };
+
+  return (
+    <Space className="mb-12px">
+      <Button type="default" onClick={props.onNext}>
+        下一步
+      </Button>
+      <span className="text-[#1890ff]">
+        你可以继续对上述功能进行修改,点击【下一步】进入下一阶段
+      </span>
+    </Space>
+  );
+};
+
+export default BubbleFooter;

+ 609 - 0
apps/ai-easy-builder/src/pages/home/components/GenerateApp.tsx

@@ -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>
+    </>
+  );
+}

+ 38 - 7
apps/ai-easy-builder/src/pages/home/components/GenerateBubble.tsx

@@ -1,4 +1,4 @@
-import { useMemo } from "react";
+import { useEffect, useMemo, useState } from "react";
 import { Steps } from "antd";
 import type { StepsProps } from "antd/es/steps";
 import { LoadingOutlined } from "@ant-design/icons";
@@ -7,16 +7,16 @@ import Flowchart from "./Flowchart";
 import Mindmap from "./Mindmap";
 
 export default function GenerateBubble() {
-  const stepItems: StepsProps["items"] = useMemo(() => {
-    return [
+  const [step, setStep] = useState(0);
+  const stepItems = useMemo(() => {
+    const items = [
       {
         title: "系统设计",
-        status: "process",
-        icon: <LoadingOutlined />,
+        status: "finish",
       },
       {
         title: "数据模型",
-        status: "wait",
+        status: "process",
       },
       {
         title: "页面设计",
@@ -31,7 +31,38 @@ export default function GenerateBubble() {
         status: "wait",
       },
     ];
-  }, []);
+
+    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]);
+
+  useEffect(() => {
+    setTimeout(() => {
+      setStep((state) => state + 1);
+    }, 4000);
+  }, [])
   return (
     <div className="w-full h-500px flex">
       <div className="w-200px">

+ 491 - 0
apps/ai-easy-builder/src/pages/home/index copy.tsx

@@ -0,0 +1,491 @@
+import {
+  Bubble,
+  Prompts,
+  Sender,
+  Suggestion,
+  XProvider,
+  Welcome,
+  Attachments,
+  AttachmentsProps,
+} from "@ant-design/x";
+import { useChat } from "@/hooks/useChat";
+import "./index.less";
+
+import {
+  Card,
+  Flex,
+  message,
+  Button,
+  Space,
+  Spin,
+  Typography,
+  Tooltip,
+} from "antd";
+import { useEffect, useRef, useState } from "react";
+import {
+  BulbOutlined,
+  SmileOutlined,
+  UserOutlined,
+  CloudUploadOutlined,
+  LinkOutlined,
+  CopyOutlined,
+  RedoOutlined,
+  ReadOutlined,
+  FireOutlined,
+  ClearOutlined,
+} from "@ant-design/icons";
+import type { GetProp, GetRef } from "antd";
+import MarkdownViewer from "@/components/ai/MarkdownViewer";
+import GenerateBubble from "./components/GenerateBubble";
+
+// bubbles角色配置
+const roles: GetProp<typeof Bubble.List, "roles"> = {
+  assistant: {
+    placement: "start",
+    avatar: {
+      icon: <i className="iconfont icon-AI1" />,
+      style: { background: "#3450ec" },
+    },
+    // typing: { step: 5, interval: 20 },
+    loadingRender: () => (
+      <Space>
+        <Spin size="small" />
+        思考中...
+      </Space>
+    ),
+    messageRender: (content) => {
+      return typeof content === "string" ? (
+        <Typography className={content?.includes("```") ? "w-full" : ""}>
+          <MarkdownViewer content={content} />
+        </Typography>
+      ) : (
+        content
+      );
+    },
+    header: "易码工坊AI助手",
+    styles: {
+      content: {
+        background: "#fff",
+        width: "100%",
+        border: "1px solid #e2ecfc",
+        boxShadow: "0 0 4px rgba(0, 0, 0, 0.12)",
+      },
+    },
+  },
+  user: {
+    placement: "start",
+    avatar: { icon: <UserOutlined />, style: { background: "#87d068" } },
+    messageRender: (content) => {
+      return <div style={{ whiteSpace: "pre-wrap" }}>{content}</div>;
+    },
+    styles: {
+      content: {
+        background: "#e2ecfc",
+        border: "1px solid #e2ecfc",
+        boxShadow: "0 0 4px rgba(0, 0, 0, 0.12)",
+      },
+    },
+  },
+};
+
+export default () => {
+  const [senderVal, setSenderVal] = useState("");
+  const {
+    messages,
+    setMessages,
+    activeConversation,
+    changeConversation,
+    conversationList,
+    onRequest,
+    cancel,
+    loading,
+    loadingSession,
+    addConversation,
+    setConversationList,
+  } = useChat({
+    app_name: "app_generate",
+    onSuccess: (msg) => {
+      setMessages((messages) => {
+        const arr = [...messages];
+        const query = arr[messages.length - 2].content as string;
+        arr[messages.length - 1].status = "done";
+        arr[messages.length - 1].footer = (
+          <BubbleFooter
+            content={arr[messages.length - 1].content as string}
+            query={query}
+          />
+        );
+        return arr;
+      });
+      setTimeout(() => {
+        const scrollHeight = listRef.current?.nativeElement?.scrollHeight;
+        listRef.current?.nativeElement?.scrollTo({
+          top: scrollHeight,
+          behavior: "smooth",
+        });
+      }, 200);
+    },
+    onUpdate: (msg) => {
+      setMessages((messages) => {
+        const arr = [...messages];
+        arr.forEach((item) => {
+          if (item.footer) {
+            item.footer = null;
+          }
+        });
+        arr[messages.length - 1].content += msg.answer;
+        arr[messages.length - 1].id = msg.message_id;
+        arr[messages.length - 1].loading = false;
+        arr[messages.length - 1].status = "error";
+        return arr;
+      });
+    },
+    onError: (error) => {
+      message.error(error.message);
+      setMessages((messages) => {
+        const arr = [...messages];
+        arr[messages.length - 1].content = (
+          <Typography.Text type="danger">{error.message}</Typography.Text>
+        );
+        arr[messages.length - 1].status = "error";
+        arr[messages.length - 1].loading = false;
+        return arr;
+      });
+    },
+  });
+
+  const [openAttachment, setOpenAttachment] = useState(false);
+  const attachmentsRef = useRef<GetRef<typeof Attachments>>(null);
+  const senderRef = useRef<GetRef<typeof Sender>>(null);
+  const [attachmentItems, setAttachmentItems] = useState<
+    GetProp<AttachmentsProps, "items">
+  >([]);
+  const contentRef = useRef<HTMLDivElement>(null);
+  const [contentHeight, setContentHeight] = useState(0);
+
+  const setHeight = () => {
+    setContentHeight(contentRef.current?.clientHeight || 0);
+  };
+  useEffect(() => {
+    setHeight();
+    const resizeObserver = new ResizeObserver(() => {
+      setHeight();
+    });
+    resizeObserver.observe(contentRef.current!);
+    return () => {
+      resizeObserver.disconnect();
+    };
+  }, []);
+  const listRef = useRef<GetRef<typeof Bubble.List>>(null);
+
+  // 附件组件
+  const senderHeader = (
+    <Sender.Header
+      title="附件"
+      styles={{
+        content: {
+          padding: 0,
+        },
+      }}
+      open={openAttachment}
+      onOpenChange={setOpenAttachment}
+      forceRender
+    >
+      <Attachments
+        ref={attachmentsRef}
+        beforeUpload={() => false}
+        items={attachmentItems}
+        onChange={({ fileList }) => setAttachmentItems(fileList)}
+        placeholder={(type) =>
+          type === "drop"
+            ? {
+                title: "拖拽文件到这里",
+              }
+            : {
+                icon: <CloudUploadOutlined />,
+                title: "文件列表",
+                description: "点击或者拖拽文件到这里上传",
+              }
+        }
+        getDropContainer={() => senderRef.current?.nativeElement}
+      />
+    </Sender.Header>
+  );
+
+  // 底部组件
+  const BubbleFooter = (props: { content: string; query: string }) => {
+    const handleCopy = () => {
+      navigator.clipboard.writeText(props.content);
+      message.success("复制成功");
+    };
+
+    const handleGenerateApp = () => {
+      // todo 生成应用
+      // message.info("正在开发中,敬请期待...");
+      setMessages((arr) => {
+        return [
+          ...arr,
+          {
+            id: arr.length + "",
+            content: <GenerateBubble />,
+            status: "done",
+            role: "assistant",
+          },
+        ];
+      })
+    };
+
+    return (
+      <Space>
+        <Button type="default" onClick={handleGenerateApp}>
+          去生成应用
+        </Button>
+        <span className="text-[#1890ff]">
+          你可以继续对上述功能进行修改,点击【去生成应用】即可创建应用
+        </span>
+      </Space>
+    );
+  };
+
+  // 提交消息
+  const submitMessage = (msg: string) => {
+    setSenderVal("");
+
+    setMessages((arr) => {
+      const index = arr.length;
+      return [
+        ...arr,
+        { id: index + "", content: msg, status: "done", role: "user" },
+        {
+          id: index + 1 + "",
+          content: "",
+          status: "loading",
+          role: "assistant",
+          loading: true,
+        },
+      ];
+    });
+    onRequest(msg);
+  };
+
+  // 点击提示词
+  const handlePromptItem = (item: any) => {
+    const msg = item.data.description || item.data.label;
+    const index = messages.length;
+    setMessages([
+      ...messages,
+      { id: index + "", content: msg, status: "done", role: "user" },
+      {
+        id: index + 1 + "",
+        content: "",
+        status: "loading",
+        role: "assistant",
+        loading: true,
+      },
+    ]);
+    onRequest(msg);
+  };
+
+  // 停止对话
+  const handleStop = () => {
+    cancel();
+    setMessages((messages) => {
+      const arr = [...messages];
+      arr[messages.length - 1].status = "stop";
+      arr[messages.length - 1].loading = false;
+      arr[messages.length - 1].footer = (
+        <div>
+          <div className="text-12px text-text-secondary pl-12px">
+            (已停止思考)
+          </div>
+          {/* <BubbleFooter
+            content={arr[messages.length - 1].content as string}
+            query={arr[messages.length - 2].content as string}
+          /> */}
+        </div>
+      );
+      return arr;
+    });
+  };
+
+  return (
+    <>
+      <div
+        className="w-[100vw] h-[100vh] overflow-hidden box-border p-12px pt-30px relative"
+        style={{
+          backgroundImage: "linear-gradient(137deg, #e5f4ff 0%, #efe7ff 100%)",
+        }}
+      >
+        <XProvider direction="ltr">
+          <Flex
+            style={{ height: "100%", maxWidth: 1200, margin: "0 auto" }}
+            gap={12}
+          >
+            <Flex
+              vertical
+              style={{ flex: 1, overflow: "hidden", height: "100%" }}
+              gap={8}
+            >
+              <div
+                className="flex-1 overflow-hidden"
+                ref={contentRef}
+                style={{ height: contentHeight }}
+              >
+                {!messages.length ? (
+                  <>
+                    <div className="mt-20 mb-10 flex items-center justify-center">
+                      <div className="w-50px">
+                        <svg className="icon h-32px w-32px" aria-hidden="true">
+                          <use xlinkHref="#icon-AI1"></use>
+                        </svg>
+                      </div>
+                      <div>
+                        <div className="ai-title">你好,我是易码工坊AI助手</div>
+                        <div className="text-18px text-#666">
+                          说出你的需求,我可以帮你快速生成应用~
+                        </div>
+                      </div>
+                    </div>
+
+                    <Prompts
+                      // title={"✨ 你可以这样问我:"}
+                      items={[
+                        {
+                          key: "1",
+                          icon: <FireOutlined style={{ color: "#FF4D4F" }} />,
+                          label: "创建客户管理系统",
+                          description: "",
+                        },
+                        {
+                          key: "2",
+                          icon: <FireOutlined style={{ color: "#FF4D4F" }} />,
+                          label: "创建资产管理系统",
+                          description: "",
+                        },
+                        {
+                          key: "3",
+                          icon: <FireOutlined style={{ color: "#FF4D4F" }} />,
+                          label: "创建项目管理系统",
+                          description: "",
+                        },
+                        {
+                          key: "4",
+                          icon: <FireOutlined style={{ color: "#FF4D4F" }} />,
+                          label: "创建OA办公自动化管理系统",
+                          description: "",
+                        },
+                      ]}
+                      onItemClick={handlePromptItem}
+                      wrap
+                      styles={{
+                        item: {
+                          flex: "none",
+                          width: "calc(50% - 6px)",
+                        },
+                      }}
+                    />
+                  </>
+                ) : (
+                  <Bubble.List
+                    ref={listRef}
+                    style={{ maxHeight: contentHeight, padding: "0 20px" }}
+                    autoScroll
+                    roles={roles}
+                    items={messages}
+                  />
+                )}
+              </div>
+              <div className="flex items-end">
+                <div className="w-60px pb-8px shrink-0">
+                  <Tooltip title="清空对话">
+                    <Button
+                      shape="circle"
+                      size="large"
+                      icon={<ClearOutlined />}
+                      onClick={() => {
+                        addConversation();
+                      }}
+                    />
+                  </Tooltip>
+                </div>
+                <div className="flex-1">
+                  <Suggestion
+                    items={[
+                      { label: "创建客户管理系统", value: "report" },
+                      { label: "创建资产管理系统", value: "report" },
+                      { label: "创建客户管理系统", value: "report" },
+                      { label: "创建客户管理系统", value: "report" },
+                    ]}
+                    onSelect={submitMessage}
+                    className="relative overflow-hidden"
+                  >
+                    {({ onTrigger, onKeyDown }) => {
+                      return (
+                        <>
+                          <div className="sender-wrapper" />
+                          <div className="w-full p-1px">
+                            <Sender
+                              submitType="shiftEnter"
+                              ref={senderRef}
+                              header={senderHeader}
+                              prefix={
+                                <Button
+                                  type="text"
+                                  icon={<LinkOutlined />}
+                                  onClick={() => {
+                                    setOpenAttachment(!openAttachment);
+                                  }}
+                                />
+                              }
+                              value={senderVal}
+                              onPasteFile={(file) => {
+                                attachmentsRef.current?.upload(file);
+                                setOpenAttachment(true);
+                              }}
+                              onChange={(nextVal) => {
+                                if (nextVal === "/") {
+                                  onTrigger();
+                                } else if (!nextVal) {
+                                  onTrigger(false);
+                                }
+                                setSenderVal(nextVal);
+                              }}
+                              onKeyDown={onKeyDown}
+                              placeholder="请输入应用名称以及需求描述"
+                              onSubmit={submitMessage}
+                              onCancel={handleStop}
+                              actions={(_, info) => {
+                                const { SendButton, LoadingButton } =
+                                  info.components;
+
+                                return (
+                                  <Space size="small">
+                                    <Typography.Text type="secondary">
+                                      <small>`Shift + Enter`提交</small>
+                                    </Typography.Text>
+                                    {loading ? (
+                                      <LoadingButton type="default" disabled />
+                                    ) : (
+                                      <SendButton
+                                        type="primary"
+                                        disabled={false}
+                                      />
+                                    )}
+                                  </Space>
+                                );
+                              }}
+                            />
+                          </div>
+                        </>
+                      );
+                    }}
+                  </Suggestion>
+                </div>
+              </div>
+            </Flex>
+          </Flex>
+        </XProvider>
+      </div>
+    </>
+  );
+};

+ 10 - 6
apps/ai-easy-builder/src/pages/home/index.less

@@ -10,11 +10,12 @@
 
   /* 设置文字颜色为透明以显示背景 */
   color: transparent;
-  font-size: 32px;
-  font-weight: bold;
+  font-size: 48px;
+  // font-weight: bold;
 
   /* 动画设置 */
   animation: gradientAnim 5s linear infinite;
+  margin-bottom: 12px;
 }
 
 @keyframes gradientAnim {
@@ -34,12 +35,12 @@
   // border: none;
   // position: relative;
   border-color: transparent;
-  box-shadow: none;
+  box-shadow: none !important;
 }
 .sender-wrapper {
   --border-size: 1px;
   width: calc(100% - 4px);
-  height: calc(100% - 4px);
+  height: calc(100% - 5px);
   position: absolute;
   top: var(--border-size);
   right: var(--border-size);
@@ -60,8 +61,7 @@
   &::before {
     content: "";
     display: block;
-    background: conic-gradient(from 180deg at 50% 50%,#e92a67 0deg,#a853ba 112.5deg,#2a8af6 228.75deg,rgba(42,138,246,0) 360deg);
-    // box-shadow: 0 0 40px 20px --var(--border-bg);
+    background: conic-gradient(#e92a67,#a853ba,#2a8af6,rgba(42,138,246,1), #e92a67);
     width: calc(100%* 1.41421356237);
     padding-bottom: calc(100%* 1.41421356237);
     position: absolute;
@@ -82,3 +82,7 @@
     transform: translate(-50%, -50%) rotate(0turn);
   }
 }
+
+.prompt-wrapper .ant-prompts-content {
+  flex: none !important;
+}

+ 208 - 195
apps/ai-easy-builder/src/pages/home/index.tsx

@@ -9,9 +9,18 @@ import {
   AttachmentsProps,
 } from "@ant-design/x";
 import { useChat } from "@/hooks/useChat";
-import  "./index.less";
+import "./index.less";
 
-import { Card, Flex, message, Button, Space, Spin, Typography } from "antd";
+import {
+  Card,
+  Flex,
+  message,
+  Button,
+  Space,
+  Spin,
+  Typography,
+  Tooltip,
+} from "antd";
 import { useEffect, useRef, useState } from "react";
 import {
   BulbOutlined,
@@ -23,10 +32,11 @@ import {
   RedoOutlined,
   ReadOutlined,
   FireOutlined,
+  ClearOutlined,
 } from "@ant-design/icons";
 import type { GetProp, GetRef } from "antd";
 import MarkdownViewer from "@/components/ai/MarkdownViewer";
-import GenerateBubble from "./components/GenerateBubble";
+import GenerateApp from "./components/GenerateApp";
 
 // bubbles角色配置
 const roles: GetProp<typeof Bubble.List, "roles"> = {
@@ -57,8 +67,11 @@ const roles: GetProp<typeof Bubble.List, "roles"> = {
       content: {
         background: "#fff",
         width: "100%",
-      }
-    }
+        border: "1px solid #e2ecfc",
+        boxShadow: "0 0 4px rgba(0, 0, 0, 0.12)",
+        marginBottom: 12
+      },
+    },
   },
   user: {
     placement: "start",
@@ -69,8 +82,10 @@ const roles: GetProp<typeof Bubble.List, "roles"> = {
     styles: {
       content: {
         background: "#e2ecfc",
-      }
-    }
+        border: "1px solid #e2ecfc",
+        boxShadow: "0 0 4px rgba(0, 0, 0, 0.12)",
+      },
+    },
   },
 };
 
@@ -95,18 +110,24 @@ export default () => {
         const arr = [...messages];
         const query = arr[messages.length - 2].content as string;
         arr[messages.length - 1].status = "done";
-        arr[messages.length - 1].footer = (
-          <BubbleFooter
-            content={arr[messages.length - 1].content as string}
-            query={query}
-          />
-        );
         return arr;
       });
+      // setTimeout(() => {
+      //   const scrollHeight = listRef.current?.nativeElement?.scrollHeight;
+      //   listRef.current?.nativeElement?.scrollTo({
+      //     top: scrollHeight,
+      //     behavior: "smooth",
+      //   });
+      // }, 200);
     },
     onUpdate: (msg) => {
       setMessages((messages) => {
         const arr = [...messages];
+        arr.forEach((item) => {
+          if (item.footer) {
+            item.footer = null;
+          }
+        });
         arr[messages.length - 1].content += msg.answer;
         arr[messages.length - 1].id = msg.message_id;
         arr[messages.length - 1].loading = false;
@@ -134,22 +155,6 @@ export default () => {
   const [attachmentItems, setAttachmentItems] = useState<
     GetProp<AttachmentsProps, "items">
   >([]);
-  const contentRef = useRef<HTMLDivElement>(null);
-  const [contentHeight, setContentHeight] = useState(0);
-
-  const setHeight = () => {
-    setContentHeight(contentRef.current?.clientHeight || 0);
-  };
-  useEffect(() => {
-    setHeight();
-    const resizeObserver = new ResizeObserver(() => {
-      setHeight();
-    });
-    resizeObserver.observe(contentRef.current!);
-    return () => {
-      resizeObserver.disconnect();
-    };
-  }, []);
 
   // 附件组件
   const senderHeader = (
@@ -185,28 +190,6 @@ export default () => {
     </Sender.Header>
   );
 
-  // 底部组件
-  const BubbleFooter = (props: { content: string; query: string }) => {
-    const handleCopy = () => {
-      navigator.clipboard.writeText(props.content);
-      message.success("复制成功");
-    };
-
-    const handleGenerateApp = () => {
-      // todo 生成应用
-    };
-    return (
-      <Space>
-        <Button
-          type="default"
-          onClick={handleGenerateApp}
-        >
-          去生成应用
-        </Button>
-      </Space>
-    );
-  };
-
   // 提交消息
   const submitMessage = (msg: string) => {
     setSenderVal("");
@@ -271,163 +254,193 @@ export default () => {
   return (
     <>
       <div
-        className="w-[100vw] h-[100vh] overflow-hidden box-border p-12px"
+        className="w-[100vw] h-[100vh] overflow-hidden box-border p-12px relative"
         style={{
           backgroundImage: "linear-gradient(137deg, #e5f4ff 0%, #efe7ff 100%)",
         }}
       >
-        <XProvider direction="ltr">
-          <Flex
-            style={{ height: "100%", maxWidth: 1200, margin: "0 auto" }}
-            gap={12}
-          >
+        {!messages.length ? (
+          <XProvider direction="ltr">
             <Flex
-              vertical
-              style={{ flex: 1, overflow: "hidden", height: "100%" }}
-              gap={8}
+              style={{ height: "100%", maxWidth: 810, margin: "0 auto" }}
+              gap={12}
             >
-              <div
-                className="flex-1 overflow-hidden"
-                ref={contentRef}
-                style={{ height: contentHeight }}
+              <Flex
+                vertical
+                style={{
+                  flex: 1,
+                  overflow: "hidden",
+                  height: "100%",
+                  justifyContent: "center",
+                }}
+                gap={8}
               >
-                {/* {!messages.length ? (
-                  <>
-                    <div className="mt-20 mb-10 flex items-center justify-center">
-                      <div className="w-50px">
-                        <svg className="icon h-32px w-32px" aria-hidden="true">
-                          <use xlinkHref="#icon-AI1"></use>
-                        </svg>
-                      </div>
-                      <div>
-                        <div className="ai-title">
-                          你好,我是易码工坊AI助手
-                        </div>
-                        <div className="text-18px text-#666">
-                          说出你的需求,我可以帮你快速生成应用~
-                        </div>
+                <div
+                  className="overflow-hidden"
+                  // style={{ height: contentHeight }}
+                >
+                  <div className="mb-10 flex items-center justify-center">
+                    <div className="w-50px">
+                      <svg className="icon h-40px w-40px" aria-hidden="true">
+                        <use xlinkHref="#icon-AI1"></use>
+                      </svg>
+                    </div>
+                    <div>
+                      <div className="ai-title">你好,我是易码工坊AI助手</div>
+                      <div className="text-18px text-#666">
+                        说出你的需求,我可以帮你快速创建应用_
                       </div>
                     </div>
-
-                    <Prompts
-                      // title={"✨ 你可以这样问我:"}
-                      items={[
-                        {
-                          key: "1",
-                          icon: <FireOutlined style={{ color: '#FF4D4F' }} />,
-                          label: "创建客户管理系统",
-                          description: ''
-                        },
-                        {
-                          key: "2",
-                          icon: <FireOutlined style={{ color: '#FF4D4F' }} />,
-                          label: "创建资产管理系统",
-                          description: ''
-                        },
-                        {
-                          key: "3",
-                          icon: <FireOutlined style={{ color: '#FF4D4F' }} />,
-                          label: "创建项目管理系统",
-                          description: ''
-                        },
-                        {
-                          key: "4",
-                          icon: <FireOutlined style={{ color: '#FF4D4F' }} />,
-                          label: "创建OA办公自动化管理系统",
-                          description: ''
-                        },
-                      ]}
-                      onItemClick={handlePromptItem}
-                      wrap
-                      styles={{
-                        item: {
-                          flex: "none",
-                          width: "calc(50% - 6px)",
-                        },
-                      }}
-                    />
-                  </>
-                ) : (
-                  <Bubble.List
-                    style={{ maxHeight: contentHeight, padding: "0 20px" }}
-                    autoScroll
-                    roles={roles}
-                    items={messages}
-                  />
-                )} */}
-                <GenerateBubble/>
-              </div>
-              <Suggestion
-                items={[
-                  { label: "创建客户管理系统", value: "report" },
-                  { label: "创建资产管理系统", value: "report" },
-                  { label: "创建客户管理系统", value: "report" },
-                  { label: "创建客户管理系统", value: "report" },
-                ]}
-                onSelect={submitMessage}
-                className="relative overflow-hidden"
-              >
-                {({ onTrigger, onKeyDown }) => {
-                  return (
-                    <>
-                    <div className="sender-wrapper"/>
+                  </div>
+                </div>
+                <div className="flex items-end">
+                  <div className="flex-1 relative">
+                    <div className="sender-wrapper" />
                     <div className="w-full p-1px">
-                    <Sender
-                      submitType="shiftEnter"
-                      ref={senderRef}
-                      header={senderHeader}
-                      prefix={
-                        <Button
-                          type="text"
-                          icon={<LinkOutlined />}
-                          onClick={() => {
-                            setOpenAttachment(!openAttachment);
-                          }}
-                        />
-                      }
-                      value={senderVal}
-                      onPasteFile={(file) => {
-                        attachmentsRef.current?.upload(file);
-                        setOpenAttachment(true);
-                      }}
-                      onChange={(nextVal) => {
-                        if (nextVal === "/") {
-                          onTrigger();
-                        } else if (!nextVal) {
-                          onTrigger(false);
+                      <Sender
+                        submitType="enter"
+                        ref={senderRef}
+                        header={senderHeader}
+                        prefix={
+                          <Button
+                            type="text"
+                            icon={<LinkOutlined />}
+                            onClick={() => {
+                              setOpenAttachment(!openAttachment);
+                            }}
+                          />
                         }
-                        setSenderVal(nextVal);
-                      }}
-                      onKeyDown={onKeyDown}
-                      placeholder="请输入应用名称以及需求描述"
-                      onSubmit={submitMessage}
-                      onCancel={handleStop}
-                      actions={(_, info) => {
-                        const { SendButton, LoadingButton, ClearButton } = info.components;
-                
-                        return (
-                          <Space size="small">
-                            <Typography.Text type="secondary">
-                              <small>`Shift + Enter`提交</small>
-                            </Typography.Text>
-                            <ClearButton />
-                            {loading ? (
-                              <LoadingButton type="default" disabled />
-                            ) : (
-                              <SendButton type="primary" disabled={false} />
-                            )}
-                          </Space>
-                        );
-                      }}
-                    />
+                        value={senderVal}
+                        onPasteFile={(file) => {
+                          attachmentsRef.current?.upload(file);
+                          setOpenAttachment(true);
+                        }}
+                        placeholder="请输入应用名称、需求描述,按`Shift + Enter`换行"
+                        onChange={(nextVal) => {
+                          setSenderVal(nextVal);
+                        }}
+                        onSubmit={submitMessage}
+                        onCancel={handleStop}
+                        autoSize={{ minRows: 3, maxRows: 6 }}
+                        actions={(_, info) => {
+                          const { SendButton, LoadingButton } = info.components;
+
+                          return (
+                            <Space size="small">
+                              <Typography.Text type="secondary">
+                                <small>`Shift + Enter`提交</small>
+                              </Typography.Text>
+                              {loading ? (
+                                <LoadingButton type="default" disabled />
+                              ) : (
+                                <SendButton type="primary" disabled={false} />
+                              )}
+                            </Space>
+                          );
+                        }}
+                      />
                     </div>
-                    </>
-                  );
-                }}
-              </Suggestion>
+                  </div>
+                </div>
+                <Prompts
+                  // title={"✨ 你可以这样问我:"}
+                  className="mt-10 prompt-wrapper"
+                  items={[
+                    {
+                      key: "1",
+                      icon: <FireOutlined style={{ color: "#FF4D4F" }} />,
+                      label: "创建客户管理系统",
+                      description: "",
+                    },
+                    {
+                      key: "2",
+                      icon: <FireOutlined style={{ color: "#FF4D4F" }} />,
+                      label: "创建资产管理系统",
+                      description: "",
+                    },
+                    {
+                      key: "3",
+                      icon: <FireOutlined style={{ color: "#FF4D4F" }} />,
+                      label: "创建项目管理系统",
+                      description: "",
+                    },
+                    {
+                      key: "4",
+                      icon: <FireOutlined style={{ color: "#FF4D4F" }} />,
+                      label: "创建OA办公自动化管理系统",
+                      description: "",
+                    },
+                  ]}
+                  onItemClick={handlePromptItem}
+                  wrap
+                  styles={{
+                    item: {
+                      flex: "none",
+                      width: "calc(50% - 6px)",
+                      backgroundImage: `linear-gradient(90deg, #e5f4ff 0%, #efe7ff 100%)`,
+                      border: "1px solid #FFF",
+                      justifyContent: "center",
+                    },
+                  }}
+                />
+              </Flex>
             </Flex>
-          </Flex>
-        </XProvider>
+          </XProvider>
+        ) : (
+          <GenerateApp
+            roles={roles}
+            messages={messages}
+            sender={
+              <div className="flex-1 relative">
+                <div className="sender-wrapper" />
+                <div className="w-full p-1px">
+                  <Sender
+                    submitType="enter"
+                    ref={senderRef}
+                    header={senderHeader}
+                    prefix={
+                      <Button
+                        type="text"
+                        icon={<LinkOutlined />}
+                        onClick={() => {
+                          setOpenAttachment(!openAttachment);
+                        }}
+                      />
+                    }
+                    value={senderVal}
+                    onPasteFile={(file) => {
+                      attachmentsRef.current?.upload(file);
+                      setOpenAttachment(true);
+                    }}
+                    placeholder="请输入应用名称、需求描述,按`Shift + Enter`换行"
+                    onChange={(nextVal) => {
+                      setSenderVal(nextVal);
+                    }}
+                    onSubmit={submitMessage}
+                    onCancel={handleStop}
+                    autoSize={{ minRows: 3, maxRows: 6 }}
+                    actions={(_, info) => {
+                      const { SendButton, LoadingButton } = info.components;
+
+                      return (
+                        <Space size="small">
+                          <Typography.Text type="secondary">
+                            <small>`Shift + Enter`提交</small>
+                          </Typography.Text>
+                          {loading ? (
+                            <LoadingButton type="default" disabled />
+                          ) : (
+                            <SendButton type="primary" disabled={false} />
+                          )}
+                        </Space>
+                      );
+                    }}
+                  />
+                </div>
+              </div>
+            }
+          />
+        )}
       </div>
     </>
   );

+ 16 - 1
apps/ai-easy-builder/unocss.config.ts

@@ -3,9 +3,24 @@ import {defineConfig, presetAttributify, presetUno} from 'unocss';
 export function createConfig({strict = true, dev = true} = {}) {
   return defineConfig({
     envMode: dev ? 'dev' : 'build', presets: [presetAttributify({strict}), presetUno()],
+    theme: {
+      colors: {
+        'primary': '#0e53e2',
+        'secondary': '#495464',
+        'text-secondary': '#354052',
+        'text-tertiary': '#676f83',
+        'text-quaternary': '#1018284d'
+      }
+    },
     rules: [
       ['flex-important', {display: 'flex !important'}],
-    ]
+    ],
+    shortcuts: {
+      'flex-center': 'flex justify-center items-center',
+      'nav-button': 'cursor-pointer flex items-center text-secondary h-8 mr-0 sm:mr-3 px-3 h-8 rounded-xl text-sm shrink-0 font-medium false hover:bg-[#fff]',
+      'nav-button-active': 'shadow-md bg-[#fff] text-primary rounded-xl font-medium text-sm hover:bg-[#fff]',
+      'system-xs-regular': 'text-12px font-400 leading-16px'
+    }
   });
 }
 

+ 1 - 1
package.json

@@ -20,7 +20,7 @@
   "dependencies": {
     "@ant-design/icons": "^5.6.1",
     "@ant-design/pro-components": "^2.8.6",
-    "@ant-design/x": "^1.0.5",
+    "@ant-design/x": "^1.1.0",
     "@antv/hierarchy": "^0.6.14",
     "@antv/x6": "^2.18.1",
     "@antv/x6-plugin-clipboard": "^2.1.6",

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 674 - 236
pnpm-lock.yaml