|
@@ -9,9 +9,18 @@ import {
|
|
AttachmentsProps,
|
|
AttachmentsProps,
|
|
} from "@ant-design/x";
|
|
} from "@ant-design/x";
|
|
import { useChat } from "@/hooks/useChat";
|
|
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 { useEffect, useRef, useState } from "react";
|
|
import {
|
|
import {
|
|
BulbOutlined,
|
|
BulbOutlined,
|
|
@@ -23,10 +32,11 @@ import {
|
|
RedoOutlined,
|
|
RedoOutlined,
|
|
ReadOutlined,
|
|
ReadOutlined,
|
|
FireOutlined,
|
|
FireOutlined,
|
|
|
|
+ ClearOutlined,
|
|
} from "@ant-design/icons";
|
|
} from "@ant-design/icons";
|
|
import type { GetProp, GetRef } from "antd";
|
|
import type { GetProp, GetRef } from "antd";
|
|
import MarkdownViewer from "@/components/ai/MarkdownViewer";
|
|
import MarkdownViewer from "@/components/ai/MarkdownViewer";
|
|
-import GenerateBubble from "./components/GenerateBubble";
|
|
|
|
|
|
+import GenerateApp from "./components/GenerateApp";
|
|
|
|
|
|
// bubbles角色配置
|
|
// bubbles角色配置
|
|
const roles: GetProp<typeof Bubble.List, "roles"> = {
|
|
const roles: GetProp<typeof Bubble.List, "roles"> = {
|
|
@@ -57,8 +67,11 @@ const roles: GetProp<typeof Bubble.List, "roles"> = {
|
|
content: {
|
|
content: {
|
|
background: "#fff",
|
|
background: "#fff",
|
|
width: "100%",
|
|
width: "100%",
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ border: "1px solid #e2ecfc",
|
|
|
|
+ boxShadow: "0 0 4px rgba(0, 0, 0, 0.12)",
|
|
|
|
+ marginBottom: 12
|
|
|
|
+ },
|
|
|
|
+ },
|
|
},
|
|
},
|
|
user: {
|
|
user: {
|
|
placement: "start",
|
|
placement: "start",
|
|
@@ -69,8 +82,10 @@ const roles: GetProp<typeof Bubble.List, "roles"> = {
|
|
styles: {
|
|
styles: {
|
|
content: {
|
|
content: {
|
|
background: "#e2ecfc",
|
|
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 arr = [...messages];
|
|
const query = arr[messages.length - 2].content as string;
|
|
const query = arr[messages.length - 2].content as string;
|
|
arr[messages.length - 1].status = "done";
|
|
arr[messages.length - 1].status = "done";
|
|
- arr[messages.length - 1].footer = (
|
|
|
|
- <BubbleFooter
|
|
|
|
- content={arr[messages.length - 1].content as string}
|
|
|
|
- query={query}
|
|
|
|
- />
|
|
|
|
- );
|
|
|
|
return arr;
|
|
return arr;
|
|
});
|
|
});
|
|
|
|
+ // setTimeout(() => {
|
|
|
|
+ // const scrollHeight = listRef.current?.nativeElement?.scrollHeight;
|
|
|
|
+ // listRef.current?.nativeElement?.scrollTo({
|
|
|
|
+ // top: scrollHeight,
|
|
|
|
+ // behavior: "smooth",
|
|
|
|
+ // });
|
|
|
|
+ // }, 200);
|
|
},
|
|
},
|
|
onUpdate: (msg) => {
|
|
onUpdate: (msg) => {
|
|
setMessages((messages) => {
|
|
setMessages((messages) => {
|
|
const arr = [...messages];
|
|
const arr = [...messages];
|
|
|
|
+ arr.forEach((item) => {
|
|
|
|
+ if (item.footer) {
|
|
|
|
+ item.footer = null;
|
|
|
|
+ }
|
|
|
|
+ });
|
|
arr[messages.length - 1].content += msg.answer;
|
|
arr[messages.length - 1].content += msg.answer;
|
|
arr[messages.length - 1].id = msg.message_id;
|
|
arr[messages.length - 1].id = msg.message_id;
|
|
arr[messages.length - 1].loading = false;
|
|
arr[messages.length - 1].loading = false;
|
|
@@ -134,22 +155,6 @@ export default () => {
|
|
const [attachmentItems, setAttachmentItems] = useState<
|
|
const [attachmentItems, setAttachmentItems] = useState<
|
|
GetProp<AttachmentsProps, "items">
|
|
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 = (
|
|
const senderHeader = (
|
|
@@ -185,28 +190,6 @@ export default () => {
|
|
</Sender.Header>
|
|
</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) => {
|
|
const submitMessage = (msg: string) => {
|
|
setSenderVal("");
|
|
setSenderVal("");
|
|
@@ -271,163 +254,193 @@ export default () => {
|
|
return (
|
|
return (
|
|
<>
|
|
<>
|
|
<div
|
|
<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={{
|
|
style={{
|
|
backgroundImage: "linear-gradient(137deg, #e5f4ff 0%, #efe7ff 100%)",
|
|
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
|
|
<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>
|
|
</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">
|
|
<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>
|
|
</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>
|
|
- </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>
|
|
</div>
|
|
</>
|
|
</>
|
|
);
|
|
);
|