18586361686
2025-04-15 313e153e20b357a5d3b29414995d2f2e1ab78089
feat: Add chat front and backend
9个文件已修改
6个文件已添加
353 ■■■■ 已修改文件
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/controller/AiBotController.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/controller/AiBotMessageController.java 16 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/entity/AiBotConversationMessage.java 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/entity/AiBotMessageMemory.java 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/entity/base/AiBotConversationBase.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/entity/base/AiBotConversationMessageBase.java 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/mapper/AiBotConversationMessageMapper.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/service/AiBotConversationMessageService.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/service/AiBotMessageService.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/service/impl/AiBotConversationMessageServiceImpl.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/service/impl/AiBotMessageServiceImpl.java 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-ui-react/src/pages/ExternalBot.tsx 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-ui-react/src/pages/ai/aiKnowledge/FileImportPanel.tsx 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-ui-react/src/pages/ai/botDesign/BotDesign.tsx 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-ui-react/src/routers/router.tsx 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/controller/AiBotController.java
@@ -1,6 +1,7 @@
package tech.aiflowy.ai.controller;
import tech.aiflowy.ai.entity.*;
import tech.aiflowy.ai.mapper.AiBotConversationMessageMapper;
import tech.aiflowy.ai.service.*;
import tech.aiflowy.common.ai.ChatManager;
import tech.aiflowy.common.ai.MySseEmitter;
@@ -50,7 +51,10 @@
    private final AiBotWorkflowService aiBotWorkflowService;
    private final AiBotKnowledgeService aiBotKnowledgeService;
    private final AiBotMessageService aiBotMessageService;
    @Resource
    private AiBotConversationMessageService aiBotConversationMessageService;
    @Resource
    private AiBotConversationMessageMapper aiBotConversationMessageMapper;
    public AiBotController(AiBotService service, AiLlmService aiLlmService, AiBotWorkflowService aiBotWorkflowService, AiBotKnowledgeService aiBotKnowledgeService, AiBotMessageService aiBotMessageService) {
        super(service);
        this.aiLlmService = aiLlmService;
@@ -116,7 +120,9 @@
        Llm llm = aiLlm.toLlm();
        AiBotMessageMemory memory = new AiBotMessageMemory(botId, SaTokenUtil.getLoginAccount().getId(), sessionId, aiBotMessageService);
        AiBotMessageMemory memory = new AiBotMessageMemory(botId, SaTokenUtil.getLoginAccount().getId(),
                sessionId, aiBotMessageService, aiBotConversationMessageMapper,
                aiBotConversationMessageService);
        final HistoriesPrompt historiesPrompt = new HistoriesPrompt();
        historiesPrompt.setSystemMessage(SystemMessage.of((String) llmOptions.get("systemPrompt")));
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/controller/AiBotMessageController.java
@@ -1,5 +1,6 @@
package tech.aiflowy.ai.controller;
import org.springframework.web.bind.annotation.RequestParam;
import tech.aiflowy.ai.entity.AiBotMessage;
import tech.aiflowy.ai.service.AiBotMessageService;
import tech.aiflowy.common.domain.Result;
@@ -11,7 +12,9 @@
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import tech.aiflowy.common.web.jsonbody.JsonBody;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List;
@@ -24,8 +27,11 @@
@RestController
@RequestMapping("/api/v1/aiBotMessage")
public class AiBotMessageController extends BaseCurdController<AiBotMessageService, AiBotMessage> {
    public AiBotMessageController(AiBotMessageService service) {
    private final AiBotMessageService aiBotMessageService;
    public AiBotMessageController(AiBotMessageService service, AiBotMessageService aiBotMessageService) {
        super(service);
        this.aiBotMessageService = aiBotMessageService;
    }
@@ -33,7 +39,7 @@
    @Override
    public Result list(AiBotMessage entity, Boolean asTree, String sortKey, String sortType) {
        if (entity.getBotId() == null || StringUtil.noText(entity.getSessionId())){
        if (entity.getBotId() == null || StringUtil.noText(entity.getSessionId())) {
            return Result.fail();
        }
@@ -67,4 +73,10 @@
        entity.setAccountId(SaTokenUtil.getLoginAccount().getId());
        return super.onSaveOrUpdateBefore(entity, isSave);
    }
    @GetMapping("externalList")
    public Result externalList(@RequestParam(value = "botId", required = true) BigInteger botId) {
    return aiBotMessageService.externalList(botId);
    }
}
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/entity/AiBotConversationMessage.java
New file
@@ -0,0 +1,15 @@
package tech.aiflowy.ai.entity;
import com.mybatisflex.annotation.Table;
import tech.aiflowy.ai.entity.base.AiBotConversationMessageBase;
/**
 *  实体类。
 *
 * @author Administrator
 * @since 2025-04-15
 */
@Table("tb_ai_bot_conversation_message")
public class AiBotConversationMessage extends AiBotConversationMessageBase {
}
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/entity/AiBotMessageMemory.java
@@ -1,5 +1,7 @@
package tech.aiflowy.ai.entity;
import tech.aiflowy.ai.mapper.AiBotConversationMessageMapper;
import tech.aiflowy.ai.service.AiBotConversationMessageService;
import tech.aiflowy.ai.service.AiBotMessageService;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
@@ -9,7 +11,9 @@
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.mybatisflex.core.query.QueryWrapper;
import tech.aiflowy.common.satoken.util.SaTokenUtil;
import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Date;
@@ -20,12 +24,17 @@
    private final BigInteger accountId;
    private final String sessionId;
    private final AiBotMessageService messageService;
    public AiBotMessageMemory(BigInteger botId, BigInteger accountId, String sessionId, AiBotMessageService messageService) {
    private final AiBotConversationMessageMapper aiBotConversationMessageMapper;
    private final AiBotConversationMessageService aiBotConversationService;
    public AiBotMessageMemory(BigInteger botId, BigInteger accountId, String sessionId, AiBotMessageService messageService,
                              AiBotConversationMessageMapper aiBotConversationMessageMapper,
                              AiBotConversationMessageService aiBotConversationService ) {
        this.botId = botId;
        this.accountId = accountId;
        this.sessionId = sessionId;
        this.messageService = messageService;
        this.aiBotConversationMessageMapper = aiBotConversationMessageMapper;
        this.aiBotConversationService = aiBotConversationService;
    }
    @Override
@@ -79,6 +88,16 @@
            aiMessage.setContent(((SystemMessage) message).getContent());
        }
        if (StrUtil.isNotEmpty(aiMessage.getContent())) {
            AiBotConversationMessage aiBotConversation = aiBotConversationMessageMapper.selectOneById(aiMessage.getSessionId());
            if (aiBotConversation == null){
                AiBotConversationMessage conversation = new AiBotConversationMessage();
                conversation.setSessionId(aiMessage.getSessionId());
                conversation.setTitle(aiMessage.getContent());
                conversation.setBotId(aiMessage.getBotId());
                conversation.setCreated(new Date());
                conversation.setAccountId(SaTokenUtil.getLoginAccount().getId());
                aiBotConversationService.save(conversation);
            }
            messageService.save(aiMessage);
        }
    }
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/entity/base/AiBotConversationBase.java
New file
@@ -0,0 +1,27 @@
package tech.aiflowy.ai.entity.base;
import java.io.Serializable;
import java.math.BigInteger;
public class AiBotConversationBase implements Serializable {
    private BigInteger sessionId;
    private String title;
    public BigInteger getSessionId() {
        return sessionId;
    }
    public void setSessionId(BigInteger sessionId) {
        this.sessionId = sessionId;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
}
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/entity/base/AiBotConversationMessageBase.java
New file
@@ -0,0 +1,83 @@
package tech.aiflowy.ai.entity.base;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import java.io.Serializable;
import java.math.BigInteger;
import java.util.Date;
public class AiBotConversationMessageBase implements Serializable {
    private static final long serialVersionUID = 1L;
    /**
     * 会话id
     */
    @Id(comment = "会话id")
    private String sessionId;
    /**
     * 会话标题
     */
    @Column(comment = "会话标题")
    private String title;
    /**
     * BotId
     */
    @Column(comment = "BotId")
    private BigInteger BotId;
    /**
     * 创建时间
     */
    @Column(comment = "创建时间")
    private Date created;
    /**
     * 用户id
     */
    @Column(comment = "用户id")
    private BigInteger accountId;
    public String getSessionId() {
        return sessionId;
    }
    public void setSessionId(String sessionId) {
        this.sessionId = sessionId;
    }
    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }
    public BigInteger getBotId() {
        return BotId;
    }
    public void setBotId(BigInteger botId) {
        BotId = botId;
    }
    public Date getCreated() {
        return created;
    }
    public void setCreated(Date created) {
        this.created = created;
    }
    public BigInteger getAccountId() {
        return accountId;
    }
    public void setAccountId(BigInteger accountId) {
        this.accountId = accountId;
    }
}
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/mapper/AiBotConversationMessageMapper.java
New file
@@ -0,0 +1,14 @@
package tech.aiflowy.ai.mapper;
import com.mybatisflex.core.BaseMapper;
import tech.aiflowy.ai.entity.AiBotConversationMessage;
/**
 *  映射层。
 *
 * @author Administrator
 * @since 2025-04-15
 */
public interface AiBotConversationMessageMapper extends BaseMapper<AiBotConversationMessage> {
}
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/service/AiBotConversationMessageService.java
New file
@@ -0,0 +1,14 @@
package tech.aiflowy.ai.service;
import com.mybatisflex.core.service.IService;
import tech.aiflowy.ai.entity.AiBotConversationMessage;
/**
 *  服务层。
 *
 * @author Administrator
 * @since 2025-04-15
 */
public interface AiBotConversationMessageService extends IService<AiBotConversationMessage> {
}
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/service/AiBotMessageService.java
@@ -2,6 +2,9 @@
import tech.aiflowy.ai.entity.AiBotMessage;
import com.mybatisflex.core.service.IService;
import tech.aiflowy.common.domain.Result;
import java.math.BigInteger;
/**
 * Bot 消息记录表 服务层。
@@ -11,4 +14,5 @@
 */
public interface AiBotMessageService extends IService<AiBotMessage> {
    Result externalList(BigInteger botId);
}
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/service/impl/AiBotConversationMessageServiceImpl.java
New file
@@ -0,0 +1,18 @@
package tech.aiflowy.ai.service.impl;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import tech.aiflowy.ai.entity.AiBotConversationMessage;
import tech.aiflowy.ai.mapper.AiBotConversationMessageMapper;
import tech.aiflowy.ai.service.AiBotConversationMessageService;
import org.springframework.stereotype.Service;
/**
 *  服务层实现。
 *
 * @author Administrator
 * @since 2025-04-15
 */
@Service
public class AiBotConversationMessageServiceImpl extends ServiceImpl<AiBotConversationMessageMapper, AiBotConversationMessage>  implements AiBotConversationMessageService{
}
aiflowy-modules/aiflowy-module-ai/src/main/java/tech/aiflowy/ai/service/impl/AiBotMessageServiceImpl.java
@@ -1,10 +1,21 @@
package tech.aiflowy.ai.service.impl;
import com.mybatisflex.core.query.QueryWrapper;
import tech.aiflowy.ai.entity.AiBotConversationMessage;
import tech.aiflowy.ai.entity.AiBotMessage;
import tech.aiflowy.ai.mapper.AiBotMessageMapper;
import tech.aiflowy.ai.service.AiBotMessageService;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import org.springframework.stereotype.Service;
import tech.aiflowy.common.domain.Result;
import tech.aiflowy.common.entity.LoginAccount;
import tech.aiflowy.common.satoken.util.SaTokenUtil;
import javax.annotation.Resource;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
 * Bot 消息记录表 服务层实现。
@@ -15,4 +26,30 @@
@Service
public class AiBotMessageServiceImpl extends ServiceImpl<AiBotMessageMapper, AiBotMessage> implements AiBotMessageService {
    @Resource
    private AiBotMessageMapper aiBotMessageMapper;
    @Override
    public Result externalList(BigInteger botId) {
        LoginAccount loginUser = SaTokenUtil.getLoginAccount();
        BigInteger accountId = loginUser.getId();
        QueryWrapper query = QueryWrapper.create()
                .select("session_id") // 选择字段
                .from("tb_ai_bot_message")
                .where("bot_id = ?", botId)
                .where("account_id = ? ", accountId);
        AiBotMessage aiBotMessage = aiBotMessageMapper.selectOneByQuery(query);
        if (aiBotMessage == null){
            return Result.fail();
        }
        QueryWrapper queryConversation = QueryWrapper.create()
                .select("session_id","title", "bot_id") // 选择字段
                .from("tb_ai_bot_conversation_message")
                .where("bot_id = ?", botId)
                .where("account_id = ? ", accountId);
        List<AiBotConversationMessage> cons = aiBotMessageMapper.selectListByQueryAs(queryConversation, AiBotConversationMessage.class);
        Map<String, Object> result = new HashMap<>();
        result.put("cons", cons);
        return Result.success(result);
    }
}
aiflowy-ui-react/src/pages/ExternalBot.tsx
@@ -9,13 +9,13 @@
import {
    DownOutlined,
    PlusOutlined,
    SmileOutlined,
} from '@ant-design/icons';
import { Button, Dropdown, type GetProp, MenuProps, Space } from 'antd';
import { AiProChat, ChatMessage } from "../components/AiProChat/AiProChat.tsx";
import { getSessionId } from "../libs/getSessionId.ts";
import { useSse } from "../hooks/useSse.ts";
import { useParams } from "react-router-dom";
import {useGet} from "../hooks/useApis.ts";
const defaultConversationsItems = [
    {
@@ -104,17 +104,7 @@
export const ExternalBot: React.FC = () => {
    const modelItems: MenuProps['items'] = [
        {
            key: '1',
            label: '通义千问',
        },
        {
            key: '2',
            label: '星火大模型',
            icon: <SmileOutlined />
        }
    ];
    const [largeModel, setLargeModel] = useState("通义千问");
    // ==================== Style ====================
    const { styles } = useStyle();
@@ -124,12 +114,26 @@
    const [conversationsItems, setConversationsItems] = React.useState(defaultConversationsItems);
    const [activeKey, setActiveKey] = React.useState(defaultConversationsItems[0].key);
    const params = useParams();
    const { start: startChat } = useSse("/api/v1/aiBot/chat");
    const {result: llms} = useGet('/api/v1/aiLlm/list')
    const {result: conversationResult} = useGet('/api/v1/aiBotMessage/externalList',{"botId": params?.id})
    console.log('conversationResult',conversationResult)
    const getOptions = (options: { id: any; title: any }[]): { key: any; label: any }[] => {
        if (options) {
            return options.map((item) => ({
                key: item.id,
                label: item.title,
            }));
        }
        return [];
    };
    const modelItems: MenuProps['items'] = getOptions(llms?.data)
    const [chats, setChats] = useState<ChatMessage[]>([]);
    const params = useParams();
    console.log('params',params)
    // ==================== Runtime ====================
    const [agent] = useXAgent({
        request: async ({ message }, { onSuccess }) => {
@@ -197,28 +201,28 @@
                />
            </div>
            <div className={styles.chat}>
                <div>
                    <Dropdown
                        menu={{
                            items: modelItems,
                            onClick: (item) => {
                                console.log('item',item);
                                // 更新 largeModel 状态为选中的模型名称
                                // @ts-ignore
                                setLargeModel(item.domEvent.target.innerText);
                            },
                        }}
                    >
                        <a onClick={(e) => {
                            e.preventDefault();
                        }}>
                            <Space>
                                {largeModel} {/* 显示当前选中的模型名称 */}
                                <DownOutlined />
                            </Space>
                        </a>
                    </Dropdown>
                </div>
                {/*<div>*/}
                {/*    <Dropdown*/}
                {/*        menu={{*/}
                {/*            items: modelItems,*/}
                {/*            onClick: (item) => {*/}
                {/*                console.log('item',item);*/}
                {/*                // 更新 largeModel 状态为选中的模型名称*/}
                {/*                // @ts-ignore*/}
                {/*                setLargeModel(item.domEvent.target.innerText);*/}
                {/*            },*/}
                {/*        }}*/}
                {/*    >*/}
                {/*        <a onClick={(e) => {*/}
                {/*            e.preventDefault();*/}
                {/*        }}>*/}
                {/*            <Space>*/}
                {/*                {largeModel} /!* 显示当前选中的模型名称 *!/*/}
                {/*                <DownOutlined />*/}
                {/*            </Space>*/}
                {/*        </a>*/}
                {/*    </Dropdown>*/}
                {/*</div>*/}
                <AiProChat
                    chats={chats}
                    onChatsChange={setChats} // 确保正确传递 onChatsChange
@@ -251,4 +255,9 @@
    );
};
export default ExternalBot;
export default {
    path: "/ai/externalBot/:id",
    element: ExternalBot,
    frontEnable: true,
};
aiflowy-ui-react/src/pages/ai/aiKnowledge/FileImportPanel.tsx
@@ -74,7 +74,7 @@
            file.type === "application/markdown" ||
            file.type === "application/vnd.openxmlformats-officedocument.wordprocessingml.document" ||
            file.name.endsWith(".md");
        const isLt15M = file.size / 1024 / 1024 < 15;
        const isLt15M = file.size / 1024 / 1024 < 200;
        if (!isAllowedType) {
            message.error("仅支持 txt, pdf, md, docx 格式的文件!");
@@ -273,7 +273,7 @@
                            setDataPreView([]);
                            setFileList([]);
                        }}>取消导入</Button>
                        <Button type="dashed" disabled={disabledConfirm} onClick={() => {
                        <Button type="dashed"  onClick={() => {
                            setPreviewListLoading({ spinning: true,tip: "正在保存文件..."})
                            setDisabledConfirm(true)
                            // 构造 FormData 对象
aiflowy-ui-react/src/pages/ai/botDesign/BotDesign.tsx
@@ -405,7 +405,7 @@
                                    <div>
                                        <span>
                                            外部访问地址 <a
                                            href={window.location.href.substring(0, window.location.href.indexOf('/ai')) + '/bot/chat/' + detail?.data.id}
                                            href={window.location.href.substring(0, window.location.href.indexOf('/ai')) + '/ai/externalBot/' + detail?.data.id}
                                            target={"_blank"}>打开</a>
                                        </span>
                                        <TextArea readOnly disabled
aiflowy-ui-react/src/routers/router.tsx
@@ -3,6 +3,7 @@
import Layout from "../components/Layout";
import Login from "../pages/commons/login.tsx";
import React from "react";
import {ExternalBot} from "../pages/ExternalBot.tsx";
/**
 * 登录成功之后的路由和菜单配置
@@ -70,6 +71,10 @@
        path: "/login",
        element: <Login/>,
    },
    {
        path: "/ai/externalBot",
        element: <ExternalBot/>,
    },
    ...frontRouters
];