|
|
@@ -0,0 +1,166 @@
|
|
|
+import React, {useRef, useState} from "react";
|
|
|
+import EDropdownSelect from "@/components/EDropdownSelect/EDropdownSelect";
|
|
|
+import styles from "./AIChatter.module.scss"
|
|
|
+import {Avatar, Button, Dropdown, Input, Menu, MenuProps, Spin} from "antd";
|
|
|
+import {ArrowUpOutlined, DownOutlined} from "@ant-design/icons";
|
|
|
+
|
|
|
+const AIChatter = () => {
|
|
|
+ const modelOptions = ["Seecoder AI", "ChatGPT3.5", "ChatGLM3"]
|
|
|
+ const AIAvatar = "https://cdn.pixabay.com/photo/2023/11/14/11/07/sparrow-8387465_1280.jpg"
|
|
|
+ const userAvatar = "https://cdn.pixabay.com/photo/2023/07/22/08/54/european-shorthair-8142959_1280.jpg"
|
|
|
+ const questionTemplates = [
|
|
|
+ "请问您提供哪些服务?",
|
|
|
+ "您的营业时间是?",
|
|
|
+ "我想了解更多关于产品A的信息。",
|
|
|
+ // 更多问题模板...
|
|
|
+ ];
|
|
|
+
|
|
|
+ const items: MenuProps['items'] = questionTemplates.map((template, index) => ({
|
|
|
+ key: index,
|
|
|
+ label: (
|
|
|
+ <div className={"option-item"}>{template}</div>
|
|
|
+ )
|
|
|
+ }))
|
|
|
+
|
|
|
+ const inputRef = useRef<any>(null); // 创建引用
|
|
|
+
|
|
|
+
|
|
|
+ const [data, setData] = useState({
|
|
|
+ model: modelOptions[0],
|
|
|
+ questionContent: '',
|
|
|
+ messages: [
|
|
|
+ {
|
|
|
+ "content": "你好,你是什么AI工具?",
|
|
|
+ "senderType": "User",
|
|
|
+ "timestamp": new Date("2024-02-29 18:00:00")
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "content": "我是Seecoder AI,是南京大学软件学院Seecoder组的AI制品,服务于软件工程教育、英文写作等工作!",
|
|
|
+ "senderType": "AI",
|
|
|
+ "timestamp": new Date("2024-02-29 18:01:10")
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "content": "你好,你是什么AI工具?",
|
|
|
+ "senderType": "User",
|
|
|
+ "timestamp": new Date("2024-02-29 18:00:00")
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "content": "我是Seecoder AI,是南京大学软件学院Seecoder组的AI制品,服务于软件工程教育、英文写作等工作!",
|
|
|
+ "senderType": "AI",
|
|
|
+ "timestamp": new Date("2024-02-29 18:01:10")
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "content": "你好,你是什么AI工具?",
|
|
|
+ "senderType": "User",
|
|
|
+ "timestamp": new Date("2024-02-29 18:00:00")
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "content": "我是Seecoder AI,是南京大学软件学院Seecoder组的AI制品,服务于软件工程教育、英文写作等工作!",
|
|
|
+ "senderType": "AI",
|
|
|
+ "timestamp": new Date("2024-02-29 18:01:10")
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "content": "你好,你是什么AI工具?",
|
|
|
+ "senderType": "User",
|
|
|
+ "timestamp": new Date("2024-02-29 18:00:00")
|
|
|
+ },
|
|
|
+ {
|
|
|
+ "content": "我是Seecoder AI,是南京大学软件学院Seecoder组的AI制品,服务于软件工程教育、英文写作等工作!",
|
|
|
+ "senderType": "AI",
|
|
|
+ "timestamp": new Date("2024-02-29 18:01:10")
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ loading: false, // 请求加载过程
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新选中值的函数
|
|
|
+ const setValue = (model: string) => {
|
|
|
+ console.log("父组件中接受到的值", model)
|
|
|
+ setData(prev => ({ ...prev, model }));
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSendQuestion = () => {
|
|
|
+ setData(prev => ({...prev, loading: true}))
|
|
|
+ let question = data.questionContent;
|
|
|
+ console.log("发送请求,请求参数为", question);
|
|
|
+ setData(prev => ({...prev, questionContent: ''}))
|
|
|
+ // TODO 请求方法,设置loading
|
|
|
+ setTimeout(() => {
|
|
|
+ setData(prev => ({...prev, loading: false}))
|
|
|
+ }, 3000)
|
|
|
+ }
|
|
|
+
|
|
|
+ const handleMenuClick = ({ key }: any) => {
|
|
|
+ setData(prev => ({...prev, questionContent: questionTemplates[key]}))
|
|
|
+ inputRef.current!.focus(); // 设置焦点自动回到Input
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className={styles.container}>
|
|
|
+ <div className={styles.header}>
|
|
|
+ <div className={styles["model-selector"]}>
|
|
|
+ <EDropdownSelect
|
|
|
+ options={modelOptions}
|
|
|
+ value={data.model}
|
|
|
+ setValue={setValue}
|
|
|
+ ></EDropdownSelect>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div className={styles["message-box"]}>
|
|
|
+ {
|
|
|
+ data.messages.map((item, index) => (
|
|
|
+ <div className={styles["item"]} key={index}>
|
|
|
+ <div>
|
|
|
+ <Avatar
|
|
|
+ size={24}
|
|
|
+ shape={"circle"}
|
|
|
+ src={index%2 === 0 && item.senderType === "User"?userAvatar: AIAvatar}></Avatar>
|
|
|
+ </div>
|
|
|
+ <div style={{marginLeft: "15px"}}>
|
|
|
+ <div>{item.senderType==="User"? "You": data.model}</div>
|
|
|
+ <div className={styles["content"]}>{item.content}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ </div>
|
|
|
+ ))
|
|
|
+ }
|
|
|
+ </div>
|
|
|
+ <div className={styles["question-box"]}>
|
|
|
+ <div className={styles["input-group"]}>
|
|
|
+ <Input
|
|
|
+ className={styles["input"]}
|
|
|
+ ref={inputRef}
|
|
|
+ variant={"borderless"}
|
|
|
+ size={"large"}
|
|
|
+ placeholder={"Message "+data.model+" ..."}
|
|
|
+ value={data.questionContent}
|
|
|
+ onChange={(e) => setData(prev => ({...prev, questionContent: e.target.value}))}
|
|
|
+ />
|
|
|
+ {data.loading && <Spin style={{marginRight: "10px"}}/>}
|
|
|
+ {
|
|
|
+ !data.loading &&
|
|
|
+ <Button
|
|
|
+ className={styles["button"]}
|
|
|
+ type={"text"}
|
|
|
+ disabled={data.questionContent === ''}
|
|
|
+ onClick={handleSendQuestion}
|
|
|
+ >
|
|
|
+ <ArrowUpOutlined/>
|
|
|
+ </Button>
|
|
|
+ }
|
|
|
+ <Dropdown
|
|
|
+ overlay={<Menu items={items} onClick={handleMenuClick}/>}
|
|
|
+ trigger={['click']}
|
|
|
+ >
|
|
|
+ <Button type={"text"} className={styles["button"]}>
|
|
|
+ <DownOutlined/>
|
|
|
+ </Button>
|
|
|
+ </Dropdown>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+export default AIChatter
|