├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── bootstrap ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── honvay │ │ │ └── flychat │ │ │ └── application │ │ │ ├── FlychatApplication.java │ │ │ └── config │ │ │ ├── GlobalExceptionAdvice.java │ │ │ ├── MyBatisPlusConfiguration.java │ │ │ └── WebMvcConfiguration.java │ └── resources │ │ ├── application.yml │ │ └── static │ │ ├── index.html │ │ └── index2.html │ └── test │ └── java │ ├── impl │ ├── ApplicationDomainServiceImplTest.java │ ├── KnowledgeBaseApplicationServiceImplTest.java │ └── KnowledgeChatServiceImplTest.java │ └── mapper │ └── KnowledgeBaseDetailMapperTest.java ├── chat ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── honvay │ └── flychat │ └── chat │ ├── application │ ├── ChatApplicationService.java │ └── impl │ │ └── ChatApplicationServiceImpl.java │ ├── domain │ ├── model │ │ ├── Chat.java │ │ ├── ChatMessage.java │ │ ├── ChatQuote.java │ │ └── User.java │ ├── repository │ │ └── ChatRepository.java │ └── service │ │ ├── ChatDomainServiceImpl.java │ │ └── impl │ │ └── ChatDomainService.java │ ├── infra │ ├── converter │ │ └── ChatConverter.java │ ├── mapper │ │ ├── ChatMapper.java │ │ ├── ChatMessageMapper.java │ │ └── ChatQuoteMapper.java │ ├── po │ │ ├── ChatMessagePo.java │ │ ├── ChatPo.java │ │ └── ChatQuotePo.java │ └── repository │ │ └── ChatRepositoryImpl.java │ └── web │ ├── ChatController.java │ └── model │ ├── ChatMessageVo.java │ └── ChatVo.java ├── conversation ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── honvay │ └── flychat │ └── conversation │ ├── application │ ├── ConversationApplicationService.java │ ├── ConversationModel.java │ └── impl │ │ └── ConversationApplicationServiceImpl.java │ ├── controller │ ├── ConversationController.java │ └── ConversationVo.java │ ├── domain │ ├── Conversation.java │ ├── Knowledge.java │ ├── Model.java │ └── Relation.java │ ├── infra │ ├── ConversationMessageProvider.java │ ├── KnowledgeMessageProvider.java │ ├── MessageProvider.java │ ├── MessageProviderDelegator.java │ └── MockChatModelService.java │ └── types │ └── ChatModel.java ├── docs └── flychat.sql ├── framework ├── framework-core │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── honvay │ │ └── cola │ │ └── framework │ │ └── core │ │ ├── ErrorConstants.java │ │ ├── ErrorMessage.java │ │ ├── enums │ │ └── GeneralEnum.java │ │ ├── exception │ │ └── ServiceException.java │ │ └── protocol │ │ └── Response.java ├── framework-web │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── honvay │ │ └── cola │ │ └── framework │ │ └── web │ │ ├── ResponsePayload.java │ │ └── ResponsePayloadAdvise.java └── pom.xml ├── frontend ├── .env.development ├── .env.production ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── .stylelintrc.js ├── babel.config.js ├── commitlint.config.js ├── components.d.ts ├── config │ ├── plugin │ │ ├── arcoResolver.ts │ │ ├── arcoStyleImport.ts │ │ ├── compress.ts │ │ ├── imagemin.ts │ │ └── visualizer.ts │ ├── utils │ │ └── index.ts │ ├── vite.config.base.ts │ ├── vite.config.dev.ts │ └── vite.config.prod.ts ├── index.html ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── src │ ├── App.vue │ ├── api │ │ ├── chat.ts │ │ ├── common.ts │ │ ├── dashboard.ts │ │ ├── interceptor.ts │ │ ├── list.ts │ │ ├── message.ts │ │ └── user.ts │ ├── assets │ │ ├── images │ │ │ ├── avatar.jpg │ │ │ └── login-banner.png │ │ ├── logo.svg │ │ ├── style │ │ │ ├── breakpoint.less │ │ │ ├── github-markdown.less │ │ │ ├── global.less │ │ │ ├── highlight.less │ │ │ └── tailwind.css │ │ └── world.json │ ├── components │ │ ├── breadcrumb │ │ │ └── index.vue │ │ ├── chart │ │ │ └── index.vue │ │ ├── common │ │ │ └── SvgIcon │ │ │ │ └── index.vue │ │ ├── footer │ │ │ └── index.vue │ │ ├── global-setting │ │ │ ├── block.vue │ │ │ ├── form-wrapper.vue │ │ │ └── index.vue │ │ ├── index.ts │ │ ├── menu │ │ │ ├── index.vue │ │ │ └── use-menu-tree.ts │ │ ├── message-box │ │ │ ├── index.vue │ │ │ ├── list.vue │ │ │ └── locale │ │ │ │ ├── en-US.ts │ │ │ │ └── zh-CN.ts │ │ ├── navbar │ │ │ └── index.vue │ │ └── tab-bar │ │ │ ├── index.vue │ │ │ ├── readme.md │ │ │ └── tab-item.vue │ ├── config │ │ └── settings.json │ ├── directive │ │ ├── index.ts │ │ └── permission │ │ │ └── index.ts │ ├── env.d.ts │ ├── hooks │ │ ├── chart-option.ts │ │ ├── icon-render.ts │ │ ├── layout.ts │ │ ├── loading.ts │ │ ├── locale.ts │ │ ├── permission.ts │ │ ├── request.ts │ │ ├── responsive.ts │ │ ├── scroll.ts │ │ ├── themes.ts │ │ ├── user.ts │ │ └── visible.ts │ ├── layout │ │ ├── default-layout.vue │ │ └── page-layout.vue │ ├── locale │ │ ├── en-US.ts │ │ ├── en-US │ │ │ └── settings.ts │ │ ├── index.ts │ │ ├── zh-CN.ts │ │ └── zh-CN │ │ │ └── settings.ts │ ├── main.ts │ ├── mock │ │ ├── index.ts │ │ ├── message-box.ts │ │ └── user.ts │ ├── router │ │ ├── app-menus │ │ │ └── index.ts │ │ ├── constants.ts │ │ ├── guard │ │ │ ├── index.ts │ │ │ ├── permission.ts │ │ │ └── userLoginInfo.ts │ │ ├── index.ts │ │ ├── routes │ │ │ ├── base.ts │ │ │ ├── index.ts │ │ │ ├── modules │ │ │ │ ├── chat.ts │ │ │ │ └── knowledge.ts │ │ │ └── types.ts │ │ └── typings.d.ts │ ├── store │ │ ├── index.ts │ │ └── modules │ │ │ ├── app │ │ │ ├── index.ts │ │ │ └── types.ts │ │ │ ├── chat │ │ │ ├── index.ts │ │ │ └── types.ts │ │ │ ├── tab-bar │ │ │ ├── index.ts │ │ │ └── types.ts │ │ │ └── user │ │ │ ├── index.ts │ │ │ └── types.ts │ ├── types │ │ ├── echarts.ts │ │ ├── global.ts │ │ └── mock.ts │ ├── utils │ │ ├── auth.ts │ │ ├── copy.ts │ │ ├── env.ts │ │ ├── event.ts │ │ ├── index.ts │ │ ├── is.ts │ │ ├── monitor.ts │ │ ├── route-listener.ts │ │ ├── setup-mock.ts │ │ └── storage.ts │ └── views │ │ ├── chat │ │ ├── chat-list.vue │ │ ├── container │ │ │ └── index.vue │ │ ├── header │ │ │ └── index.vue │ │ ├── index.vue │ │ └── message │ │ │ ├── avatar.vue │ │ │ ├── index.vue │ │ │ ├── style.less │ │ │ └── text.vue │ │ ├── dashboard │ │ └── workplace │ │ │ ├── components │ │ │ ├── announcement.vue │ │ │ ├── banner.vue │ │ │ ├── carousel.vue │ │ │ ├── categories-percent.vue │ │ │ ├── content-chart.vue │ │ │ ├── data-panel.vue │ │ │ ├── docs.vue │ │ │ ├── popular-content.vue │ │ │ ├── quick-operation.vue │ │ │ └── recently-visited.vue │ │ │ ├── index.vue │ │ │ ├── locale │ │ │ ├── en-US.ts │ │ │ └── zh-CN.ts │ │ │ └── mock.ts │ │ ├── knowledge │ │ ├── components │ │ │ ├── card-wrap.vue │ │ │ ├── quality-inspection.vue │ │ │ ├── rules-preset.vue │ │ │ └── the-service.vue │ │ ├── index.vue │ │ ├── locale │ │ │ ├── en-US.ts │ │ │ └── zh-CN.ts │ │ └── mock.ts │ │ ├── login │ │ ├── components │ │ │ ├── banner.vue │ │ │ └── login-form.vue │ │ ├── index.vue │ │ └── locale │ │ │ ├── en-US.ts │ │ │ └── zh-CN.ts │ │ ├── not-found │ │ └── index.vue │ │ └── redirect │ │ └── index.vue ├── tailwind.config.js └── tsconfig.json ├── knowledge ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── honvay │ │ │ └── flychat │ │ │ └── knowledge │ │ │ ├── application │ │ │ ├── KnowledgeApplicationService.java │ │ │ ├── KnowledgeChatService.java │ │ │ ├── KnowledgeEmbeddingService.java │ │ │ └── impl │ │ │ │ ├── KnowledgeApplicationServiceImpl.java │ │ │ │ ├── KnowledgeChatServiceImpl.java │ │ │ │ └── KnowledgeEmbeddingServiceImpl.java │ │ │ ├── domain │ │ │ ├── event │ │ │ │ └── KnowledgeItemCreateEvent.java │ │ │ ├── model │ │ │ │ ├── Application.java │ │ │ │ ├── ApplicationKnowledge.java │ │ │ │ ├── ApplicationModel.java │ │ │ │ ├── KnowledgeBase.java │ │ │ │ ├── KnowledgeChat.java │ │ │ │ ├── KnowledgeDetail.java │ │ │ │ ├── KnowledgeItem.java │ │ │ │ ├── KnowledgeItemStatus.java │ │ │ │ ├── Owner.java │ │ │ │ ├── Relevant.java │ │ │ │ └── SplitType.java │ │ │ ├── repository │ │ │ │ ├── ApplicationKnowledgeRepository.java │ │ │ │ ├── ApplicationRepository.java │ │ │ │ ├── KnowledgeDetailRepository.java │ │ │ │ ├── KnowledgeItemRepository.java │ │ │ │ └── KnowledgeRepository.java │ │ │ └── service │ │ │ │ ├── ApplicationDomainService.java │ │ │ │ ├── KnowledgeBaseDomainService.java │ │ │ │ └── impl │ │ │ │ ├── ApplicationDomainServiceImpl.java │ │ │ │ └── KnowledgeBaseDomainServiceImpl.java │ │ │ ├── infra │ │ │ ├── document │ │ │ │ ├── DocumentType.java │ │ │ │ └── KnowledgeDocumentLoader.java │ │ │ ├── factory │ │ │ │ ├── ApplicationConverter.java │ │ │ │ └── KnowledgeConverter.java │ │ │ ├── interceptor │ │ │ │ └── PgVectorInterceptor.java │ │ │ ├── mapper │ │ │ │ ├── ApplicationKnowledgeMapper.java │ │ │ │ ├── ApplicationMapper.java │ │ │ │ ├── KnowledgeBaseMapper.java │ │ │ │ ├── KnowledgeDetailMapper.java │ │ │ │ └── KnowledgeItemMapper.java │ │ │ ├── po │ │ │ │ ├── ApplicationKnowledgePo.java │ │ │ │ ├── ApplicationPo.java │ │ │ │ ├── KnowledgeBasePo.java │ │ │ │ ├── KnowledgeDetailPo.java │ │ │ │ └── KnowledgeItemPo.java │ │ │ ├── repository │ │ │ │ └── impl │ │ │ │ │ ├── ApplicationKnowledgeRepositoryImpl.java │ │ │ │ │ ├── ApplicationRepositoryImpl.java │ │ │ │ │ ├── KnowledgeDetailRepositoryImpl.java │ │ │ │ │ ├── KnowledgeItemRepositoryImpl.java │ │ │ │ │ └── KnowledgeRepositoryImpl.java │ │ │ └── splitter │ │ │ │ ├── FixedTextSplitter.java │ │ │ │ ├── KnowledgeSplitter.java │ │ │ │ ├── ParagraphTextSplitter.java │ │ │ │ ├── SentenceTextSplitter.java │ │ │ │ └── TextSplitter.java │ │ │ └── web │ │ │ ├── controller │ │ │ ├── KnowledgeChatController.java │ │ │ └── KnowledgeController.java │ │ │ ├── converter │ │ │ ├── KnowledgeAssembler.java │ │ │ └── KnowledgeChatAssembler.java │ │ │ └── model │ │ │ └── dto │ │ │ ├── KnowledgeChatDto.java │ │ │ ├── KnowledgeDto.java │ │ │ └── KnowledgeItemDto.java │ └── resources │ │ └── mapper │ │ └── KnowledgeDetailMapper.xml │ └── test │ ├── java │ └── com │ │ └── honvay │ │ └── flychat │ │ └── knowledge │ │ ├── llama │ │ └── OpenAiEmbeddingServiceImplTest.java │ │ └── splitter │ │ ├── FixedTextSplitterTest.java │ │ ├── ParagraphTextSplitterTest.java │ │ └── SentenceTextSplitterTest.java │ └── resources │ └── story-about-happy-carrot.pdf ├── langchain ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── honvay │ │ └── flychat │ │ └── langchain │ │ ├── chat │ │ ├── ChatModelService.java │ │ ├── ChatSetup.java │ │ ├── DefaultStreamChatObserver.java │ │ ├── OpenAiChatModelService.java │ │ └── StreamChatObserver.java │ │ ├── document │ │ └── StreamDocumentSource.java │ │ ├── llama │ │ ├── embedding │ │ │ ├── EmbeddingService.java │ │ │ └── OpenAiEmbeddingService.java │ │ └── model │ │ │ ├── OpenAiConverters.java │ │ │ └── OpenAiStreamingChatLanguageModel.java │ │ └── parser │ │ └── WordDocumentParser.java │ └── test │ └── java │ └── com │ └── honvay │ └── flychat │ └── langchain │ └── chat │ └── OpenAiChatModelServiceTest.java ├── mvnw ├── mvnw.cmd ├── pom.xml └── screenshots └── img.png /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leecho/flychat/d238fbd2a60673243686764d8aaff3ae956177af/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.7/apache-maven-3.8.7-bin.zip 18 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.1/maven-wrapper-3.1.1.jar 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 leecho 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlyChat 2 | 3 | # 介绍 4 | 5 | 基于Java和ChatGPT的AI平台,已实现基本的问答和基于知识库的问题,暂时只有后端应用,没有前端。 6 | 7 | ![Screenshot](screenshots/img.png) 8 | # 框架 9 | 10 | - Spring Boot 2.7.13 11 | - JDK 17 12 | - Postgresql 15 13 | - MyBatis Plus 14 | - Lombok 15 | 16 | 17 | # 技术要点 18 | 19 | ## 在线问答 20 | 21 | ### 上下文聊天 22 | 23 | 通过 Postgresql 实现聊天数据存储来实现上下文聊天,已实现流式输出结果,可以通过配置参数 maxTokens 来限制上下问问题的数量。 24 | 25 | 数据库存储了每次聊天对话的记录,在选择上下文聊天时,通过 chatId 获取历史消息,将历史问题以及回答消息都发送给 GPT。 26 | 27 | ## 知识库问答 28 | 29 | ### 知识库分词 30 | 已实现按照句子分词、段落分词、固定长度分词等分词方式,并通过ChatGPT进行Embedding 31 | 32 | ### 知识库匹配 33 | 通过Postgresql 插件 PGvector进行相似度匹配,获取与问题比较匹配的知识点 34 | 35 | ### 知识库问答 36 | 通过对问题进行Embedding,然后匹配知识库,构建Prompt,发送给ChatGPT获取答案。 37 | 38 | # 私有部署问题 39 | 40 | 可导入知识库文件、文本对知识库进行构建,知识库存储在本地数据库,调用ChatGPT接口进行问答,简而言之,数据还是会给到ChatGPT,但是不是所有的数据都给过去,理论上是一个半私有或者部分私有部署的状态,如果要实现完全私有化则需要部署一套私有的大语言模型。 41 | 42 | # 风险声明 43 | 44 | 本项目仅供学习和研究使用,不鼓励用于商业用途。对于因使用本项目而导致的任何损失,我们不承担任何责任。 45 | 46 | # 后续计划 47 | 目前项目只有后端没有前端,所有期待感兴趣的前端、UI、测试小伙伴一起参与项目。 48 | 后续实现功能: 49 | - 增加类似与ChatPDF的功能 50 | - 增加AI应用功能 51 | - 集成其他大语言模型 52 | 53 | # 联系方式 54 | 55 | wechat:leecho571 56 | 57 | # LICENSE 58 | [MIT](LICENSE) 59 | 60 | 前端部分基于 61 | 62 | [Chagpt-Web](https://github.com/Chanzhaoyu/chatgpt-web) -------------------------------------------------------------------------------- /bootstrap/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.honvay 8 | flychat 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | org.example 13 | bootstrap 14 | 15 | 16 | 17 17 | 17 18 | UTF-8 19 | 20 | 21 | 22 | 23 | com.honvay 24 | knowledge 25 | 0.0.1-SNAPSHOT 26 | 27 | 28 | com.honvay 29 | conversation 30 | 0.0.1-SNAPSHOT 31 | 32 | 33 | org.postgresql 34 | postgresql 35 | 42.2.27 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-test 40 | test 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-validation 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /bootstrap/src/main/java/com/honvay/flychat/application/FlychatApplication.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.application; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | 7 | @SpringBootApplication(scanBasePackages = "com.honvay") 8 | public class FlychatApplication { 9 | 10 | public static void main(String[] args) { 11 | SpringApplication.run(FlychatApplication.class,args); 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /bootstrap/src/main/java/com/honvay/flychat/application/config/MyBatisPlusConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.application.config; 2 | 3 | import com.baomidou.mybatisplus.annotation.DbType; 4 | import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; 5 | import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; 6 | import com.honvay.flychat.knowledge.infra.interceptor.PgVectorInterceptor; 7 | import org.mybatis.spring.annotation.MapperScan; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | @MapperScan("com.honvay.**.mapper") 13 | public class MyBatisPlusConfiguration { 14 | 15 | @Bean 16 | public MybatisPlusInterceptor mybatisPlusInterceptor(){ 17 | MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor(); 18 | mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); 19 | return mybatisPlusInterceptor; 20 | } 21 | 22 | @Bean 23 | public PgVectorInterceptor pdVectorInterceptor(){ 24 | return new PgVectorInterceptor(); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /bootstrap/src/main/java/com/honvay/flychat/application/config/WebMvcConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.application.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.http.HttpMethod; 5 | import org.springframework.web.servlet.config.annotation.CorsRegistry; 6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 7 | 8 | @Configuration 9 | public class WebMvcConfiguration implements WebMvcConfigurer { 10 | 11 | @Override 12 | public void addCorsMappings(CorsRegistry registry) { 13 | registry.addMapping("/**") 14 | .allowCredentials(true) 15 | .allowedHeaders("*") 16 | .allowedMethods(HttpMethod.GET.name(),HttpMethod.POST.name()) 17 | .allowedOriginPatterns("*"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /bootstrap/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | datasource: 3 | url: jdbc:postgresql://localhost:5432/flychat 4 | username: postgres 5 | password: postgres 6 | mybatis-plus: 7 | configuration: 8 | cache-enabled: true 9 | use-generated-keys: true 10 | default-executor-type: REUSE 11 | use-actual-param-name: true 12 | log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl 13 | type-handlers-package: com.honvay.**.handler 14 | logging: 15 | level: 16 | com.honvay: debug 17 | dev.langchain4j: debug 18 | openai: 19 | apiKey: ${OPENAI_API_KEY} -------------------------------------------------------------------------------- /bootstrap/src/main/resources/static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sse测试文档 5 | 6 | 7 | 8 |
sse测试
9 |
10 | 11 | 12 | 30 | -------------------------------------------------------------------------------- /bootstrap/src/main/resources/static/index2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Sse测试文档 5 | 6 | 7 | 8 |
sse测试
9 |
10 | 11 | 12 | 31 | -------------------------------------------------------------------------------- /bootstrap/src/test/java/impl/KnowledgeBaseApplicationServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package impl; 2 | 3 | import com.honvay.flychat.application.FlychatApplication; 4 | import com.honvay.flychat.knowledge.application.KnowledgeApplicationService; 5 | import com.honvay.flychat.knowledge.domain.model.KnowledgeBase; 6 | import com.honvay.flychat.knowledge.domain.model.Owner; 7 | import com.honvay.flychat.knowledge.domain.model.SplitType; 8 | import org.junit.jupiter.api.Test; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | 12 | import java.io.File; 13 | 14 | @SpringBootTest(classes = FlychatApplication.class) 15 | class KnowledgeBaseApplicationServiceImplTest { 16 | 17 | @Autowired 18 | private KnowledgeApplicationService knowledgeApplicationService; 19 | 20 | 21 | @Test 22 | void create(){ 23 | KnowledgeBase knowledgeBase = new KnowledgeBase(); 24 | knowledgeBase.setName("测试知识库"); 25 | Owner knowledgeOwner = new Owner(); 26 | knowledgeOwner.setId(1L); 27 | knowledgeBase.setOwner(knowledgeOwner); 28 | knowledgeApplicationService.create(knowledgeBase); 29 | } 30 | 31 | @Test 32 | void addItem(){ 33 | File file = new File("/Users/user/workspace/ChatGPT/flychat/chat/src/test/resources/story-about-happy-carrot.pdf"); 34 | KnowledgeBase knowledgeBase = new KnowledgeBase(); 35 | knowledgeBase.setId(2L); 36 | knowledgeApplicationService.addFile(knowledgeBase,file, SplitType.SENTENCE); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /bootstrap/src/test/java/impl/KnowledgeChatServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package impl; 2 | 3 | import com.honvay.flychat.application.FlychatApplication; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest(classes = FlychatApplication.class) 7 | class KnowledgeChatServiceImplTest { 8 | 9 | /* @Autowired 10 | private KnowledgeChatService knowledgeChatService; 11 | 12 | @Test 13 | void chat(){ 14 | String chat = knowledgeChatService.chat("Who is Charlie? Answer in 10 words.", Collections.singletonList(1L),0.8f,5); 15 | System.out.println(chat); 16 | } 17 | */ 18 | } -------------------------------------------------------------------------------- /chat/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.honvay 8 | flychat 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | chat 13 | 14 | 15 | 17 16 | 17 17 | UTF-8 18 | 19 | 20 | 21 | 22 | org.projectlombok 23 | lombok 24 | 25 | 26 | com.baomidou 27 | mybatis-plus-boot-starter 28 | 3.5.3.1 29 | 30 | 31 | com.honvay 32 | framework-web 33 | 0.0.1-SNAPSHOT 34 | 35 | 36 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/application/ChatApplicationService.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.application; 2 | 3 | import com.honvay.flychat.chat.domain.model.Chat; 4 | 5 | public interface ChatApplicationService{ 6 | 7 | void create(Chat chat); 8 | 9 | void saveMessages(Chat chat); 10 | } 11 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/application/impl/ChatApplicationServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.application.impl; 2 | 3 | import com.honvay.flychat.chat.application.ChatApplicationService; 4 | import com.honvay.flychat.chat.domain.model.Chat; 5 | import com.honvay.flychat.chat.domain.repository.ChatRepository; 6 | import com.honvay.flychat.chat.domain.service.impl.ChatDomainService; 7 | import org.springframework.stereotype.Service; 8 | 9 | @Service 10 | public class ChatApplicationServiceImpl implements ChatApplicationService { 11 | 12 | private final ChatRepository chatRepository; 13 | 14 | public ChatApplicationServiceImpl(ChatRepository chatRepository) { 15 | this.chatRepository = chatRepository; 16 | } 17 | 18 | @Override 19 | public void create(Chat chat){ 20 | this.chatRepository.create(chat); 21 | } 22 | 23 | @Override 24 | public void saveMessages(Chat chat){ 25 | this.chatRepository.saveMessages(chat); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/domain/model/Chat.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.domain.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.ArrayList; 6 | import java.util.Date; 7 | import java.util.List; 8 | 9 | @Data 10 | public class Chat { 11 | 12 | private Long id; 13 | 14 | private String name; 15 | 16 | private User user; 17 | 18 | private List messages; 19 | 20 | private Date createTime; 21 | 22 | public void addMessage(ChatMessage message){ 23 | if(this.messages == null){ 24 | this.messages = new ArrayList<>(); 25 | } 26 | this.messages.add(message); 27 | } 28 | 29 | public boolean isExists(){ 30 | return this.id != null; 31 | } 32 | 33 | public void create(){ 34 | this.createTime = new Date(); 35 | } 36 | 37 | public Long getApplicationId(){ 38 | return null; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/domain/model/ChatMessage.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.domain.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.math.BigDecimal; 6 | import java.util.Date; 7 | import java.util.List; 8 | 9 | @Data 10 | public class ChatMessage { 11 | 12 | private Long id; 13 | 14 | private String conversationId; 15 | 16 | private String content; 17 | 18 | private String prompt; 19 | 20 | private String role; 21 | 22 | private List quotes; 23 | 24 | private Integer tokenSize; 25 | 26 | private BigDecimal cost; 27 | 28 | private Date createTime; 29 | 30 | public boolean hasQuotes() { 31 | return this.quotes != null && this.quotes.size() > 0; 32 | } 33 | 34 | public static ChatMessage ofUser(String conversationId, String content, String prompt, Integer tokenSize) { 35 | ChatMessage chatMessage = ChatMessage.of(conversationId, content, prompt, null, tokenSize); 36 | chatMessage.setRole("user"); 37 | return chatMessage; 38 | } 39 | 40 | public static ChatMessage ofAi(String conversationId, String content, List quotes, Integer tokenSize) { 41 | ChatMessage chatMessage = ChatMessage.of(conversationId, content, null, quotes, tokenSize); 42 | chatMessage.setRole("ai"); 43 | return chatMessage; 44 | } 45 | 46 | private static ChatMessage of(String conversationId, String content, String prompt, List quotes, Integer tokenSize) { 47 | ChatMessage chatMessage = new ChatMessage(); 48 | chatMessage.setConversationId(conversationId); 49 | chatMessage.setQuotes(quotes); 50 | chatMessage.setContent(content); 51 | chatMessage.setTokenSize(tokenSize); 52 | chatMessage.setPrompt(prompt); 53 | return chatMessage; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/domain/model/ChatQuote.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.domain.model; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ChatQuote { 7 | 8 | private Double similarity; 9 | 10 | private Long sourceId; 11 | 12 | private String segment; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/domain/model/User.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.domain.model; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class User { 7 | 8 | private Long id; 9 | 10 | private String name; 11 | 12 | private String avatar; 13 | 14 | } 15 | 16 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/domain/repository/ChatRepository.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.domain.repository; 2 | 3 | 4 | import com.honvay.flychat.chat.domain.model.Chat; 5 | import com.honvay.flychat.chat.domain.model.ChatMessage; 6 | 7 | import java.util.List; 8 | 9 | public interface ChatRepository { 10 | 11 | void create(Chat knowledgeChat); 12 | 13 | List findMessage(Chat chat, int start, int size); 14 | 15 | void saveMessages(Chat knowledgeChat); 16 | 17 | List find(); 18 | 19 | List findMessage(Chat chat); 20 | } 21 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/domain/service/ChatDomainServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.domain.service; 2 | 3 | import com.honvay.flychat.chat.domain.model.Chat; 4 | import com.honvay.flychat.chat.domain.repository.ChatRepository; 5 | import com.honvay.flychat.chat.domain.service.impl.ChatDomainService; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | public class ChatDomainServiceImpl implements ChatDomainService { 10 | 11 | private final ChatRepository chatRepository; 12 | 13 | public ChatDomainServiceImpl(ChatRepository chatRepository) { 14 | this.chatRepository = chatRepository; 15 | } 16 | 17 | @Override 18 | public void create(Chat chat){ 19 | this.chatRepository.create(chat); 20 | } 21 | 22 | @Override 23 | public void saveMessages(Chat chat){ 24 | this.chatRepository.saveMessages(chat); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/domain/service/impl/ChatDomainService.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.domain.service.impl; 2 | 3 | 4 | import com.honvay.flychat.chat.domain.model.Chat; 5 | 6 | public interface ChatDomainService { 7 | void create(Chat chat); 8 | 9 | void saveMessages(Chat chat); 10 | } 11 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/infra/mapper/ChatMapper.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.infra.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.honvay.flychat.chat.infra.po.ChatPo; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | @Mapper 8 | public interface ChatMapper extends BaseMapper { 9 | } 10 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/infra/mapper/ChatMessageMapper.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.infra.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.honvay.flychat.chat.infra.po.ChatMessagePo; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | @Mapper 8 | public interface ChatMessageMapper extends BaseMapper { 9 | } 10 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/infra/mapper/ChatQuoteMapper.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.infra.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.honvay.flychat.chat.infra.po.ChatQuotePo; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | @Mapper 8 | public interface ChatQuoteMapper extends BaseMapper { 9 | } 10 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/infra/po/ChatMessagePo.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.infra.po; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.Data; 7 | 8 | import java.math.BigDecimal; 9 | import java.util.Date; 10 | 11 | @Data 12 | @TableName("chat_message") 13 | public class ChatMessagePo { 14 | 15 | @TableId(type = IdType.AUTO) 16 | private Long id; 17 | 18 | private String conversationId; 19 | 20 | private Long chatId; 21 | 22 | private String content; 23 | 24 | private String prompt; 25 | 26 | private String role; 27 | 28 | private Integer tokenSize; 29 | 30 | private BigDecimal cost; 31 | 32 | private Date createTime; 33 | } 34 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/infra/po/ChatPo.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.infra.po; 2 | 3 | 4 | import com.baomidou.mybatisplus.annotation.IdType; 5 | import com.baomidou.mybatisplus.annotation.TableId; 6 | import com.baomidou.mybatisplus.annotation.TableName; 7 | import lombok.Data; 8 | 9 | import java.util.Date; 10 | 11 | @Data 12 | @TableName("chat") 13 | public class ChatPo { 14 | 15 | @TableId(type = IdType.AUTO) 16 | private Long id; 17 | 18 | private String name; 19 | 20 | private Long applicationId; 21 | 22 | private Long userId; 23 | 24 | private Date createTime; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/infra/po/ChatQuotePo.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.infra.po; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.Data; 7 | 8 | @Data 9 | @TableName("chat_message_relevant") 10 | public class ChatQuotePo { 11 | 12 | @TableId(type = IdType.AUTO) 13 | private Long id; 14 | 15 | private Long messageId; 16 | 17 | private String segment; 18 | 19 | private Double similarity; 20 | 21 | private Long sourceId; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/web/ChatController.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.web; 2 | 3 | import com.honvay.cola.framework.web.ResponsePayload; 4 | import com.honvay.flychat.chat.domain.model.Chat; 5 | import com.honvay.flychat.chat.domain.model.ChatMessage; 6 | import com.honvay.flychat.chat.domain.repository.ChatRepository; 7 | import com.honvay.flychat.chat.web.model.ChatMessageVo; 8 | import com.honvay.flychat.chat.web.model.ChatVo; 9 | import org.springframework.web.bind.annotation.GetMapping; 10 | import org.springframework.web.bind.annotation.PathVariable; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | import java.util.List; 15 | 16 | @ResponsePayload 17 | @RestController 18 | @RequestMapping("/chat") 19 | public class ChatController { 20 | 21 | private final ChatRepository chatRepository; 22 | 23 | public ChatController(ChatRepository chatRepository) { 24 | this.chatRepository = chatRepository; 25 | } 26 | 27 | @GetMapping 28 | public List list(){ 29 | List chats = this.chatRepository.find(); 30 | return chats.stream() 31 | .map(ChatVo::from) 32 | .toList(); 33 | } 34 | 35 | @GetMapping("/{id}/messages") 36 | public List findMessage(@PathVariable Long id){ 37 | Chat chat = new Chat(); 38 | chat.setId(id); 39 | List chatMessages = this.chatRepository.findMessage(chat); 40 | return chatMessages.stream() 41 | .map(ChatMessageVo::from) 42 | .toList(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/web/model/ChatMessageVo.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.web.model; 2 | 3 | import com.honvay.flychat.chat.domain.model.ChatMessage; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class ChatMessageVo { 8 | 9 | private Long id; 10 | 11 | private String content; 12 | 13 | private Long createTime; 14 | 15 | private String role; 16 | 17 | public static ChatMessageVo from(ChatMessage chatMessage){ 18 | ChatMessageVo chatMessageVo = new ChatMessageVo(); 19 | chatMessageVo.setId(chatMessage.getId()); 20 | chatMessageVo.setContent(chatMessage.getContent()); 21 | chatMessageVo.setCreateTime(chatMessage.getCreateTime().getTime()); 22 | chatMessageVo.setRole(chatMessage.getRole()); 23 | return chatMessageVo; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /chat/src/main/java/com/honvay/flychat/chat/web/model/ChatVo.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.chat.web.model; 2 | 3 | import com.honvay.flychat.chat.domain.model.Chat; 4 | import lombok.Data; 5 | 6 | import java.util.Date; 7 | 8 | @Data 9 | public class ChatVo { 10 | 11 | private Long id; 12 | 13 | private String name; 14 | 15 | private Date createTime; 16 | 17 | public static ChatVo from(Chat chat){ 18 | ChatVo chatVo = new ChatVo(); 19 | chatVo.setId(chat.getId()); 20 | chatVo.setName(chat.getName()); 21 | chatVo.setCreateTime(chat.getCreateTime()); 22 | return chatVo; 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /conversation/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.honvay 8 | flychat 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | conversation 13 | 14 | 15 | 17 16 | 17 17 | UTF-8 18 | 19 | 20 | 21 | 22 | com.honvay 23 | chat 24 | 0.0.1-SNAPSHOT 25 | 26 | 27 | com.honvay 28 | langchain 29 | 0.0.1-SNAPSHOT 30 | 31 | 32 | com.honvay 33 | knowledge 34 | 0.0.1-SNAPSHOT 35 | 36 | 37 | org.apache.commons 38 | commons-lang3 39 | 40 | 41 | com.github.javafaker 42 | javafaker 43 | 1.0.2 44 | 45 | 46 | snakeyaml 47 | org.yaml 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /conversation/src/main/java/com/honvay/flychat/conversation/application/ConversationApplicationService.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.conversation.application; 2 | 3 | import com.honvay.flychat.conversation.domain.Conversation; 4 | 5 | import java.util.function.Consumer; 6 | 7 | public interface ConversationApplicationService { 8 | void converse(Conversation conversation); 9 | 10 | void converse(Conversation conversation, 11 | Consumer onResult, 12 | Consumer onComplete, 13 | Consumer onError); 14 | } 15 | -------------------------------------------------------------------------------- /conversation/src/main/java/com/honvay/flychat/conversation/application/ConversationModel.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.conversation.application; 2 | 3 | import com.honvay.flychat.langchain.chat.ChatSetup; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class ConversationModel { 8 | 9 | private String modelName; 10 | 11 | private Double temperature; 12 | 13 | private Integer maxTokenSize; 14 | 15 | public ChatSetup toModelSetup(){ 16 | ChatSetup chatSetup = new ChatSetup(); 17 | chatSetup.setModelName(this.modelName); 18 | chatSetup.setTemperature(this.temperature); 19 | return chatSetup; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /conversation/src/main/java/com/honvay/flychat/conversation/controller/ConversationVo.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.conversation.controller; 2 | 3 | import com.honvay.flychat.chat.domain.model.Chat; 4 | import com.honvay.flychat.chat.web.model.ChatVo; 5 | import com.honvay.flychat.conversation.domain.Conversation; 6 | import lombok.Data; 7 | 8 | @Data 9 | public class ConversationVo { 10 | 11 | private String id; 12 | 13 | public ChatVo chat; 14 | 15 | private String message; 16 | 17 | public static ConversationVo from(Conversation conversation){ 18 | ConversationVo conversationVo = new ConversationVo(); 19 | conversationVo.setMessage(conversation.getResult()); 20 | Chat chat = conversation.getChat(); 21 | conversationVo.setChat(ChatVo.from(chat)); 22 | conversationVo.setId(conversation.getId()); 23 | return conversationVo; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /conversation/src/main/java/com/honvay/flychat/conversation/domain/Knowledge.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.conversation.domain; 2 | 3 | import com.honvay.flychat.chat.domain.model.ChatQuote; 4 | import lombok.Data; 5 | 6 | import java.util.List; 7 | 8 | @Data 9 | public class Knowledge { 10 | 11 | /** 12 | * 知识库列表 13 | */ 14 | private List knowledgeBases; 15 | 16 | /** 17 | * 相似度 18 | */ 19 | private Double similarity; 20 | 21 | /** 22 | * 关联数量 23 | */ 24 | private Integer relevantSize; 25 | 26 | /** 27 | * 引用 28 | */ 29 | private List quotes; 30 | 31 | private boolean blockOnEmpty; 32 | 33 | } 34 | -------------------------------------------------------------------------------- /conversation/src/main/java/com/honvay/flychat/conversation/domain/Model.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.conversation.domain; 2 | 3 | import com.honvay.flychat.conversation.types.ChatModel; 4 | import com.honvay.flychat.langchain.chat.ChatSetup; 5 | import lombok.Data; 6 | 7 | @Data 8 | public class Model { 9 | 10 | private String apiKey; 11 | 12 | private String modelName; 13 | 14 | private Double temperature; 15 | 16 | private Integer maxTokens; 17 | 18 | private Double topP; 19 | 20 | private Double presencePenalty; 21 | 22 | private Double frequencyPenalty; 23 | 24 | public void init(){ 25 | if(modelName == null){ 26 | this.modelName = ChatModel.GPT_35_TURBO.getModelName(); 27 | this.maxTokens = ChatModel.GPT_35_TURBO.getMaxTokens(); 28 | } 29 | } 30 | 31 | public ChatSetup toModelSetup(){ 32 | ChatSetup chatSetup = new ChatSetup(); 33 | chatSetup.setModelName(this.modelName); 34 | chatSetup.setTemperature(this.temperature); 35 | chatSetup.setApiKey(this.apiKey); 36 | chatSetup.setMaxTokens(this.maxTokens); 37 | chatSetup.setTopP(this.topP); 38 | chatSetup.setPresencePenalty(this.presencePenalty); 39 | chatSetup.setFrequencyPenalty(this.frequencyPenalty); 40 | return chatSetup; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /conversation/src/main/java/com/honvay/flychat/conversation/domain/Relation.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.conversation.domain; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * 对话上下文 7 | */ 8 | @Data 9 | public class Relation { 10 | 11 | /** 12 | * 历史消息数量 13 | */ 14 | private Integer size; 15 | 16 | /** 17 | * 是否为清洁模式,不关联历史消息 18 | */ 19 | private boolean clean; 20 | } 21 | -------------------------------------------------------------------------------- /conversation/src/main/java/com/honvay/flychat/conversation/infra/MessageProvider.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.conversation.infra; 2 | 3 | import com.honvay.flychat.chat.domain.model.Chat; 4 | import com.honvay.flychat.chat.domain.model.ChatMessage; 5 | import com.honvay.flychat.conversation.domain.Conversation; 6 | import org.jetbrains.annotations.NotNull; 7 | 8 | import java.util.List; 9 | 10 | public interface MessageProvider { 11 | 12 | String getPrompt(Conversation conversation); 13 | 14 | @NotNull 15 | List getRelationMessages(Conversation conversation, Chat chat); 16 | 17 | String getName(); 18 | } 19 | -------------------------------------------------------------------------------- /conversation/src/main/java/com/honvay/flychat/conversation/infra/MessageProviderDelegator.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.conversation.infra; 2 | 3 | import com.honvay.flychat.chat.domain.model.Chat; 4 | import com.honvay.flychat.chat.domain.model.ChatMessage; 5 | import com.honvay.flychat.conversation.domain.Conversation; 6 | import org.apache.commons.collections4.CollectionUtils; 7 | import org.springframework.stereotype.Component; 8 | 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.stream.Collectors; 12 | 13 | @Component 14 | public class MessageProviderDelegator { 15 | 16 | private final Map providers; 17 | 18 | public MessageProviderDelegator(List providers) { 19 | this.providers = providers.stream() 20 | .collect(Collectors.toMap(MessageProvider::getName, 21 | provider -> provider)); 22 | } 23 | 24 | public String getPrompt(Conversation conversation){ 25 | return this.getMessageProvider(conversation).getPrompt(conversation); 26 | } 27 | 28 | public List getRelation(Conversation conversation, Chat chat){ 29 | return this.getMessageProvider(conversation).getRelationMessages(conversation,chat); 30 | } 31 | 32 | private MessageProvider getMessageProvider(Conversation conversation) { 33 | return CollectionUtils.isNotEmpty(conversation.getKnowledge().getKnowledgeBases()) ? providers.get("knowledge") : providers.get("conversation"); 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /conversation/src/main/java/com/honvay/flychat/conversation/types/ChatModel.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.conversation.types; 2 | 3 | import lombok.Getter; 4 | 5 | public enum ChatModel { 6 | GPT_35_TURBO("gpt-3.5-turbo",4069), 7 | GPT_35_TURBO_0613("gpt-3.5-turbo-0613",4069), 8 | GPT_35_TURBO_16K("gpt-3.5-turbo-16k",16384), 9 | GPT_35_TURBO_16K_0613("gpt-3.5-turbo-16k-0613",16384), 10 | GPT_4("gpt-4",8192), 11 | GPT_4_0613("gpt-4-0613",8192), 12 | GPT_4_32k("gpt-4-32k",8192), 13 | GPT_4_32k_0613("gpt-4-32k-0613",8192); 14 | 15 | @Getter 16 | private final String modelName; 17 | 18 | @Getter 19 | private final Integer maxTokens; 20 | 21 | 22 | ChatModel(String modelName, Integer maxTokens) { 23 | this.modelName = modelName; 24 | this.maxTokens = maxTokens; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /framework/framework-core/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.honvay 8 | framework 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | framework-core 13 | 14 | 15 | 17 16 | 17 17 | UTF-8 18 | 19 | 20 | -------------------------------------------------------------------------------- /framework/framework-core/src/main/java/com/honvay/cola/framework/core/ErrorConstants.java: -------------------------------------------------------------------------------- 1 | package com.honvay.cola.framework.core; 2 | 3 | /** 4 | * @author LIQIU 5 | * @date 2018-1-12 6 | **/ 7 | public class ErrorConstants implements ErrorMessage { 8 | 9 | 10 | // 1xx Informational 11 | 12 | /** 13 | * 系统内部错误 14 | */ 15 | public static final ErrorConstants INTERNAL_SERVER_ERROR = new ErrorConstants("10000", "系统错误"); 16 | /** 17 | * 参数错误 18 | */ 19 | public static final ErrorConstants ILLEGAL_ARGUMENT = new ErrorConstants("10001", "参数错误"); 20 | /** 21 | * 业务错误 22 | */ 23 | public static final ErrorConstants SERVICE_EXCEPTION = new ErrorConstants("10002", "业务错误"); 24 | /** 25 | * 非法的数据格式,参数没有经过校验 26 | */ 27 | public static final ErrorConstants ILLEGAL_DATA = new ErrorConstants("10003", "数据错误"); 28 | /** 29 | * 非法状态 30 | */ 31 | public static final ErrorConstants ILLEGAL_STATE = new ErrorConstants("10005", "非法状态"); 32 | /** 33 | * 缺少参数 34 | */ 35 | public static final ErrorConstants MISSING_ARGUMENT = new ErrorConstants("10006", "缺少参数"); 36 | /** 37 | * 非法访问 38 | */ 39 | public static final ErrorConstants ACCESS_DEFINED = new ErrorConstants("10007", "非法访问,没有认证"); 40 | /** 41 | * 权限不足 42 | */ 43 | public static final ErrorConstants UNAUTHORIZED = new ErrorConstants("10008", "权限不足"); 44 | 45 | /** 46 | * 错误的请求 47 | */ 48 | public static final ErrorConstants METHOD_NOT_ALLOWED = ErrorConstants.of("10009", "不支持的方法"); 49 | 50 | /** 51 | * 参数错误 52 | */ 53 | public static final ErrorConstants ILLEGAL_ARGUMENT_TYPE = ErrorConstants.of("10010", "参数类型错误"); 54 | 55 | 56 | private final String code; 57 | 58 | private final String message; 59 | 60 | 61 | ErrorConstants(String value, String message) { 62 | this.code = value; 63 | this.message = message; 64 | } 65 | 66 | public static ErrorConstants of(String code, String message) { 67 | return new ErrorConstants(code, message); 68 | } 69 | 70 | 71 | @Override 72 | public String getCode() { 73 | return this.code; 74 | } 75 | 76 | /** 77 | * Return the reason phrase of this status credential. 78 | */ 79 | @Override 80 | public String getMessage() { 81 | return this.message; 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /framework/framework-core/src/main/java/com/honvay/cola/framework/core/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package com.honvay.cola.framework.core; 2 | 3 | /** 4 | * @author LIQIU 5 | * created on 2018/12/24 6 | **/ 7 | public interface ErrorMessage { 8 | 9 | /** 10 | * 获取错误码 11 | * 12 | * @return 错误码 13 | */ 14 | String getCode(); 15 | 16 | /** 17 | * 获取错误信息 18 | * 19 | * @return 错误信息 20 | */ 21 | String getMessage(); 22 | 23 | } 24 | -------------------------------------------------------------------------------- /framework/framework-core/src/main/java/com/honvay/cola/framework/core/enums/GeneralEnum.java: -------------------------------------------------------------------------------- 1 | package com.honvay.cola.framework.core.enums; 2 | 3 | public interface GeneralEnum { 4 | 5 | T getCode(); 6 | String getName(); 7 | 8 | } 9 | -------------------------------------------------------------------------------- /framework/framework-core/src/main/java/com/honvay/cola/framework/core/exception/ServiceException.java: -------------------------------------------------------------------------------- 1 | package com.honvay.cola.framework.core.exception; 2 | 3 | 4 | import com.honvay.cola.framework.core.ErrorMessage; 5 | 6 | /** 7 | * @author LIQIU 8 | * created on 2018/12/24 9 | **/ 10 | public class ServiceException extends RuntimeException { 11 | 12 | private String code; 13 | 14 | public ServiceException(String code, String message, Throwable throwable) { 15 | super(message, throwable); 16 | this.code = code; 17 | } 18 | 19 | public ServiceException(String code, String message) { 20 | this(code, message, null); 21 | } 22 | 23 | public ServiceException(String message) { 24 | this("0", message, null); 25 | } 26 | 27 | public ServiceException(ErrorMessage errorMessage) { 28 | this(errorMessage.getCode(), errorMessage.getMessage(), null); 29 | } 30 | 31 | public ServiceException(ErrorMessage errorMessage, Throwable throwable) { 32 | this(errorMessage.getCode(), errorMessage.getMessage(), throwable); 33 | } 34 | 35 | public ServiceException(ErrorMessage errorMessage, String message) { 36 | this(errorMessage.getCode(), message, null); 37 | } 38 | 39 | public ServiceException(ErrorMessage errorMessage, String message, Throwable throwable) { 40 | this(errorMessage.getCode(), message, throwable); 41 | } 42 | 43 | public String getCode() { 44 | return code; 45 | } 46 | 47 | public void setCode(String code) { 48 | this.code = code; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /framework/framework-web/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.honvay 8 | framework 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | framework-web 13 | 14 | 15 | 17 16 | 17 17 | UTF-8 18 | 19 | 20 | 21 | 22 | org.springframework.boot 23 | spring-boot-starter-web 24 | 25 | 26 | com.honvay 27 | framework-core 28 | 29 | 30 | -------------------------------------------------------------------------------- /framework/framework-web/src/main/java/com/honvay/cola/framework/web/ResponsePayload.java: -------------------------------------------------------------------------------- 1 | package com.honvay.cola.framework.web; 2 | 3 | 4 | import java.lang.annotation.*; 5 | 6 | /** 7 | * @author LIQIU 8 | * created on 2020/9/15 9 | **/ 10 | @Target({ElementType.METHOD,ElementType.TYPE}) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | @Documented 13 | public @interface ResponsePayload { 14 | } 15 | -------------------------------------------------------------------------------- /framework/framework-web/src/main/java/com/honvay/cola/framework/web/ResponsePayloadAdvise.java: -------------------------------------------------------------------------------- 1 | package com.honvay.cola.framework.web; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.honvay.cola.framework.core.protocol.Response; 6 | import jakarta.annotation.Resource; 7 | import org.springframework.core.MethodParameter; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.http.converter.HttpMessageConverter; 10 | import org.springframework.http.server.ServerHttpRequest; 11 | import org.springframework.http.server.ServerHttpResponse; 12 | import org.springframework.web.bind.annotation.RestControllerAdvice; 13 | import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; 14 | 15 | 16 | /** 17 | * @author LIQIU 18 | * created on 2019/1/14 19 | **/ 20 | @RestControllerAdvice 21 | public class ResponsePayloadAdvise implements ResponseBodyAdvice { 22 | 23 | @Resource 24 | private ObjectMapper objectMapper; 25 | 26 | @Override 27 | public boolean supports(MethodParameter returnType, Class> converterType) { 28 | return returnType.hasMethodAnnotation(ResponsePayload.class) 29 | || returnType.getContainingClass().getAnnotation(ResponsePayload.class) != null; 30 | } 31 | 32 | @Override 33 | public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { 34 | if (body instanceof String || body == null) { 35 | try { 36 | response.getHeaders().add("Content-Type",MediaType.APPLICATION_JSON_VALUE); 37 | return objectMapper.writeValueAsString(Response.success(body)); 38 | } catch (JsonProcessingException e) { 39 | throw new RuntimeException(e); 40 | } 41 | } 42 | 43 | if(body instanceof Response){ 44 | return body; 45 | } 46 | return Response.success(body); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /framework/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.honvay 8 | flychat 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | framework 13 | pom 14 | 15 | framework-core 16 | framework-web 17 | 18 | 19 | 20 | 17 21 | 17 22 | UTF-8 23 | 24 | 25 | -------------------------------------------------------------------------------- /frontend/.env.development: -------------------------------------------------------------------------------- 1 | VITE_API_BASE_URL= 'http://localhost:8080' -------------------------------------------------------------------------------- /frontend/.env.production: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leecho/flychat/d238fbd2a60673243686764d8aaff3ae956177af/frontend/.env.production -------------------------------------------------------------------------------- /frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | /*.json 2 | /*.js 3 | dist -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | node_modules 7 | .DS_Store 8 | dist 9 | dist-ssr 10 | *.local 11 | .idea 12 | -------------------------------------------------------------------------------- /frontend/.prettierignore: -------------------------------------------------------------------------------- 1 | /dist/* 2 | .local 3 | .output.js 4 | /node_modules/** 5 | 6 | **/*.svg 7 | **/*.sh -------------------------------------------------------------------------------- /frontend/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tabWidth: 2, 3 | semi: true, 4 | printWidth: 80, 5 | singleQuote: true, 6 | quoteProps: 'consistent', 7 | htmlWhitespaceSensitivity: 'strict', 8 | vueIndentScriptAndStyle: true, 9 | }; 10 | -------------------------------------------------------------------------------- /frontend/.stylelintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: [ 3 | 'stylelint-config-standard', 4 | 'stylelint-config-rational-order', 5 | 'stylelint-config-prettier', 6 | 'stylelint-config-recommended-vue', 7 | ], 8 | defaultSeverity: 'warning', 9 | plugins: ['stylelint-order'], 10 | rules: { 11 | 'at-rule-no-unknown': [ 12 | true, 13 | { 14 | ignoreAtRules: ['plugin'], 15 | }, 16 | ], 17 | 'rule-empty-line-before': [ 18 | 'always', 19 | { 20 | except: ['after-single-line-comment', 'first-nested'], 21 | }, 22 | ], 23 | 'selector-pseudo-class-no-unknown': [ 24 | true, 25 | { 26 | ignorePseudoClasses: ['deep'], 27 | }, 28 | ], 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /frontend/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: ['@vue/babel-plugin-jsx'], 3 | }; 4 | -------------------------------------------------------------------------------- /frontend/commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | }; 4 | -------------------------------------------------------------------------------- /frontend/components.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* prettier-ignore */ 3 | // @ts-nocheck 4 | // Generated by unplugin-vue-components 5 | // Read more: https://github.com/vuejs/core/pull/3399 6 | import '@vue/runtime-core' 7 | 8 | export {}; 9 | 10 | declare module '@vue/runtime-core' { 11 | export interface GlobalComponents { 12 | RouterLink: typeof import('vue-router')['RouterLink']; 13 | RouterView: typeof import('vue-router')['RouterView']; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /frontend/config/plugin/arcoResolver.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * If you use the template method for development, you can use the unplugin-vue-components plugin to enable on-demand loading support. 3 | * 按需引入 4 | * https://github.com/antfu/unplugin-vue-components 5 | * https://arco.design/vue/docs/start 6 | * Although the Pro project is full of imported components, this plugin will be used by default. 7 | * 虽然Pro项目中是全量引入组件,但此插件会默认使用。 8 | */ 9 | import Components from 'unplugin-vue-components/vite'; 10 | import { ArcoResolver } from 'unplugin-vue-components/resolvers'; 11 | 12 | export default function configArcoResolverPlugin() { 13 | const arcoResolverPlugin = Components({ 14 | dirs: [], // Avoid parsing src/components. 避免解析到src/components 15 | deep: false, 16 | resolvers: [ArcoResolver()], 17 | }); 18 | return arcoResolverPlugin; 19 | } 20 | -------------------------------------------------------------------------------- /frontend/config/plugin/arcoStyleImport.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Theme import 3 | * 样式按需引入 4 | * https://github.com/arco-design/arco-plugins/blob/main/packages/plugin-vite-vue/README.md 5 | * https://arco.design/vue/docs/start 6 | */ 7 | import { vitePluginForArco } from '@arco-plugins/vite-vue'; 8 | 9 | export default function configArcoStyleImportPlugin() { 10 | const arcoResolverPlugin = vitePluginForArco({}); 11 | return arcoResolverPlugin; 12 | } 13 | -------------------------------------------------------------------------------- /frontend/config/plugin/compress.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated 3 | * gzip压缩 4 | * https://github.com/anncwb/vite-plugin-compression 5 | */ 6 | import type { Plugin } from 'vite'; 7 | import compressPlugin from 'vite-plugin-compression'; 8 | 9 | export default function configCompressPlugin( 10 | compress: 'gzip' | 'brotli', 11 | deleteOriginFile = false 12 | ): Plugin | Plugin[] { 13 | const plugins: Plugin[] = []; 14 | 15 | if (compress === 'gzip') { 16 | plugins.push( 17 | compressPlugin({ 18 | ext: '.gz', 19 | deleteOriginFile, 20 | }) 21 | ); 22 | } 23 | 24 | if (compress === 'brotli') { 25 | plugins.push( 26 | compressPlugin({ 27 | ext: '.br', 28 | algorithm: 'brotliCompress', 29 | deleteOriginFile, 30 | }) 31 | ); 32 | } 33 | return plugins; 34 | } 35 | -------------------------------------------------------------------------------- /frontend/config/plugin/imagemin.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Image resource files used to compress the output of the production environment 3 | * 图片压缩 4 | * https://github.com/anncwb/vite-plugin-imagemin 5 | */ 6 | import viteImagemin from 'vite-plugin-imagemin'; 7 | 8 | export default function configImageminPlugin() { 9 | const imageminPlugin = viteImagemin({ 10 | gifsicle: { 11 | optimizationLevel: 7, 12 | interlaced: false, 13 | }, 14 | optipng: { 15 | optimizationLevel: 7, 16 | }, 17 | mozjpeg: { 18 | quality: 20, 19 | }, 20 | pngquant: { 21 | quality: [0.8, 0.9], 22 | speed: 4, 23 | }, 24 | svgo: { 25 | plugins: [ 26 | { 27 | name: 'removeViewBox', 28 | }, 29 | { 30 | name: 'removeEmptyAttrs', 31 | active: false, 32 | }, 33 | ], 34 | }, 35 | }); 36 | return imageminPlugin; 37 | } 38 | -------------------------------------------------------------------------------- /frontend/config/plugin/visualizer.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Generation packaging analysis 3 | * 生成打包分析 4 | */ 5 | import visualizer from 'rollup-plugin-visualizer'; 6 | import { isReportMode } from '../utils'; 7 | 8 | export default function configVisualizerPlugin() { 9 | if (isReportMode()) { 10 | return visualizer({ 11 | filename: './node_modules/.cache/visualizer/stats.html', 12 | open: true, 13 | gzipSize: true, 14 | brotliSize: true, 15 | }); 16 | } 17 | return []; 18 | } 19 | -------------------------------------------------------------------------------- /frontend/config/utils/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Whether to generate package preview 3 | * 是否生成打包报告 4 | */ 5 | export default {}; 6 | 7 | export function isReportMode(): boolean { 8 | return process.env.REPORT === 'true'; 9 | } 10 | -------------------------------------------------------------------------------- /frontend/config/vite.config.base.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { defineConfig } from 'vite'; 3 | import vue from '@vitejs/plugin-vue'; 4 | import vueJsx from '@vitejs/plugin-vue-jsx'; 5 | import svgLoader from 'vite-svg-loader'; 6 | import tailwindcss from 'tailwindcss'; 7 | import autoprefixer from 'autoprefixer'; 8 | import configArcoStyleImportPlugin from './plugin/arcoStyleImport'; 9 | 10 | export default defineConfig({ 11 | plugins: [ 12 | vue(), 13 | vueJsx(), 14 | svgLoader({ svgoConfig: {} }), 15 | configArcoStyleImportPlugin(), 16 | ], 17 | resolve: { 18 | alias: [ 19 | { 20 | find: '@', 21 | replacement: resolve(__dirname, '../src'), 22 | }, 23 | { 24 | find: 'assets', 25 | replacement: resolve(__dirname, '../src/assets'), 26 | }, 27 | { 28 | find: 'vue-i18n', 29 | replacement: 'vue-i18n/dist/vue-i18n.cjs.js', // Resolve the i18n warning issue 30 | }, 31 | { 32 | find: 'vue', 33 | replacement: 'vue/dist/vue.esm-bundler.js', // compile template 34 | }, 35 | ], 36 | extensions: ['.ts', '.js'], 37 | }, 38 | define: { 39 | 'process.env': {}, 40 | }, 41 | css: { 42 | preprocessorOptions: { 43 | less: { 44 | modifyVars: { 45 | hack: `true; @import (reference) "${resolve( 46 | 'src/assets/style/breakpoint.less' 47 | )}";`, 48 | }, 49 | javascriptEnabled: true, 50 | }, 51 | }, 52 | postcss: { 53 | plugins: [tailwindcss, autoprefixer], 54 | }, 55 | }, 56 | }); 57 | -------------------------------------------------------------------------------- /frontend/config/vite.config.dev.ts: -------------------------------------------------------------------------------- 1 | import { mergeConfig } from 'vite'; 2 | import eslint from 'vite-plugin-eslint'; 3 | import baseConfig from './vite.config.base'; 4 | 5 | export default mergeConfig( 6 | { 7 | mode: 'development', 8 | server: { 9 | open: true, 10 | fs: { 11 | strict: true, 12 | }, 13 | }, 14 | plugins: [ 15 | eslint({ 16 | cache: false, 17 | include: ['src/**/*.ts', 'src/**/*.tsx', 'src/**/*.vue'], 18 | exclude: ['node_modules'], 19 | }), 20 | ], 21 | }, 22 | baseConfig 23 | ); 24 | -------------------------------------------------------------------------------- /frontend/config/vite.config.prod.ts: -------------------------------------------------------------------------------- 1 | import { mergeConfig } from 'vite'; 2 | import baseConfig from './vite.config.base'; 3 | import configCompressPlugin from './plugin/compress'; 4 | import configVisualizerPlugin from './plugin/visualizer'; 5 | import configArcoResolverPlugin from './plugin/arcoResolver'; 6 | import configImageminPlugin from './plugin/imagemin'; 7 | 8 | export default mergeConfig( 9 | { 10 | mode: 'production', 11 | plugins: [ 12 | configCompressPlugin('gzip'), 13 | configVisualizerPlugin(), 14 | configArcoResolverPlugin(), 15 | configImageminPlugin(), 16 | ], 17 | build: { 18 | rollupOptions: { 19 | output: { 20 | manualChunks: { 21 | arco: ['@arco-design/web-vue'], 22 | chart: ['echarts', 'vue-echarts'], 23 | vue: ['vue', 'vue-router', 'pinia', '@vueuse/core', 'vue-i18n'], 24 | }, 25 | }, 26 | }, 27 | chunkSizeWarningLimit: 2000, 28 | }, 29 | }, 30 | baseConfig 31 | ); 32 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Arco Design Pro - 开箱即用的中台前端/设计解决方案 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /frontend/src/App.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 27 | -------------------------------------------------------------------------------- /frontend/src/api/common.ts: -------------------------------------------------------------------------------- 1 | export interface Response { 2 | success: boolean; 3 | code: number; 4 | message: string; 5 | data: T; 6 | } 7 | -------------------------------------------------------------------------------- /frontend/src/api/dashboard.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import type { TableData } from '@arco-design/web-vue/es/table/interface'; 3 | 4 | export interface ContentDataRecord { 5 | x: string; 6 | y: number; 7 | } 8 | 9 | export function queryContentData() { 10 | return axios.get('/api/content-data'); 11 | } 12 | 13 | export interface PopularRecord { 14 | key: number; 15 | clickNumber: string; 16 | title: string; 17 | increases: number; 18 | } 19 | 20 | export function queryPopularList(params: { type: string }) { 21 | return axios.get('/api/popular/list', { params }); 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/api/list.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import qs from 'query-string'; 3 | import type { DescData } from '@arco-design/web-vue/es/descriptions/interface'; 4 | 5 | export interface PolicyRecord { 6 | id: string; 7 | number: number; 8 | name: string; 9 | contentType: 'img' | 'horizontalVideo' | 'verticalVideo'; 10 | filterType: 'artificial' | 'rules'; 11 | count: number; 12 | status: 'online' | 'offline'; 13 | createdTime: string; 14 | } 15 | 16 | export interface PolicyParams extends Partial { 17 | current: number; 18 | pageSize: number; 19 | } 20 | 21 | export interface PolicyListRes { 22 | list: PolicyRecord[]; 23 | total: number; 24 | } 25 | 26 | export function queryPolicyList(params: PolicyParams) { 27 | return axios.get('/api/list/policy', { 28 | params, 29 | paramsSerializer: (obj) => { 30 | return qs.stringify(obj); 31 | }, 32 | }); 33 | } 34 | 35 | export interface ServiceRecord { 36 | id: number; 37 | title: string; 38 | description: string; 39 | name?: string; 40 | actionType?: string; 41 | icon?: string; 42 | data?: DescData[]; 43 | enable?: boolean; 44 | expires?: boolean; 45 | } 46 | export function queryInspectionList() { 47 | return axios.get('/api/list/quality-inspection'); 48 | } 49 | 50 | export function queryTheServiceList() { 51 | return axios.get('/api/list/the-service'); 52 | } 53 | 54 | export function queryRulesPresetList() { 55 | return axios.get('/api/list/rules-preset'); 56 | } 57 | -------------------------------------------------------------------------------- /frontend/src/api/message.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | export interface MessageRecord { 4 | id: number; 5 | type: string; 6 | title: string; 7 | subTitle: string; 8 | avatar?: string; 9 | content: string; 10 | time: string; 11 | status: 0 | 1; 12 | messageType?: number; 13 | } 14 | export type MessageListType = MessageRecord[]; 15 | 16 | export function queryMessageList() { 17 | return axios.post('/api/message/list'); 18 | } 19 | 20 | interface MessageStatus { 21 | ids: number[]; 22 | } 23 | 24 | export function setMessageStatus(data: MessageStatus) { 25 | return axios.post('/api/message/read', data); 26 | } 27 | 28 | export interface ChatRecord { 29 | id: number; 30 | username: string; 31 | content: string; 32 | time: string; 33 | isCollect: boolean; 34 | } 35 | 36 | export function queryChatList() { 37 | return axios.post('/api/chat/list'); 38 | } 39 | -------------------------------------------------------------------------------- /frontend/src/api/user.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import type { RouteRecordNormalized } from 'vue-router'; 3 | import { UserState } from '@/store/modules/user/types'; 4 | 5 | export interface LoginData { 6 | username: string; 7 | password: string; 8 | } 9 | 10 | export interface LoginRes { 11 | token: string; 12 | } 13 | export function login(data: LoginData) { 14 | return axios.post('/api/user/login', data); 15 | } 16 | 17 | export function logout() { 18 | return axios.post('/api/user/logout'); 19 | } 20 | 21 | export function getUserInfo() { 22 | return axios.post('/api/user/info'); 23 | } 24 | 25 | export function getMenuList() { 26 | return axios.post('/api/user/menu'); 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/assets/images/avatar.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leecho/flychat/d238fbd2a60673243686764d8aaff3ae956177af/frontend/src/assets/images/avatar.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/login-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leecho/flychat/d238fbd2a60673243686764d8aaff3ae956177af/frontend/src/assets/images/login-banner.png -------------------------------------------------------------------------------- /frontend/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /frontend/src/assets/style/breakpoint.less: -------------------------------------------------------------------------------- 1 | // ==============breakpoint============ 2 | 3 | // Extra small screen / phone 4 | @screen-xs: 480px; 5 | 6 | // Small screen / tablet 7 | @screen-sm: 576px; 8 | 9 | // Medium screen / desktop 10 | @screen-md: 768px; 11 | 12 | // Large screen / wide desktop 13 | @screen-lg: 992px; 14 | 15 | // Extra large screen / full hd 16 | @screen-xl: 1200px; 17 | 18 | // Extra extra large screen / large desktop 19 | @screen-xxl: 1600px; 20 | -------------------------------------------------------------------------------- /frontend/src/assets/style/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /frontend/src/components/breadcrumb/index.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 24 | 25 | 36 | -------------------------------------------------------------------------------- /frontend/src/components/chart/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /frontend/src/components/common/SvgIcon/index.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 22 | -------------------------------------------------------------------------------- /frontend/src/components/footer/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 17 | -------------------------------------------------------------------------------- /frontend/src/components/global-setting/block.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 61 | 62 | 80 | -------------------------------------------------------------------------------- /frontend/src/components/global-setting/form-wrapper.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 40 | -------------------------------------------------------------------------------- /frontend/src/components/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue'; 2 | import { use } from 'echarts/core'; 3 | import { CanvasRenderer } from 'echarts/renderers'; 4 | import { BarChart, LineChart, PieChart, RadarChart } from 'echarts/charts'; 5 | import { 6 | GridComponent, 7 | TooltipComponent, 8 | LegendComponent, 9 | DataZoomComponent, 10 | GraphicComponent, 11 | } from 'echarts/components'; 12 | import SvgIcon from './common/SvgIcon/index.vue'; 13 | import Chart from './chart/index.vue'; 14 | import Breadcrumb from './breadcrumb/index.vue'; 15 | 16 | // Manually introduce ECharts modules to reduce packing size 17 | 18 | use([ 19 | CanvasRenderer, 20 | BarChart, 21 | LineChart, 22 | PieChart, 23 | RadarChart, 24 | GridComponent, 25 | TooltipComponent, 26 | LegendComponent, 27 | DataZoomComponent, 28 | GraphicComponent, 29 | ]); 30 | 31 | export default { 32 | install(Vue: App) { 33 | Vue.component('Chart', Chart); 34 | Vue.component('Breadcrumb', Breadcrumb); 35 | Vue.component('SvgIcon', SvgIcon); 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /frontend/src/components/menu/use-menu-tree.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import { RouteRecordRaw, RouteRecordNormalized } from 'vue-router'; 3 | import usePermission from '@/hooks/permission'; 4 | import { useAppStore } from '@/store'; 5 | import appClientMenus from '@/router/app-menus'; 6 | import { cloneDeep } from 'lodash'; 7 | 8 | export default function useMenuTree() { 9 | const permission = usePermission(); 10 | const appStore = useAppStore(); 11 | const appRoute = computed(() => { 12 | if (appStore.menuFromServer) { 13 | return appStore.appAsyncMenus; 14 | } 15 | return appClientMenus; 16 | }); 17 | const menuTree = computed(() => { 18 | const copyRouter = cloneDeep(appRoute.value) as RouteRecordNormalized[]; 19 | copyRouter.sort((a: RouteRecordNormalized, b: RouteRecordNormalized) => { 20 | return (a.meta.order || 0) - (b.meta.order || 0); 21 | }); 22 | function travel(_routes: RouteRecordRaw[], layer: number) { 23 | if (!_routes) return null; 24 | 25 | const collector: any = _routes.map((element) => { 26 | // no access 27 | if (!permission.accessRouter(element)) { 28 | return null; 29 | } 30 | 31 | // leaf node 32 | if (element.meta?.hideChildrenInMenu || !element.children) { 33 | element.children = []; 34 | return element; 35 | } 36 | 37 | // route filter hideInMenu true 38 | element.children = element.children.filter( 39 | (x) => x.meta?.hideInMenu !== true 40 | ); 41 | 42 | // Associated child node 43 | const subItem = travel(element.children, layer + 1); 44 | 45 | if (subItem.length) { 46 | element.children = subItem; 47 | return element; 48 | } 49 | // the else logic 50 | if (layer > 1) { 51 | element.children = subItem; 52 | return element; 53 | } 54 | 55 | if (element.meta?.hideInMenu === false) { 56 | return element; 57 | } 58 | 59 | return null; 60 | }); 61 | return collector.filter(Boolean); 62 | } 63 | return travel(copyRouter, 0); 64 | }); 65 | 66 | return { 67 | menuTree, 68 | }; 69 | } 70 | -------------------------------------------------------------------------------- /frontend/src/components/message-box/locale/en-US.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'messageBox.tab.title.message': 'Message', 3 | 'messageBox.tab.title.notice': 'Notice', 4 | 'messageBox.tab.title.todo': 'Todo', 5 | 'messageBox.tab.button': 'empty', 6 | 'messageBox.allRead': 'All Read', 7 | 'messageBox.viewMore': 'View More', 8 | 'messageBox.noContent': 'No Content', 9 | 'messageBox.switchRoles': 'Switch Roles', 10 | 'messageBox.userCenter': 'User Center', 11 | 'messageBox.userSettings': 'User Settings', 12 | 'messageBox.logout': 'Logout', 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/src/components/message-box/locale/zh-CN.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'messageBox.tab.title.message': '消息', 3 | 'messageBox.tab.title.notice': '通知', 4 | 'messageBox.tab.title.todo': '待办', 5 | 'messageBox.tab.button': '清空', 6 | 'messageBox.allRead': '全部已读', 7 | 'messageBox.viewMore': '查看更多', 8 | 'messageBox.noContent': '暂无内容', 9 | 'messageBox.switchRoles': '切换角色', 10 | 'messageBox.userCenter': '用户中心', 11 | 'messageBox.userSettings': '用户设置', 12 | 'messageBox.logout': '登出登录', 13 | }; 14 | -------------------------------------------------------------------------------- /frontend/src/components/tab-bar/readme.md: -------------------------------------------------------------------------------- 1 | ## 组件说明 2 | 3 | 该组件非官方最终设计规范,以单独组件存在。 4 | 5 | 同时仅仅提供最基本的功能,后续进行优化及更改。 6 | 7 | 8 | ## Component description 9 | 10 | The component unofficial final design specification exists as a separate component. 11 | 12 | At the same time, only the most basic functions are provided, and subsequent optimizations and changes will be made. -------------------------------------------------------------------------------- /frontend/src/config/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "theme": "light", 3 | "colorWeak": false, 4 | "navbar": true, 5 | "menu": true, 6 | "topMenu": false, 7 | "hideMenu": false, 8 | "menuCollapse": true, 9 | "footer": true, 10 | "themeColor": "#165DFF", 11 | "menuWidth": 220, 12 | "globalSettings": false, 13 | "device": "desktop", 14 | "tabBar": false, 15 | "menuFromServer": false, 16 | "serverMenu": [] 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/directive/index.ts: -------------------------------------------------------------------------------- 1 | import { App } from 'vue'; 2 | import permission from './permission'; 3 | 4 | export default { 5 | install(Vue: App) { 6 | Vue.directive('permission', permission); 7 | }, 8 | }; 9 | -------------------------------------------------------------------------------- /frontend/src/directive/permission/index.ts: -------------------------------------------------------------------------------- 1 | import { DirectiveBinding } from 'vue'; 2 | import { useUserStore } from '@/store'; 3 | 4 | function checkPermission(el: HTMLElement, binding: DirectiveBinding) { 5 | const { value } = binding; 6 | const userStore = useUserStore(); 7 | const { role } = userStore; 8 | 9 | if (Array.isArray(value)) { 10 | if (value.length > 0) { 11 | const permissionValues = value; 12 | 13 | const hasPermission = permissionValues.includes(role); 14 | if (!hasPermission && el.parentNode) { 15 | el.parentNode.removeChild(el); 16 | } 17 | } 18 | } else { 19 | throw new Error(`need roles! Like v-permission="['admin','user']"`); 20 | } 21 | } 22 | 23 | export default { 24 | mounted(el: HTMLElement, binding: DirectiveBinding) { 25 | checkPermission(el, binding); 26 | }, 27 | updated(el: HTMLElement, binding: DirectiveBinding) { 28 | checkPermission(el, binding); 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /frontend/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import { DefineComponent } from 'vue'; 5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types 6 | const component: DefineComponent<{}, {}, any>; 7 | export default component; 8 | } 9 | interface ImportMetaEnv { 10 | readonly VITE_API_BASE_URL: string; 11 | } 12 | -------------------------------------------------------------------------------- /frontend/src/hooks/chart-option.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import { EChartsOption } from 'echarts'; 3 | import { useAppStore } from '@/store'; 4 | 5 | // for code hints 6 | // import { SeriesOption } from 'echarts'; 7 | // Because there are so many configuration items, this provides a relatively convenient code hint. 8 | // When using vue, pay attention to the reactive issues. It is necessary to ensure that corresponding functions can be triggered, TypeScript does not report errors, and code writing is convenient. 9 | interface optionsFn { 10 | (isDark: boolean): EChartsOption; 11 | } 12 | 13 | export default function useChartOption(sourceOption: optionsFn) { 14 | const appStore = useAppStore(); 15 | const isDark = computed(() => { 16 | return appStore.theme === 'dark'; 17 | }); 18 | // echarts support https://echarts.apache.org/zh/theme-builder.html 19 | // It's not used here 20 | // TODO echarts themes 21 | const chartOption = computed(() => { 22 | return sourceOption(isDark.value); 23 | }); 24 | return { 25 | chartOption, 26 | }; 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/hooks/icon-render.ts: -------------------------------------------------------------------------------- 1 | import { h } from 'vue'; 2 | import SvgIcon from '@/components/common/SvgIcon/index.vue'; 3 | 4 | // eslint-disable-next-line import/prefer-default-export 5 | export const useIconRender = () => { 6 | interface IconConfig { 7 | icon?: string; 8 | color?: string; 9 | fontSize?: number; 10 | } 11 | 12 | interface IconStyle { 13 | color?: string; 14 | fontSize?: string; 15 | } 16 | 17 | const iconRender = (config: IconConfig) => { 18 | const { color, fontSize, icon } = config; 19 | 20 | const style: IconStyle = {}; 21 | 22 | if (color) style.color = color; 23 | 24 | if (fontSize) style.fontSize = `${fontSize}px`; 25 | 26 | if (!icon) window.console.warn('iconRender: icon is required'); 27 | 28 | return () => h(SvgIcon, { icon, style }); 29 | }; 30 | 31 | return { 32 | iconRender, 33 | }; 34 | }; 35 | -------------------------------------------------------------------------------- /frontend/src/hooks/layout.ts: -------------------------------------------------------------------------------- 1 | import { breakpointsTailwind, useBreakpoints } from '@vueuse/core'; 2 | 3 | const useLayout = () => { 4 | const breakpoints = useBreakpoints(breakpointsTailwind); 5 | const isMobile = breakpoints.smaller('sm'); 6 | 7 | return { isMobile }; 8 | }; 9 | export default useLayout; 10 | -------------------------------------------------------------------------------- /frontend/src/hooks/loading.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | 3 | export default function useLoading(initValue = false) { 4 | const loading = ref(initValue); 5 | const setLoading = (value: boolean) => { 6 | loading.value = value; 7 | }; 8 | const toggle = () => { 9 | loading.value = !loading.value; 10 | }; 11 | return { 12 | loading, 13 | setLoading, 14 | toggle, 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/hooks/locale.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import { useI18n } from 'vue-i18n'; 3 | import { Message } from '@arco-design/web-vue'; 4 | 5 | export default function useLocale() { 6 | const i18 = useI18n(); 7 | const currentLocale = computed(() => { 8 | return i18.locale.value; 9 | }); 10 | const changeLocale = (value: string) => { 11 | if (i18.locale.value === value) { 12 | return; 13 | } 14 | i18.locale.value = value; 15 | localStorage.setItem('arco-locale', value); 16 | Message.success(i18.t('navbar.action.locale')); 17 | }; 18 | return { 19 | currentLocale, 20 | changeLocale, 21 | }; 22 | } 23 | -------------------------------------------------------------------------------- /frontend/src/hooks/permission.ts: -------------------------------------------------------------------------------- 1 | import { RouteLocationNormalized, RouteRecordRaw } from 'vue-router'; 2 | import { useUserStore } from '@/store'; 3 | 4 | export default function usePermission() { 5 | const userStore = useUserStore(); 6 | return { 7 | accessRouter(route: RouteLocationNormalized | RouteRecordRaw) { 8 | return ( 9 | !route.meta?.requiresAuth || 10 | !route.meta?.roles || 11 | route.meta?.roles?.includes('*') || 12 | route.meta?.roles?.includes(userStore.role) 13 | ); 14 | }, 15 | findFirstPermissionRoute(_routers: any, role = 'admin') { 16 | const cloneRouters = [..._routers]; 17 | while (cloneRouters.length) { 18 | const firstElement = cloneRouters.shift(); 19 | if ( 20 | firstElement?.meta?.roles?.find((el: string[]) => { 21 | return el.includes('*') || el.includes(role); 22 | }) 23 | ) 24 | return { name: firstElement.name }; 25 | if (firstElement?.children) { 26 | cloneRouters.push(...firstElement.children); 27 | } 28 | } 29 | return null; 30 | }, 31 | // You can add any rules you want 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /frontend/src/hooks/request.ts: -------------------------------------------------------------------------------- 1 | import { ref, UnwrapRef } from 'vue'; 2 | import { AxiosResponse } from 'axios'; 3 | import { HttpResponse } from '@/api/interceptor'; 4 | import useLoading from './loading'; 5 | 6 | // use to fetch list 7 | // Don't use async function. It doesn't work in async function. 8 | // Use the bind function to add parameters 9 | // example: useRequest(api.bind(null, {})) 10 | 11 | export default function useRequest( 12 | api: () => Promise>, 13 | defaultValue = [] as unknown as T, 14 | isLoading = true 15 | ) { 16 | const { loading, setLoading } = useLoading(isLoading); 17 | const response = ref(defaultValue); 18 | api() 19 | .then((res) => { 20 | response.value = res.data as unknown as UnwrapRef; 21 | }) 22 | .finally(() => { 23 | setLoading(false); 24 | }); 25 | return { loading, response }; 26 | } 27 | -------------------------------------------------------------------------------- /frontend/src/hooks/responsive.ts: -------------------------------------------------------------------------------- 1 | import { onMounted, onBeforeMount, onBeforeUnmount } from 'vue'; 2 | import { useDebounceFn } from '@vueuse/core'; 3 | import { useAppStore } from '@/store'; 4 | import { addEventListen, removeEventListen } from '@/utils/event'; 5 | 6 | const WIDTH = 992; // https://arco.design/vue/component/grid#responsivevalue 7 | 8 | function queryDevice() { 9 | const rect = document.body.getBoundingClientRect(); 10 | return rect.width - 1 < WIDTH; 11 | } 12 | 13 | export default function useResponsive(immediate?: boolean) { 14 | const appStore = useAppStore(); 15 | function resizeHandler() { 16 | if (!document.hidden) { 17 | const isMobile = queryDevice(); 18 | appStore.toggleDevice(isMobile ? 'mobile' : 'desktop'); 19 | appStore.toggleMenu(isMobile); 20 | } 21 | } 22 | const debounceFn = useDebounceFn(resizeHandler, 100); 23 | onMounted(() => { 24 | if (immediate) debounceFn(); 25 | }); 26 | onBeforeMount(() => { 27 | addEventListen(window, 'resize', debounceFn); 28 | }); 29 | onBeforeUnmount(() => { 30 | removeEventListen(window, 'resize', debounceFn); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /frontend/src/hooks/scroll.ts: -------------------------------------------------------------------------------- 1 | import type { Ref } from 'vue'; 2 | import { nextTick, ref } from 'vue'; 3 | 4 | type ScrollElement = HTMLDivElement | null; 5 | 6 | interface ScrollReturn { 7 | scrollRef: Ref; 8 | scrollToBottom: () => Promise; 9 | scrollToTop: () => Promise; 10 | scrollToBottomIfAtBottom: () => Promise; 11 | } 12 | 13 | export default function useScroll(): ScrollReturn { 14 | const scrollRef = ref(null); 15 | 16 | const scrollToBottom = async () => { 17 | await nextTick(); 18 | if (scrollRef.value) 19 | scrollRef.value.scrollTop = scrollRef.value.scrollHeight; 20 | }; 21 | 22 | const scrollToTop = async () => { 23 | await nextTick(); 24 | if (scrollRef.value) scrollRef.value.scrollTop = 0; 25 | }; 26 | 27 | const scrollToBottomIfAtBottom = async () => { 28 | await nextTick(); 29 | if (scrollRef.value) { 30 | const threshold = 100; // 阈值,表示滚动条到底部的距离阈值 31 | const distanceToBottom = 32 | scrollRef.value.scrollHeight - 33 | scrollRef.value.scrollTop - 34 | scrollRef.value.clientHeight; 35 | if (distanceToBottom <= threshold) 36 | scrollRef.value.scrollTop = scrollRef.value.scrollHeight; 37 | } 38 | }; 39 | 40 | return { 41 | scrollRef, 42 | scrollToBottom, 43 | scrollToTop, 44 | scrollToBottomIfAtBottom, 45 | }; 46 | } 47 | -------------------------------------------------------------------------------- /frontend/src/hooks/themes.ts: -------------------------------------------------------------------------------- 1 | import { computed } from 'vue'; 2 | import { useAppStore } from '@/store'; 3 | 4 | export default function useThemes() { 5 | const appStore = useAppStore(); 6 | const isDark = computed(() => { 7 | return appStore.theme === 'dark'; 8 | }); 9 | return { 10 | isDark, 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/hooks/user.ts: -------------------------------------------------------------------------------- 1 | import { useRouter } from 'vue-router'; 2 | import { Message } from '@arco-design/web-vue'; 3 | 4 | import { useUserStore } from '@/store'; 5 | 6 | export default function useUser() { 7 | const router = useRouter(); 8 | const userStore = useUserStore(); 9 | const logout = async (logoutTo?: string) => { 10 | await userStore.logout(); 11 | const currentRoute = router.currentRoute.value; 12 | Message.success('登出成功'); 13 | router.push({ 14 | name: logoutTo && typeof logoutTo === 'string' ? logoutTo : 'login', 15 | query: { 16 | ...router.currentRoute.value.query, 17 | redirect: currentRoute.name as string, 18 | }, 19 | }); 20 | }; 21 | return { 22 | logout, 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /frontend/src/hooks/visible.ts: -------------------------------------------------------------------------------- 1 | import { ref } from 'vue'; 2 | 3 | export default function useVisible(initValue = false) { 4 | const visible = ref(initValue); 5 | const setVisible = (value: boolean) => { 6 | visible.value = value; 7 | }; 8 | const toggle = () => { 9 | visible.value = !visible.value; 10 | }; 11 | return { 12 | visible, 13 | setVisible, 14 | toggle, 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/layout/page-layout.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /frontend/src/locale/en-US.ts: -------------------------------------------------------------------------------- 1 | import localeMessageBox from '@/components/message-box/locale/en-US'; 2 | import localeLogin from '@/views/login/locale/en-US'; 3 | 4 | import localeWorkplace from '@/views/dashboard/workplace/locale/en-US'; 5 | 6 | import localeSettings from './en-US/settings'; 7 | 8 | export default { 9 | 'menu.dashboard': 'Dashboard', 10 | 'menu.server.dashboard': 'Dashboard-Server', 11 | 'menu.server.workplace': 'Workplace-Server', 12 | 'menu.server.monitor': 'Monitor-Server', 13 | 'menu.list': 'List', 14 | 'menu.result': 'Result', 15 | 'menu.exception': 'Exception', 16 | 'menu.form': 'Form', 17 | 'menu.profile': 'Profile', 18 | 'menu.visualization': 'Data Visualization', 19 | 'menu.user': 'User Center', 20 | 'menu.arcoWebsite': 'Arco Design', 21 | 'menu.faq': 'FAQ', 22 | 'navbar.docs': 'Docs', 23 | 'navbar.action.locale': 'Switch to English', 24 | ...localeSettings, 25 | ...localeMessageBox, 26 | ...localeLogin, 27 | ...localeWorkplace, 28 | }; 29 | -------------------------------------------------------------------------------- /frontend/src/locale/en-US/settings.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'settings.title': 'Settings', 3 | 'settings.themeColor': 'Theme Color', 4 | 'settings.content': 'Content Setting', 5 | 'settings.search': 'Search', 6 | 'settings.language': 'Language', 7 | 'settings.navbar': 'Navbar', 8 | 'settings.menuWidth': 'Menu Width (px)', 9 | 'settings.navbar.theme.toLight': 'Click to use light mode', 10 | 'settings.navbar.theme.toDark': 'Click to use dark mode', 11 | 'settings.navbar.screen.toFull': 'Click to switch to full screen mode', 12 | 'settings.navbar.screen.toExit': 'Click to exit the full screen mode', 13 | 'settings.navbar.alerts': 'alerts', 14 | 'settings.menu': 'Menu', 15 | 'settings.topMenu': 'Top Menu', 16 | 'settings.tabBar': 'Tab Bar', 17 | 'settings.footer': 'Footer', 18 | 'settings.otherSettings': 'Other Settings', 19 | 'settings.colorWeak': 'Color Weak', 20 | 'settings.alertContent': 21 | 'After the configuration is only temporarily effective, if you want to really affect the project, click the "Copy Settings" button below and replace the configuration in settings.json.', 22 | 'settings.copySettings': 'Copy Settings', 23 | 'settings.copySettings.message': 24 | 'Copy succeeded, please paste to file src/settings.json.', 25 | 'settings.close': 'Close', 26 | 'settings.color.tooltip': 27 | '10 gradient colors generated according to the theme color', 28 | 'settings.menuFromServer': 'Menu From Server', 29 | }; 30 | -------------------------------------------------------------------------------- /frontend/src/locale/index.ts: -------------------------------------------------------------------------------- 1 | import { createI18n } from 'vue-i18n'; 2 | import en from './en-US'; 3 | import cn from './zh-CN'; 4 | 5 | export const LOCALE_OPTIONS = [ 6 | { label: '中文', value: 'zh-CN' }, 7 | { label: 'English', value: 'en-US' }, 8 | ]; 9 | const defaultLocale = localStorage.getItem('arco-locale') || 'zh-CN'; 10 | const i18n = createI18n({ 11 | locale: defaultLocale, 12 | fallbackLocale: 'en-US', 13 | legacy: false, 14 | allowComposition: true, 15 | messages: { 16 | 'en-US': en, 17 | 'zh-CN': cn, 18 | }, 19 | }); 20 | export const { t } = i18n.global; 21 | 22 | export default i18n; 23 | -------------------------------------------------------------------------------- /frontend/src/locale/zh-CN/settings.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'settings.title': '页面配置', 3 | 'settings.themeColor': '主题色', 4 | 'settings.content': '内容区域', 5 | 'settings.search': '搜索', 6 | 'settings.language': '语言', 7 | 'settings.navbar': '导航栏', 8 | 'settings.menuWidth': '菜单宽度 (px)', 9 | 'settings.navbar.theme.toLight': '点击切换为亮色模式', 10 | 'settings.navbar.theme.toDark': '点击切换为暗黑模式', 11 | 'settings.navbar.screen.toFull': '点击切换全屏模式', 12 | 'settings.navbar.screen.toExit': '点击退出全屏模式', 13 | 'settings.navbar.alerts': '消息通知', 14 | 'settings.menu': '菜单栏', 15 | 'settings.topMenu': '顶部菜单栏', 16 | 'settings.tabBar': '多页签', 17 | 'settings.footer': '底部', 18 | 'settings.otherSettings': '其他设置', 19 | 'settings.colorWeak': '色弱模式', 20 | 'settings.alertContent': 21 | '配置之后仅是临时生效,要想真正作用于项目,点击下方的 "复制配置" 按钮,将配置替换到 settings.json 中即可。', 22 | 'settings.copySettings': '复制配置', 23 | 'settings.copySettings.message': 24 | '复制成功,请粘贴到 src/settings.json 文件中', 25 | 'settings.close': '关闭', 26 | 'settings.color.tooltip': 27 | '根据主题颜色生成的 10 个梯度色(将配置复制到项目中,主题色才能对亮色 / 暗黑模式同时生效)', 28 | 'settings.menuFromServer': '菜单来源于后台', 29 | }; 30 | -------------------------------------------------------------------------------- /frontend/src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import ArcoVue, { Message } from '@arco-design/web-vue'; 3 | import ArcoVueIcon from '@arco-design/web-vue/es/icon'; 4 | import 'tailwindcss/tailwind.css'; 5 | import globalComponents from '@/components'; 6 | import router from './router'; 7 | import store from './store'; 8 | import i18n from './locale'; 9 | import directive from './directive'; 10 | import './mock'; 11 | import App from './App.vue'; 12 | import '@arco-themes/vue-hsbr/css/arco.css'; 13 | import '@/assets/style/highlight.less'; 14 | import '@/assets/style/github-markdown.less'; 15 | import 'katex/dist/katex.min.css'; 16 | import '@/assets/style/global.less'; 17 | import '@/api/interceptor'; 18 | 19 | const app = createApp(App); 20 | 21 | app.use(ArcoVue, {}); 22 | app.use(ArcoVueIcon); 23 | // eslint-disable-next-line no-underscore-dangle 24 | Message._context = app._context; 25 | 26 | app.use(router); 27 | app.use(store); 28 | app.use(i18n); 29 | app.use(globalComponents); 30 | app.use(directive); 31 | 32 | app.mount('#app'); 33 | -------------------------------------------------------------------------------- /frontend/src/mock/index.ts: -------------------------------------------------------------------------------- 1 | import Mock from 'mockjs'; 2 | 3 | import './user'; 4 | import './message-box'; 5 | import '@/views/knowledge/mock'; 6 | import '@/views/dashboard/workplace/mock'; 7 | 8 | Mock.setup({ 9 | timeout: '600-1000', 10 | }); 11 | -------------------------------------------------------------------------------- /frontend/src/router/app-menus/index.ts: -------------------------------------------------------------------------------- 1 | import { appRoutes, appExternalRoutes } from '../routes'; 2 | 3 | const mixinRoutes = [...appRoutes, ...appExternalRoutes]; 4 | 5 | const appClientMenus = mixinRoutes.map((el) => { 6 | const { name, path, meta, redirect, children } = el; 7 | return { 8 | name, 9 | path, 10 | meta, 11 | redirect, 12 | children, 13 | }; 14 | }); 15 | 16 | export default appClientMenus; 17 | -------------------------------------------------------------------------------- /frontend/src/router/constants.ts: -------------------------------------------------------------------------------- 1 | export const WHITE_LIST = [ 2 | { name: 'notFound', children: [] }, 3 | { name: 'login', children: [] }, 4 | ]; 5 | 6 | export const NOT_FOUND = { 7 | name: 'notFound', 8 | }; 9 | 10 | export const REDIRECT_ROUTE_NAME = 'Redirect'; 11 | 12 | export const DEFAULT_ROUTE_NAME = 'Workplace'; 13 | 14 | export const DEFAULT_ROUTE = { 15 | title: 'menu.dashboard.workplace', 16 | name: DEFAULT_ROUTE_NAME, 17 | fullPath: '/dashboard/workplace', 18 | }; 19 | -------------------------------------------------------------------------------- /frontend/src/router/guard/index.ts: -------------------------------------------------------------------------------- 1 | import type { Router } from 'vue-router'; 2 | import { setRouteEmitter } from '@/utils/route-listener'; 3 | import setupUserLoginInfoGuard from './userLoginInfo'; 4 | import setupPermissionGuard from './permission'; 5 | 6 | function setupPageGuard(router: Router) { 7 | router.beforeEach(async (to) => { 8 | // emit route change 9 | setRouteEmitter(to); 10 | }); 11 | } 12 | 13 | export default function createRouteGuard(router: Router) { 14 | setupPageGuard(router); 15 | setupUserLoginInfoGuard(router); 16 | setupPermissionGuard(router); 17 | } 18 | -------------------------------------------------------------------------------- /frontend/src/router/guard/permission.ts: -------------------------------------------------------------------------------- 1 | import type { Router, RouteRecordNormalized } from 'vue-router'; 2 | import NProgress from 'nprogress'; // progress bar 3 | 4 | import usePermission from '@/hooks/permission'; 5 | import { useUserStore, useAppStore } from '@/store'; 6 | import { appRoutes } from '../routes'; 7 | import { WHITE_LIST, NOT_FOUND } from '../constants'; 8 | 9 | export default function setupPermissionGuard(router: Router) { 10 | router.beforeEach(async (to, from, next) => { 11 | const appStore = useAppStore(); 12 | const userStore = useUserStore(); 13 | const Permission = usePermission(); 14 | const permissionsAllow = Permission.accessRouter(to); 15 | if (appStore.menuFromServer) { 16 | // 针对来自服务端的菜单配置进行处理 17 | // Handle routing configuration from the server 18 | 19 | // 根据需要自行完善来源于服务端的菜单配置的permission逻辑 20 | // Refine the permission logic from the server's menu configuration as needed 21 | if ( 22 | !appStore.appAsyncMenus.length && 23 | !WHITE_LIST.find((el) => el.name === to.name) 24 | ) { 25 | await appStore.fetchServerMenuConfig(); 26 | } 27 | const serverMenuConfig = [...appStore.appAsyncMenus, ...WHITE_LIST]; 28 | 29 | let exist = false; 30 | while (serverMenuConfig.length && !exist) { 31 | const element = serverMenuConfig.shift(); 32 | if (element?.name === to.name) exist = true; 33 | 34 | if (element?.children) { 35 | serverMenuConfig.push( 36 | ...(element.children as unknown as RouteRecordNormalized[]) 37 | ); 38 | } 39 | } 40 | if (exist && permissionsAllow) { 41 | next(); 42 | } else next(NOT_FOUND); 43 | } else { 44 | // eslint-disable-next-line no-lonely-if 45 | if (permissionsAllow) next(); 46 | else { 47 | const destination = 48 | Permission.findFirstPermissionRoute(appRoutes, userStore.role) || 49 | NOT_FOUND; 50 | next(destination); 51 | } 52 | } 53 | NProgress.done(); 54 | }); 55 | } 56 | -------------------------------------------------------------------------------- /frontend/src/router/guard/userLoginInfo.ts: -------------------------------------------------------------------------------- 1 | import type { Router, LocationQueryRaw } from 'vue-router'; 2 | import NProgress from 'nprogress'; // progress bar 3 | 4 | import { useUserStore } from '@/store'; 5 | import { isLogin } from '@/utils/auth'; 6 | 7 | export default function setupUserLoginInfoGuard(router: Router) { 8 | router.beforeEach(async (to, from, next) => { 9 | NProgress.start(); 10 | const userStore = useUserStore(); 11 | if (isLogin()) { 12 | if (userStore.role) { 13 | next(); 14 | } else { 15 | try { 16 | await userStore.info(); 17 | next(); 18 | } catch (error) { 19 | await userStore.logout(); 20 | next({ 21 | name: 'login', 22 | query: { 23 | redirect: to.name, 24 | ...to.query, 25 | } as LocationQueryRaw, 26 | }); 27 | } 28 | } 29 | } else { 30 | if (to.name === 'login') { 31 | next(); 32 | return; 33 | } 34 | next({ 35 | name: 'login', 36 | query: { 37 | redirect: to.name, 38 | ...to.query, 39 | } as LocationQueryRaw, 40 | }); 41 | } 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /frontend/src/router/routes/base.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordRaw } from 'vue-router'; 2 | import { REDIRECT_ROUTE_NAME } from '@/router/constants'; 3 | 4 | export const DEFAULT_LAYOUT = () => import('@/layout/default-layout.vue'); 5 | 6 | export const REDIRECT_MAIN: RouteRecordRaw = { 7 | path: '/redirect', 8 | name: 'redirectWrapper', 9 | component: DEFAULT_LAYOUT, 10 | meta: { 11 | requiresAuth: true, 12 | hideInMenu: true, 13 | }, 14 | children: [ 15 | { 16 | path: '/redirect/:path', 17 | name: REDIRECT_ROUTE_NAME, 18 | component: () => import('@/views/redirect/index.vue'), 19 | meta: { 20 | requiresAuth: true, 21 | hideInMenu: true, 22 | }, 23 | }, 24 | ], 25 | }; 26 | 27 | export const NOT_FOUND_ROUTE: RouteRecordRaw = { 28 | path: '/:pathMatch(.*)*', 29 | name: 'notFound', 30 | component: () => import('@/views/not-found/index.vue'), 31 | }; 32 | -------------------------------------------------------------------------------- /frontend/src/router/routes/index.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordNormalized } from 'vue-router'; 2 | 3 | const modules = import.meta.glob('./modules/*.ts', { eager: true }); 4 | const externalModules = import.meta.glob('./externalModules/*.ts', { 5 | eager: true, 6 | }); 7 | 8 | function formatModules(_modules: any, result: RouteRecordNormalized[]) { 9 | Object.keys(_modules).forEach((key) => { 10 | const defaultModule = _modules[key].default; 11 | if (!defaultModule) return; 12 | const moduleList = Array.isArray(defaultModule) 13 | ? [...defaultModule] 14 | : [defaultModule]; 15 | result.push(...moduleList); 16 | }); 17 | return result; 18 | } 19 | 20 | export const appRoutes: RouteRecordNormalized[] = formatModules(modules, []); 21 | 22 | export const appExternalRoutes: RouteRecordNormalized[] = formatModules( 23 | externalModules, 24 | [] 25 | ); 26 | -------------------------------------------------------------------------------- /frontend/src/router/routes/modules/chat.ts: -------------------------------------------------------------------------------- 1 | import { AppRouteRecordRaw } from '../types'; 2 | 3 | const CONVERSATION: AppRouteRecordRaw = { 4 | component: undefined, 5 | path: '/chat/conversation', 6 | name: 'conversation', 7 | meta: { 8 | locale: 'menu.chat', 9 | requiresAuth: true, 10 | icon: 'icon-message', 11 | order: 0, 12 | }, 13 | }; 14 | 15 | export default CONVERSATION; 16 | -------------------------------------------------------------------------------- /frontend/src/router/routes/modules/knowledge.ts: -------------------------------------------------------------------------------- 1 | import { AppRouteRecordRaw } from '../types'; 2 | 3 | const KNOWLEDGE: AppRouteRecordRaw = { 4 | path: '/knowledge/list', 5 | name: 'knowledge-list', 6 | meta: { 7 | locale: 'menu.knowledge', 8 | requiresAuth: true, 9 | icon: 'icon-layers', 10 | order: 0, 11 | }, 12 | }; 13 | 14 | export default KNOWLEDGE; 15 | -------------------------------------------------------------------------------- /frontend/src/router/routes/types.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent } from 'vue'; 2 | import type { RouteMeta, NavigationGuard } from 'vue-router'; 3 | 4 | export type Component = 5 | | ReturnType 6 | | (() => Promise) 7 | | (() => Promise); 8 | 9 | export interface AppRouteRecordRaw { 10 | path: string; 11 | name?: string | symbol; 12 | meta?: RouteMeta; 13 | redirect?: string; 14 | component: Component | string | null; 15 | children?: AppRouteRecordRaw[]; 16 | alias?: string | string[]; 17 | props?: Record; 18 | beforeEnter?: NavigationGuard | NavigationGuard[]; 19 | fullPath?: string; 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/router/typings.d.ts: -------------------------------------------------------------------------------- 1 | import 'vue-router'; 2 | 3 | declare module 'vue-router' { 4 | interface RouteMeta { 5 | roles?: string[]; // Controls roles that have access to the page 6 | requiresAuth: boolean; // Whether login is required to access the current page (every route must declare) 7 | icon?: string; // The icon show in the side menu 8 | locale?: string; // The locale name show in side menu and breadcrumb 9 | hideInMenu?: boolean; // If true, it is not displayed in the side menu 10 | hideChildrenInMenu?: boolean; // if set true, the children are not displayed in the side menu 11 | activeMenu?: string; // if set name, the menu will be highlighted according to the name you set 12 | order?: number; // Sort routing menu items. If set key, the higher the value, the more forward it is 13 | noAffix?: boolean; // if set true, the tag will not affix in the tab-bar 14 | ignoreCache?: boolean; // if set true, the page will not be cached 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/store/index.ts: -------------------------------------------------------------------------------- 1 | import { createPinia } from 'pinia'; 2 | import useChatStore from './modules/chat'; 3 | import useAppStore from './modules/app'; 4 | import useUserStore from './modules/user'; 5 | import useTabBarStore from './modules/tab-bar'; 6 | 7 | const pinia = createPinia(); 8 | 9 | export { useAppStore, useUserStore, useTabBarStore, useChatStore }; 10 | export default pinia; 11 | -------------------------------------------------------------------------------- /frontend/src/store/modules/app/types.ts: -------------------------------------------------------------------------------- 1 | import type { RouteRecordNormalized } from 'vue-router'; 2 | 3 | export interface AppState { 4 | theme: string; 5 | colorWeak: boolean; 6 | navbar: boolean; 7 | menu: boolean; 8 | topMenu: boolean; 9 | hideMenu: boolean; 10 | menuCollapse: boolean; 11 | footer: boolean; 12 | themeColor: string; 13 | menuWidth: number; 14 | globalSettings: boolean; 15 | device: string; 16 | tabBar: boolean; 17 | menuFromServer: boolean; 18 | serverMenu: RouteRecordNormalized[]; 19 | [key: string]: unknown; 20 | } 21 | -------------------------------------------------------------------------------- /frontend/src/store/modules/chat/index.ts: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia'; 2 | import { Chat, ChatState, ChatMessage } from '@/store/modules/chat/types'; 3 | import { loadChats } from '@/api/chat'; 4 | 5 | const useChatStore = defineStore('chat', { 6 | state: (): ChatState => ({ 7 | active: null, 8 | mapping: {}, 9 | chats: [ 10 | { 11 | id: 1, 12 | localId: new Date().getTime(), 13 | name: 'New Chat', 14 | messages: [], 15 | mapping: {}, 16 | } as Chat, 17 | ], 18 | }), 19 | 20 | getters: { 21 | getChats(): Chat[] { 22 | return this.chats; 23 | }, 24 | }, 25 | 26 | actions: { 27 | create() { 28 | const localId = new Date().getTime(); 29 | const chat = { 30 | id: null, 31 | localId, 32 | name: 'New Chat', 33 | messages: [], 34 | mapping: {}, 35 | }; 36 | this.chats.push(chat); 37 | this.mapping[localId] = chat; 38 | this.setActive(chat); 39 | return chat; 40 | }, 41 | remove(chat: Chat) { 42 | const chatIndex = this.chats.indexOf(chat); 43 | this.chats.splice(chatIndex, 1); 44 | }, 45 | 46 | createMessage(chatId: number, message: ChatMessage) { 47 | const chat = this.mapping[chatId]; 48 | const messageId = new Date().getTime(); 49 | message.localId = messageId; 50 | chat.mapping[messageId] = chat.messages.length; 51 | chat.messages.push(message); 52 | }, 53 | 54 | updateMessage(chatId: number, message: ChatMessage) { 55 | const chat = this.mapping[chatId]; 56 | const index = chat.mapping[message.localId]; 57 | chat.messages[index] = message; 58 | }, 59 | 60 | setActive(chat: Chat) { 61 | this.active = chat; 62 | }, 63 | 64 | findById(id: number): Chat { 65 | return this.chats.find((chat) => chat.localId === id) as Chat; 66 | }, 67 | 68 | findMessageById(id: number) { 69 | const existsChat = this.chats.find((chat) => chat.id === id); 70 | if (existsChat) { 71 | return existsChat.messages; 72 | } 73 | return []; 74 | }, 75 | }, 76 | }); 77 | 78 | export default useChatStore; 79 | -------------------------------------------------------------------------------- /frontend/src/store/modules/chat/types.ts: -------------------------------------------------------------------------------- 1 | export interface ChatMessage { 2 | createTime: string; 3 | content: string; 4 | inversion?: boolean; 5 | error?: boolean; 6 | loading?: boolean; 7 | localId: number; 8 | } 9 | 10 | export interface ChatMapping { 11 | [key: number]: any; 12 | } 13 | 14 | export interface ChatMessageMapping { 15 | [key: number]: any; 16 | } 17 | 18 | export interface Chat { 19 | id: number | null; 20 | localId: number; 21 | name: string; 22 | messages: ChatMessage[]; 23 | mapping: ChatMessageMapping; 24 | } 25 | 26 | export interface ChatState { 27 | active: Chat | null; 28 | chats: Chat[]; 29 | mapping: ChatMapping; 30 | } 31 | -------------------------------------------------------------------------------- /frontend/src/store/modules/tab-bar/types.ts: -------------------------------------------------------------------------------- 1 | export interface TagProps { 2 | title: string; 3 | name: string; 4 | fullPath: string; 5 | query?: any; 6 | ignoreCache?: boolean; 7 | } 8 | 9 | export interface TabBarState { 10 | tagList: TagProps[]; 11 | cacheTabList: Set; 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/store/modules/user/types.ts: -------------------------------------------------------------------------------- 1 | export type RoleType = '' | '*' | 'admin' | 'user'; 2 | export interface UserState { 3 | name?: string; 4 | avatar?: string; 5 | job?: string; 6 | organization?: string; 7 | location?: string; 8 | email?: string; 9 | introduction?: string; 10 | personalWebsite?: string; 11 | jobName?: string; 12 | organizationName?: string; 13 | locationName?: string; 14 | phone?: string; 15 | registrationDate?: string; 16 | accountId?: string; 17 | certification?: number; 18 | role: RoleType; 19 | } 20 | -------------------------------------------------------------------------------- /frontend/src/types/echarts.ts: -------------------------------------------------------------------------------- 1 | import { CallbackDataParams } from 'echarts/types/dist/shared'; 2 | 3 | export interface ToolTipFormatterParams extends CallbackDataParams { 4 | axisDim: string; 5 | axisIndex: number; 6 | axisType: string; 7 | axisId: string; 8 | axisValue: string; 9 | axisValueLabel: string; 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/types/global.ts: -------------------------------------------------------------------------------- 1 | export interface AnyObject { 2 | [key: string]: unknown; 3 | } 4 | 5 | export interface Options { 6 | value: unknown; 7 | label: string; 8 | } 9 | 10 | export interface NodeOptions extends Options { 11 | children?: NodeOptions[]; 12 | } 13 | 14 | export interface GetParams { 15 | body: null; 16 | type: string; 17 | url: string; 18 | } 19 | 20 | export interface PostData { 21 | body: string; 22 | type: string; 23 | url: string; 24 | } 25 | 26 | export interface Pagination { 27 | current: number; 28 | pageSize: number; 29 | total?: number; 30 | } 31 | 32 | export type TimeRanger = [string, string]; 33 | 34 | export interface GeneralChart { 35 | xAxis: string[]; 36 | data: Array<{ name: string; value: number[] }>; 37 | } 38 | -------------------------------------------------------------------------------- /frontend/src/types/mock.ts: -------------------------------------------------------------------------------- 1 | export interface MockParams { 2 | url: string; 3 | type: string; 4 | body: string; 5 | } 6 | -------------------------------------------------------------------------------- /frontend/src/utils/auth.ts: -------------------------------------------------------------------------------- 1 | const TOKEN_KEY = 'token'; 2 | 3 | const isLogin = () => { 4 | return !!localStorage.getItem(TOKEN_KEY); 5 | }; 6 | 7 | const getToken = () => { 8 | return localStorage.getItem(TOKEN_KEY); 9 | }; 10 | 11 | const setToken = (token: string) => { 12 | localStorage.setItem(TOKEN_KEY, token); 13 | }; 14 | 15 | const clearToken = () => { 16 | localStorage.removeItem(TOKEN_KEY); 17 | }; 18 | 19 | export { isLogin, getToken, setToken, clearToken }; 20 | -------------------------------------------------------------------------------- /frontend/src/utils/copy.ts: -------------------------------------------------------------------------------- 1 | export default function copyToClip(text: string) { 2 | return new Promise((resolve, reject) => { 3 | try { 4 | const input: HTMLTextAreaElement = document.createElement('textarea'); 5 | input.setAttribute('readonly', 'readonly'); 6 | input.value = text; 7 | document.body.appendChild(input); 8 | input.select(); 9 | if (document.execCommand('copy')) document.execCommand('copy'); 10 | document.body.removeChild(input); 11 | resolve(text); 12 | } catch (error) { 13 | reject(error); 14 | } 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /frontend/src/utils/env.ts: -------------------------------------------------------------------------------- 1 | const debug = import.meta.env.MODE !== 'production'; 2 | 3 | export default debug; 4 | -------------------------------------------------------------------------------- /frontend/src/utils/event.ts: -------------------------------------------------------------------------------- 1 | export function addEventListen( 2 | target: Window | HTMLElement, 3 | event: string, 4 | handler: EventListenerOrEventListenerObject, 5 | capture = false 6 | ) { 7 | if ( 8 | target.addEventListener && 9 | typeof target.addEventListener === 'function' 10 | ) { 11 | target.addEventListener(event, handler, capture); 12 | } 13 | } 14 | 15 | export function removeEventListen( 16 | target: Window | HTMLElement, 17 | event: string, 18 | handler: EventListenerOrEventListenerObject, 19 | capture = false 20 | ) { 21 | if ( 22 | target.removeEventListener && 23 | typeof target.removeEventListener === 'function' 24 | ) { 25 | target.removeEventListener(event, handler, capture); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /frontend/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | type TargetContext = '_self' | '_parent' | '_blank' | '_top'; 2 | 3 | export const openWindow = ( 4 | url: string, 5 | opts?: { target?: TargetContext; [key: string]: any } 6 | ) => { 7 | const { target = '_blank', ...others } = opts || {}; 8 | window.open( 9 | url, 10 | target, 11 | Object.entries(others) 12 | .reduce((preValue: string[], curValue) => { 13 | const [key, value] = curValue; 14 | return [...preValue, `${key}=${value}`]; 15 | }, []) 16 | .join(',') 17 | ); 18 | }; 19 | 20 | export const regexUrl = new RegExp( 21 | '^(?!mailto:)(?:(?:http|https|ftp)://)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-?)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$', 22 | 'i' 23 | ); 24 | 25 | export default null; 26 | -------------------------------------------------------------------------------- /frontend/src/utils/is.ts: -------------------------------------------------------------------------------- 1 | const opt = Object.prototype.toString; 2 | 3 | export function isArray(obj: any): obj is any[] { 4 | return opt.call(obj) === '[object Array]'; 5 | } 6 | 7 | export function isObject(obj: any): obj is { [key: string]: any } { 8 | return opt.call(obj) === '[object Object]'; 9 | } 10 | 11 | export function isString(obj: any): obj is string { 12 | return opt.call(obj) === '[object String]'; 13 | } 14 | 15 | export function isNumber(obj: any): obj is number { 16 | return opt.call(obj) === '[object Number]' && obj === obj; // eslint-disable-line 17 | } 18 | 19 | export function isRegExp(obj: any) { 20 | return opt.call(obj) === '[object RegExp]'; 21 | } 22 | 23 | export function isFile(obj: any): obj is File { 24 | return opt.call(obj) === '[object File]'; 25 | } 26 | 27 | export function isBlob(obj: any): obj is Blob { 28 | return opt.call(obj) === '[object Blob]'; 29 | } 30 | 31 | export function isUndefined(obj: any): obj is undefined { 32 | return obj === undefined; 33 | } 34 | 35 | export function isNull(obj: any): obj is null { 36 | return obj === null; 37 | } 38 | 39 | export function isFunction(obj: any): obj is (...args: any[]) => any { 40 | return typeof obj === 'function'; 41 | } 42 | 43 | export function isEmptyObject(obj: any): boolean { 44 | return isObject(obj) && Object.keys(obj).length === 0; 45 | } 46 | 47 | export function isExist(obj: any): boolean { 48 | return obj || obj === 0; 49 | } 50 | 51 | export function isWindow(el: any): el is Window { 52 | return el === window; 53 | } 54 | -------------------------------------------------------------------------------- /frontend/src/utils/monitor.ts: -------------------------------------------------------------------------------- 1 | import { App, ComponentPublicInstance } from 'vue'; 2 | import axios from 'axios'; 3 | 4 | export default function handleError(Vue: App, baseUrl: string) { 5 | if (!baseUrl) { 6 | return; 7 | } 8 | Vue.config.errorHandler = ( 9 | err: unknown, 10 | instance: ComponentPublicInstance | null, 11 | info: string 12 | ) => { 13 | // send error info 14 | axios.post(`${baseUrl}/report-error`, { 15 | err, 16 | instance, 17 | info, 18 | // location: window.location.href, 19 | // message: err.message, 20 | // stack: err.stack, 21 | // browserInfo: getBrowserInfo(), 22 | // user info 23 | // dom info 24 | // url info 25 | // ... 26 | }); 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /frontend/src/utils/route-listener.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Listening to routes alone would waste rendering performance. Use the publish-subscribe model for distribution management 3 | * 单独监听路由会浪费渲染性能。使用发布订阅模式去进行分发管理。 4 | */ 5 | import mitt, { Handler } from 'mitt'; 6 | import type { RouteLocationNormalized } from 'vue-router'; 7 | 8 | const emitter = mitt(); 9 | 10 | const key = Symbol('ROUTE_CHANGE'); 11 | 12 | let latestRoute: RouteLocationNormalized; 13 | 14 | export function setRouteEmitter(to: RouteLocationNormalized) { 15 | emitter.emit(key, to); 16 | latestRoute = to; 17 | } 18 | 19 | export function listenerRouteChange( 20 | handler: (route: RouteLocationNormalized) => void, 21 | immediate = true 22 | ) { 23 | emitter.on(key, handler as Handler); 24 | if (immediate && latestRoute) { 25 | handler(latestRoute); 26 | } 27 | } 28 | 29 | export function removeRouteListener() { 30 | emitter.off(key); 31 | } 32 | -------------------------------------------------------------------------------- /frontend/src/utils/setup-mock.ts: -------------------------------------------------------------------------------- 1 | import debug from './env'; 2 | 3 | export default ({ mock, setup }: { mock?: boolean; setup: () => void }) => { 4 | if (mock !== false && debug) setup(); 5 | }; 6 | 7 | export const successResponseWrap = (data: unknown) => { 8 | return { 9 | data, 10 | success: true, 11 | message: '请求成功', 12 | code: 20000, 13 | }; 14 | }; 15 | 16 | export const failResponseWrap = (data: unknown, msg: string, code = 50000) => { 17 | return { 18 | data, 19 | success: false, 20 | msg, 21 | code, 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /frontend/src/utils/storage.ts: -------------------------------------------------------------------------------- 1 | interface StorageData { 2 | data: T; 3 | expire: number | null; 4 | } 5 | 6 | export function createLocalStorage(options?: { expire?: number | null }) { 7 | const DEFAULT_CACHE_TIME = 60 * 60 * 24 * 7; 8 | 9 | const { expire } = Object.assign({ expire: DEFAULT_CACHE_TIME }, options); 10 | 11 | function set(key: string, data: T) { 12 | const storageData: StorageData = { 13 | data, 14 | expire: expire !== null ? new Date().getTime() + expire * 1000 : null, 15 | }; 16 | 17 | const json = JSON.stringify(storageData); 18 | window.localStorage.setItem(key, json); 19 | } 20 | 21 | function get(key: string) { 22 | const json = window.localStorage.getItem(key); 23 | if (json) { 24 | let storageData: StorageData | null = null; 25 | 26 | try { 27 | storageData = JSON.parse(json); 28 | } catch { 29 | // Prevent failure 30 | } 31 | 32 | if (storageData) { 33 | const { data, expire } = storageData; 34 | if (expire === null || expire >= Date.now()) return data; 35 | } 36 | 37 | remove(key); 38 | return null; 39 | } 40 | } 41 | 42 | function remove(key: string) { 43 | window.localStorage.removeItem(key); 44 | } 45 | 46 | function clear() { 47 | window.localStorage.clear(); 48 | } 49 | 50 | return { set, get, remove, clear }; 51 | } 52 | 53 | export const ls = createLocalStorage(); 54 | 55 | export const ss = createLocalStorage({ expire: null }); 56 | -------------------------------------------------------------------------------- /frontend/src/views/chat/index.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 52 | 53 | 58 | -------------------------------------------------------------------------------- /frontend/src/views/chat/message/avatar.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 41 | 42 | 47 | -------------------------------------------------------------------------------- /frontend/src/views/chat/message/style.less: -------------------------------------------------------------------------------- 1 | .markdown-body { 2 | font-size: 14px; 3 | background-color: transparent; 4 | 5 | p { 6 | white-space: pre-wrap; 7 | } 8 | 9 | ol { 10 | list-style-type: decimal; 11 | } 12 | 13 | ul { 14 | list-style-type: disc; 15 | } 16 | 17 | pre code, 18 | pre tt { 19 | line-height: 1.65; 20 | } 21 | 22 | .highlight pre, 23 | pre { 24 | background-color: #fff; 25 | } 26 | 27 | code.hljs { 28 | padding: 0; 29 | } 30 | 31 | .code-block { 32 | &-wrapper { 33 | position: relative; 34 | padding-top: 24px; 35 | } 36 | 37 | &-header { 38 | position: absolute; 39 | top: 5px; 40 | right: 0; 41 | display: flex; 42 | align-items: center; 43 | justify-content: flex-end; 44 | width: 100%; 45 | padding: 0 1rem; 46 | color: #b3b3b3; 47 | 48 | &__copy { 49 | margin-left: 0.5rem; 50 | cursor: pointer; 51 | user-select: none; 52 | 53 | &:hover { 54 | color: #65a665; 55 | } 56 | } 57 | } 58 | } 59 | 60 | } 61 | 62 | body[arco-theme='dark'] { 63 | .message-reply { 64 | .whitespace-pre-wrap { 65 | color: var(--n-text-color); 66 | white-space: pre-wrap; 67 | } 68 | } 69 | 70 | .highlight pre, pre { 71 | background-color: #282c34; 72 | } 73 | } 74 | 75 | @media screen and (max-width: 533px) { 76 | .markdown-body .code-block-wrapper { 77 | padding: unset; 78 | 79 | code { 80 | padding: 24px 16px 16px; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /frontend/src/views/dashboard/workplace/components/announcement.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 51 | 52 | 72 | -------------------------------------------------------------------------------- /frontend/src/views/dashboard/workplace/components/banner.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 23 | 24 | 36 | -------------------------------------------------------------------------------- /frontend/src/views/dashboard/workplace/components/carousel.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 25 | -------------------------------------------------------------------------------- /frontend/src/views/dashboard/workplace/components/docs.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 43 | -------------------------------------------------------------------------------- /frontend/src/views/dashboard/workplace/components/quick-operation.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /frontend/src/views/dashboard/workplace/components/recently-visited.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 39 | 40 | 45 | -------------------------------------------------------------------------------- /frontend/src/views/dashboard/workplace/locale/en-US.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.dashboard.workplace': 'Workplace', 3 | 'workplace.welcome': 'Welcome!', 4 | 'workplace.balance': 'Balance (CNY)', 5 | 'workplace.order.pending': 'Pending', 6 | 'workplace.order.pendingRenewal': 'Renewal Order', 7 | 'workplace.onlineContent': 'Online Content', 8 | 'workplace.putIn': 'Put In', 9 | 'workplace.newDay': 'Daily Additional Comments', 10 | 'workplace.newFromYesterday': 'New From Yesterday', 11 | 'workplace.minute': 'Min', 12 | 'workplace.docs': 'Documents', 13 | 'workplace.docs.productOverview': 'Product Overview', 14 | 'workplace.docs.userGuide': 'User Guide', 15 | 'workplace.docs.workflow': 'Workflow', 16 | 'workplace.docs.interfaceDocs': 'Interface Docs', 17 | // 18 | 'workplace.contentManagement': 'Content Management', 19 | 'workplace.contentStatistical': 'Content Statistical', 20 | 'workplace.advanced': 'Advanced', 21 | 'workplace.onlinePromotion': 'Online Promotion', 22 | 'workplace.contentPutIn': 'Put In', 23 | 'workplace.announcement': 'Announcement', 24 | 'workplace.recently.visited': 'Recently Visited', 25 | 'workplace.record.nodata': 'No data', 26 | 'workplace.quick.operation': 'Quick Operation', 27 | 'workplace.quickOperation.setup': 'Setup', 28 | 'workplace.allProject': 'All', 29 | 'workplace.loadMore': 'More', 30 | 'workplace.viewMore': 'More', 31 | 'workplace.contentData': 'Content Data', 32 | 'workplace.popularContent': 'Popular Content', 33 | 'workplace.popularContent.text': 'text', 34 | 'workplace.popularContent.image': 'image', 35 | 'workplace.popularContent.video': 'video', 36 | 'workplace.categoriesPercent': 'Categories Percent', 37 | 'workplace.pecs': 'pecs', 38 | }; 39 | -------------------------------------------------------------------------------- /frontend/src/views/dashboard/workplace/locale/zh-CN.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.dashboard.workplace': '工作台', 3 | 'workplace.welcome': '欢迎回来!', 4 | 'workplace.balance': '余额(元)', 5 | 'workplace.order.pending': '待支付', 6 | 'workplace.order.pendingRenewal': '待续费订单', 7 | 'workplace.onlineContent': '线上总内容', 8 | 'workplace.putIn': '投放中内容', 9 | 'workplace.newDay': '日新增评论', 10 | 'workplace.newFromYesterday': '较昨日新增', 11 | 'workplace.minute': '分钟', 12 | 'workplace.docs': '帮助文档', 13 | 'workplace.docs.productOverview': '产品概要', 14 | 'workplace.docs.userGuide': '使用指南', 15 | 'workplace.docs.workflow': '接入流程', 16 | 'workplace.docs.interfaceDocs': '接口文档', 17 | 'workplace.contentManagement': '内容管理', 18 | 'workplace.contentStatistical': '内容分析', 19 | 'workplace.advanced': '高级管理', 20 | 'workplace.onlinePromotion': '线上推广', 21 | 'workplace.contentPutIn': '内容投放', 22 | 'workplace.announcement': '公告', 23 | 'workplace.recently.visited': '最近访问', 24 | 'workplace.record.nodata': '暂无数据', 25 | 'workplace.quick.operation': '快捷操作', 26 | 'workplace.quickOperation.setup': '管理', 27 | 'workplace.allProject': '所有项目', 28 | 'workplace.loadMore': '加载更多', 29 | 'workplace.viewMore': '查看更多', 30 | 'workplace.contentData': '内容数据', 31 | 'workplace.popularContent': '线上热门内容', 32 | 'workplace.popularContent.text': '文本', 33 | 'workplace.popularContent.image': '图片', 34 | 'workplace.popularContent.video': '视频', 35 | 'workplace.categoriesPercent': '内容类型占比', 36 | 'workplace.pecs': '个', 37 | }; 38 | -------------------------------------------------------------------------------- /frontend/src/views/knowledge/components/rules-preset.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /frontend/src/views/knowledge/components/the-service.vue: -------------------------------------------------------------------------------- 1 | 44 | 45 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /frontend/src/views/knowledge/locale/en-US.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.list.cardList': 'Card List', 3 | 'cardList.tab.title.all': 'All', 4 | 'cardList.tab.title.content': 'Quality Inspection', 5 | 'cardList.tab.title.service': 'The service', 6 | 'cardList.tab.title.preset': 'Rules Preset', 7 | 'cardList.searchInput.placeholder': 'Search', 8 | 'cardList.enable': 'Enable', 9 | 'cardList.disable': 'Disable', 10 | 'cardList.content.delete': 'Delete', 11 | 'cardList.content.inspection': 'Inspection', 12 | 'cardList.content.action': 'Click Create Qc Content queue', 13 | 'cardList.service.open': 'Open', 14 | 'cardList.service.cancel': 'Cancel', 15 | 'cardList.service.renew': 'Contract of service', 16 | 'cardList.service.tag': 'Opened', 17 | 'cardList.service.expiresTag': 'Expired', 18 | 'cardList.preset.tag': 'Enable', 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/views/knowledge/locale/zh-CN.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'menu.list.cardList': '卡片列表', 3 | 'cardList.tab.title.all': '全部', 4 | 'cardList.tab.title.content': '内容质检', 5 | 'cardList.tab.title.service': '开通服务', 6 | 'cardList.tab.title.preset': '规则预置', 7 | 'cardList.searchInput.placeholder': '搜索', 8 | // 'cardList.statistic.enable': '已启用', 9 | // 'cardList.statistic.disable': '未启用', 10 | 'cardList.content.delete': '删除', 11 | 'cardList.content.inspection': '质检', 12 | 'cardList.content.action': '点击创建质检内容队列', 13 | 'cardList.service.open': '开通服务', 14 | 'cardList.service.cancel': '取消服务', 15 | 'cardList.service.renew': '续约服务', 16 | 'cardList.service.tag': '已开通', 17 | 'cardList.service.expiresTag': '已过期', 18 | 'cardList.preset.tag': '已启用', 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/views/login/index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 27 | 28 | 71 | 72 | 82 | -------------------------------------------------------------------------------- /frontend/src/views/login/locale/en-US.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'login.form.title': 'Login to Arco Design Pro', 3 | 'login.form.userName.errMsg': 'Username cannot be empty', 4 | 'login.form.password.errMsg': 'Password cannot be empty', 5 | 'login.form.login.errMsg': 'Login error, refresh and try again', 6 | 'login.form.login.success': 'welcome to use', 7 | 'login.form.userName.placeholder': 'Username: admin', 8 | 'login.form.password.placeholder': 'Password: admin', 9 | 'login.form.rememberPassword': 'Remember password', 10 | 'login.form.forgetPassword': 'Forgot password', 11 | 'login.form.login': 'login', 12 | 'login.form.register': 'register account', 13 | 'login.banner.slogan1': 'Out-of-the-box high-quality template', 14 | 'login.banner.subSlogan1': 15 | 'Rich page templates, covering most typical business scenarios', 16 | 'login.banner.slogan2': 'Built-in solutions to common.js problems', 17 | 'login.banner.subSlogan2': 18 | 'Internationalization, routing configuration, state management everything', 19 | 'login.banner.slogan3': 'Access visualization enhancement tool AUX', 20 | 'login.banner.subSlogan3': 'Realize flexible block development', 21 | }; 22 | -------------------------------------------------------------------------------- /frontend/src/views/login/locale/zh-CN.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | 'login.form.title': '登录 Fly GPT', 3 | 'login.form.userName.errMsg': '用户名不能为空', 4 | 'login.form.password.errMsg': '密码不能为空', 5 | 'login.form.login.errMsg': '登录出错,轻刷新重试', 6 | 'login.form.login.success': '欢迎使用', 7 | 'login.form.userName.placeholder': '用户名:admin', 8 | 'login.form.password.placeholder': '密码:admin', 9 | 'login.form.rememberPassword': '记住密码', 10 | 'login.form.forgetPassword': '忘记密码', 11 | 'login.form.login': '登录', 12 | 'login.form.register': '注册账号', 13 | 'login.banner.slogan1': '开箱即用的高质量模板', 14 | 'login.banner.subSlogan1': '丰富的的页面模板,覆盖大多数典型业务场景', 15 | 'login.banner.slogan2': '内置了常见问题的解决方案', 16 | 'login.banner.subSlogan2': '国际化,路由配置,状态管理应有尽有', 17 | 'login.banner.slogan3': '接入可视化增强工具AUX', 18 | 'login.banner.subSlogan3': '实现灵活的区块式开发', 19 | }; 20 | -------------------------------------------------------------------------------- /frontend/src/views/not-found/index.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 19 | 20 | 31 | -------------------------------------------------------------------------------- /frontend/src/views/redirect/index.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ['body[arco-theme="dark"]'], 4 | content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], 5 | theme: { 6 | extend: {}, 7 | }, 8 | plugins: [], 9 | }; 10 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "ES2020", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "jsx": "preserve", 8 | "sourceMap": true, 9 | "resolveJsonModule": true, 10 | "esModuleInterop": true, 11 | "baseUrl": ".", 12 | "paths": { 13 | "@/*": ["src/*"] 14 | }, 15 | "lib": ["es2020", "dom"], 16 | "skipLibCheck": true 17 | }, 18 | "include": ["src/**/*", "src/**/*.vue"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/application/KnowledgeApplicationService.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.application; 2 | 3 | import com.honvay.flychat.knowledge.domain.model.KnowledgeBase; 4 | import com.honvay.flychat.knowledge.domain.model.SplitType; 5 | 6 | import java.io.File; 7 | 8 | public interface KnowledgeApplicationService { 9 | 10 | void create(KnowledgeBase knowledgeBase); 11 | 12 | void update(KnowledgeBase knowledgeBase); 13 | 14 | void addFile(KnowledgeBase knowledgeBase, File file, SplitType splitType); 15 | 16 | void addText(KnowledgeBase knowledgeBase, String name, String text, SplitType splitType); 17 | 18 | void deleteItem(KnowledgeBase knowledgeBase); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/application/KnowledgeChatService.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.application; 2 | 3 | import com.honvay.flychat.knowledge.domain.model.KnowledgeChat; 4 | 5 | import java.util.function.Consumer; 6 | 7 | public interface KnowledgeChatService { 8 | String chat(KnowledgeChat knowledgeChat, String question); 9 | 10 | void chat(KnowledgeChat knowledgeChat, 11 | String question, 12 | Consumer onResult, 13 | Consumer onComplete, 14 | Consumer onError); 15 | } 16 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/application/KnowledgeEmbeddingService.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.application; 2 | 3 | import com.honvay.flychat.knowledge.domain.model.Relevant; 4 | 5 | import java.util.List; 6 | 7 | public interface KnowledgeEmbeddingService { 8 | List findRelevant(String source, List knowledgeIds, Double similarity, int relevantSize); 9 | } 10 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/event/KnowledgeItemCreateEvent.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.event; 2 | 3 | import org.springframework.context.ApplicationEvent; 4 | 5 | public class KnowledgeItemCreateEvent extends ApplicationEvent { 6 | 7 | public KnowledgeItemCreateEvent(Object source) { 8 | super(source); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/model/Application.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.List; 6 | 7 | 8 | @Data 9 | public class Application { 10 | 11 | private Long id; 12 | 13 | private String name; 14 | 15 | private String logo; 16 | 17 | private String introduction; 18 | 19 | private Owner owner; 20 | 21 | private ApplicationKnowledge knowledge; 22 | 23 | private ApplicationModel model; 24 | 25 | public void addKnowledgeBase(KnowledgeBase knowledgeBase){ 26 | this.knowledge.addKnowledgeBase(knowledgeBase); 27 | } 28 | 29 | public boolean hasKnowledge(){ 30 | if(this.knowledge == null){ 31 | return false; 32 | } 33 | List knowledgeBases = this.knowledge.getKnowledgeBases(); 34 | return knowledgeBases != null && knowledgeBases.size() > 0; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/model/ApplicationKnowledge.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | @Data 9 | public class ApplicationKnowledge { 10 | 11 | private Double similarity; 12 | 13 | private int relevantSize; 14 | 15 | private List knowledgeBases; 16 | 17 | public void addKnowledgeBase(KnowledgeBase knowledgeBase){ 18 | if(this.knowledgeBases == null){ 19 | this.knowledgeBases = new ArrayList<>(); 20 | } 21 | this.knowledgeBases.add(knowledgeBase); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/model/ApplicationModel.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.model; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ApplicationModel { 7 | 8 | private String modelName; 9 | 10 | private String limitPrompt; 11 | 12 | private Double temperature; 13 | 14 | private Integer topk; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/model/KnowledgeBase.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.model; 2 | 3 | 4 | import lombok.Data; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | @Data 10 | public class KnowledgeBase { 11 | 12 | private Long id; 13 | 14 | /** 15 | * 名称 16 | */ 17 | private String name; 18 | 19 | /** 20 | * 标签 21 | */ 22 | private String tags; 23 | 24 | private String logo; 25 | 26 | private Owner owner; 27 | 28 | private List items; 29 | 30 | public static KnowledgeBase of(Long id){ 31 | KnowledgeBase knowledgeBase = new KnowledgeBase(); 32 | knowledgeBase.setId(id); 33 | return knowledgeBase; 34 | } 35 | 36 | public void create(){ 37 | 38 | } 39 | 40 | /** 41 | * @param knowledgeItem 42 | * @return 43 | */ 44 | public KnowledgeItem addItem(KnowledgeItem knowledgeItem){ 45 | if(this.items == null){ 46 | this.items = new ArrayList<>(); 47 | } 48 | this.items.add(knowledgeItem); 49 | return knowledgeItem; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/model/KnowledgeChat.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.model; 2 | 3 | import com.honvay.flychat.chat.domain.model.Chat; 4 | import lombok.Data; 5 | import lombok.EqualsAndHashCode; 6 | 7 | 8 | @Data 9 | @EqualsAndHashCode(callSuper = true) 10 | public class KnowledgeChat extends Chat { 11 | 12 | private Application application; 13 | 14 | @Override 15 | public Long getApplicationId() { 16 | return application.getId(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/model/KnowledgeDetail.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.model; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class KnowledgeDetail { 7 | 8 | private Long id; 9 | 10 | private float[] embedding; 11 | 12 | private String source; 13 | 14 | private String segment; 15 | 16 | private KnowledgeItemStatus status; 17 | 18 | public KnowledgeDetail(){ 19 | 20 | } 21 | 22 | public void embed(float[] embedding){ 23 | this.embedding = embedding; 24 | this.status = KnowledgeItemStatus.DONE; 25 | } 26 | 27 | public KnowledgeDetail(String source,String segment,KnowledgeItemStatus status){ 28 | this.source = source; 29 | this.segment = segment; 30 | this.status = status; 31 | } 32 | 33 | public static KnowledgeDetail create(String source, String segment){ 34 | return new KnowledgeDetail(source,segment,KnowledgeItemStatus.PENDING); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/model/KnowledgeItem.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.model; 2 | 3 | import lombok.Data; 4 | 5 | import java.io.File; 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | @Data 10 | public class KnowledgeItem { 11 | 12 | private Long id; 13 | 14 | private String name; 15 | 16 | private SplitType splitType; 17 | 18 | private List details; 19 | 20 | public KnowledgeItem(){ 21 | 22 | } 23 | 24 | public static KnowledgeItem of(Long id){ 25 | KnowledgeItem knowledgeItem = new KnowledgeItem(); 26 | knowledgeItem.setId(id); 27 | return knowledgeItem; 28 | } 29 | 30 | public KnowledgeItem(File file,SplitType splitType){ 31 | this.name = file.getName(); 32 | this.splitType = splitType; 33 | } 34 | 35 | public KnowledgeItem(String name,SplitType splitType){ 36 | if(name == null){ 37 | name = "自定义内容"; 38 | } 39 | this.name = name; 40 | this.splitType = splitType; 41 | } 42 | 43 | public void addDetail(KnowledgeDetail detail){ 44 | if (details == null) { 45 | details = new ArrayList<>(); 46 | } 47 | details.add(detail); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/model/KnowledgeItemStatus.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.model; 2 | 3 | import com.honvay.cola.framework.core.enums.GeneralEnum; 4 | import lombok.Getter; 5 | 6 | import java.util.Arrays; 7 | import java.util.Map; 8 | import java.util.stream.Collectors; 9 | 10 | public enum KnowledgeItemStatus implements GeneralEnum { 11 | PENDING(1,"PENDING"), 12 | DONE(2,"DONE"); 13 | 14 | 15 | @Getter 16 | private final Integer code; 17 | 18 | @Getter 19 | private final String name; 20 | 21 | KnowledgeItemStatus(Integer code, String name) { 22 | this.code = code; 23 | this.name = name; 24 | } 25 | 26 | private static final Map mapping; 27 | 28 | static { 29 | mapping = Arrays.stream(KnowledgeItemStatus.values()) 30 | .collect(Collectors.toMap(KnowledgeItemStatus::getCode, status -> status)); 31 | } 32 | 33 | /** 34 | * @param value 35 | * @return 36 | */ 37 | public static KnowledgeItemStatus of(Integer value){ 38 | return mapping.get(value); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/model/Owner.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.model; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class Owner { 7 | 8 | private Long id; 9 | 10 | private String name; 11 | 12 | private String avatar; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/model/Relevant.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.model; 2 | 3 | import com.honvay.flychat.chat.domain.model.ChatQuote; 4 | import lombok.Data; 5 | 6 | @Data 7 | public class Relevant { 8 | 9 | private Long detailId; 10 | 11 | private String segment; 12 | 13 | private Double similarity; 14 | 15 | private String source; 16 | 17 | private Long knowledgeId; 18 | 19 | private String knowledgeName; 20 | 21 | public ChatQuote toQuote(){ 22 | ChatQuote chatQuote = new ChatQuote(); 23 | chatQuote.setSegment(this.segment); 24 | chatQuote.setSimilarity(similarity); 25 | chatQuote.setSourceId(detailId); 26 | return chatQuote; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/model/SplitType.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.model; 2 | 3 | import com.honvay.cola.framework.core.enums.GeneralEnum; 4 | import lombok.Getter; 5 | 6 | import java.util.Arrays; 7 | import java.util.Map; 8 | import java.util.stream.Collectors; 9 | 10 | public enum SplitType implements GeneralEnum { 11 | SENTENCE(1,"SENTENCE"), 12 | FIXED(2,"FIXED"), 13 | PARAGRAPH(3,"PARAGRAPH"), 14 | AI(4,"AI"), 15 | NONE(5,"NONE"); 16 | 17 | 18 | @Getter 19 | private final Integer code; 20 | 21 | @Getter 22 | private final String name; 23 | 24 | SplitType(Integer value, String code) { 25 | this.code = value; 26 | this.name = code; 27 | } 28 | 29 | private static final Map mapping; 30 | 31 | static { 32 | mapping = Arrays.stream(SplitType.values()) 33 | .collect(Collectors.toMap(SplitType::getCode, splitType -> splitType)); 34 | } 35 | 36 | /** 37 | * @param value 38 | * @return 39 | */ 40 | public static SplitType of(Integer value){ 41 | return mapping.get(value); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/repository/ApplicationKnowledgeRepository.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.repository; 2 | 3 | import com.honvay.flychat.knowledge.domain.model.Application; 4 | import com.honvay.flychat.knowledge.domain.model.ApplicationKnowledge; 5 | import com.honvay.flychat.knowledge.domain.model.KnowledgeBase; 6 | 7 | import java.util.List; 8 | 9 | public interface ApplicationKnowledgeRepository { 10 | 11 | List findByApplication(Application application); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/repository/ApplicationRepository.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.repository; 2 | 3 | import com.honvay.flychat.knowledge.domain.model.Application; 4 | 5 | public interface ApplicationRepository { 6 | 7 | Application get(Long applicationId); 8 | 9 | void save(Application application); 10 | 11 | void update(Application application); 12 | 13 | void delete(Application application); 14 | 15 | void saveKnowledgeBase(Application application); 16 | 17 | void deleteKnowledgeBase(Application application); 18 | 19 | void updateKnowledge(Application application); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/repository/KnowledgeDetailRepository.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.repository; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.honvay.flychat.knowledge.domain.model.KnowledgeDetail; 5 | import com.honvay.flychat.knowledge.domain.model.Relevant; 6 | import com.honvay.flychat.knowledge.infra.po.KnowledgeDetailPo; 7 | 8 | import java.util.List; 9 | 10 | public interface KnowledgeDetailRepository extends IService { 11 | 12 | List findPendingKnowledgeDetails(); 13 | 14 | List findByKnowledgeItem(List itemIds); 15 | 16 | void deleteByItemIds(List itemIds); 17 | 18 | List findRelevant(List knowledgeIds, float[] embedding, Double similarity, int size); 19 | } 20 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/repository/KnowledgeItemRepository.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.repository; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.honvay.flychat.knowledge.infra.po.KnowledgeItemPo; 5 | 6 | public interface KnowledgeItemRepository extends IService { 7 | } 8 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/repository/KnowledgeRepository.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.repository; 2 | 3 | import com.baomidou.mybatisplus.extension.service.IService; 4 | import com.honvay.flychat.knowledge.domain.model.KnowledgeBase; 5 | import com.honvay.flychat.knowledge.infra.po.KnowledgeBasePo; 6 | 7 | import java.util.List; 8 | 9 | public interface KnowledgeRepository extends IService { 10 | 11 | KnowledgeBase get(Long id); 12 | 13 | List findByUser(Long userId); 14 | 15 | void save(KnowledgeBase knowledgeBase); 16 | 17 | void update(KnowledgeBase knowledgeBase); 18 | 19 | void saveItem(KnowledgeBase knowledgeBase); 20 | 21 | void deleteItem(KnowledgeBase knowledgeBase); 22 | 23 | void updateEmbedding(KnowledgeBase knowledgeBase); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/service/ApplicationDomainService.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.service; 2 | 3 | import com.honvay.flychat.knowledge.domain.model.Application; 4 | 5 | public interface ApplicationDomainService { 6 | void create(Application application); 7 | 8 | void update(Application application); 9 | 10 | void delete(Application application); 11 | 12 | void addKnowledgeBase(Application application); 13 | 14 | void updateKnowledge(Application application); 15 | 16 | Application get(Long applicationId); 17 | } 18 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/service/KnowledgeBaseDomainService.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.service; 2 | 3 | import com.honvay.flychat.knowledge.domain.model.KnowledgeBase; 4 | import com.honvay.flychat.knowledge.domain.model.Relevant; 5 | 6 | import java.util.List; 7 | 8 | public interface KnowledgeBaseDomainService { 9 | void create(KnowledgeBase knowledgeBase); 10 | 11 | void update(KnowledgeBase knowledgeBase); 12 | 13 | void delete(KnowledgeBase knowledgeBase); 14 | 15 | void addItem(KnowledgeBase knowledgeBase); 16 | 17 | void deleteItem(KnowledgeBase knowledgeBase); 18 | 19 | void updateEmbedding(KnowledgeBase knowledgeBase); 20 | 21 | List findRelevantDetails(List knowledgeIds, float[] embedding, Double similarity, int size); 22 | } 23 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/domain/service/impl/ApplicationDomainServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.domain.service.impl; 2 | 3 | import com.honvay.flychat.knowledge.domain.model.Application; 4 | import com.honvay.flychat.knowledge.domain.repository.ApplicationRepository; 5 | import com.honvay.flychat.knowledge.domain.service.ApplicationDomainService; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | public class ApplicationDomainServiceImpl implements ApplicationDomainService { 10 | 11 | private final ApplicationRepository applicationRepository; 12 | 13 | public ApplicationDomainServiceImpl(ApplicationRepository applicationRepository) { 14 | this.applicationRepository = applicationRepository; 15 | } 16 | 17 | @Override 18 | public void create(Application application){ 19 | applicationRepository.save(application); 20 | } 21 | 22 | @Override 23 | public void update(Application application){ 24 | this.applicationRepository.update(application); 25 | } 26 | 27 | @Override 28 | public void delete(Application application){ 29 | this.applicationRepository.delete(application); 30 | } 31 | 32 | @Override 33 | public void addKnowledgeBase(Application application){ 34 | this.applicationRepository.saveKnowledgeBase(application); 35 | } 36 | 37 | @Override 38 | public void updateKnowledge(Application application){ 39 | this.applicationRepository.updateKnowledge(application); 40 | } 41 | 42 | @Override 43 | public Application get(Long applicationId){ 44 | return applicationRepository.get(applicationId); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/document/DocumentType.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.document; 2 | 3 | public enum DocumentType { 4 | 5 | PDF,TXT,WORD 6 | 7 | } 8 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/interceptor/PgVectorInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.interceptor; 2 | 3 | import com.pgvector.PGvector; 4 | import org.apache.ibatis.executor.statement.StatementHandler; 5 | import org.apache.ibatis.plugin.Interceptor; 6 | import org.apache.ibatis.plugin.Intercepts; 7 | import org.apache.ibatis.plugin.Invocation; 8 | import org.apache.ibatis.plugin.Signature; 9 | 10 | import java.sql.Connection; 11 | 12 | @Intercepts( 13 | { 14 | @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class}) 15 | } 16 | ) 17 | public class PgVectorInterceptor implements Interceptor { 18 | 19 | @Override 20 | public Object intercept(Invocation invocation) throws Throwable { 21 | Object[] args = invocation.getArgs(); 22 | if(args != null){ 23 | Connection connections = (Connection) args[0]; 24 | PGvector.addVectorType(connections); 25 | } 26 | return invocation.proceed(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/mapper/ApplicationKnowledgeMapper.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.honvay.flychat.knowledge.infra.po.ApplicationKnowledgePo; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | @Mapper 8 | public interface ApplicationKnowledgeMapper extends BaseMapper { 9 | } 10 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/mapper/ApplicationMapper.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.mapper; 2 | 3 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 4 | import com.honvay.flychat.knowledge.infra.po.ApplicationPo; 5 | import org.apache.ibatis.annotations.Mapper; 6 | 7 | @Mapper 8 | public interface ApplicationMapper extends BaseMapper { 9 | 10 | 11 | } 12 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/mapper/KnowledgeBaseMapper.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.mapper; 2 | 3 | import com.honvay.flychat.knowledge.infra.po.KnowledgeBasePo; 4 | 5 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 6 | import org.apache.ibatis.annotations.Mapper; 7 | 8 | /** 9 | * @description Mapper for Knowledge 10 | * @createDate ${.now?string('yyyy-MM-dd HH:mm:ss')} 11 | * @Entity Knowledge 12 | */ 13 | @Mapper 14 | public interface KnowledgeBaseMapper extends BaseMapper { 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/mapper/KnowledgeDetailMapper.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.mapper; 2 | 3 | import com.honvay.flychat.knowledge.domain.model.Relevant; 4 | import com.honvay.flychat.knowledge.infra.po.KnowledgeDetailPo; 5 | 6 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 7 | import com.pgvector.PGvector; 8 | import org.apache.ibatis.annotations.Mapper; 9 | import org.apache.ibatis.annotations.Param; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * @description Mapper for KnowledgeDetailPo 15 | * @createDate ${.now?string('yyyy-MM-dd HH:mm:ss')} 16 | * @Entity KnowledgeDetailPo 17 | */ 18 | @Mapper 19 | public interface KnowledgeDetailMapper extends BaseMapper { 20 | 21 | List findRelevant(@Param("knowledgeIds") List knowledgeIds, 22 | @Param("embedding") PGvector embedding, 23 | @Param("similarity") Double similarity, 24 | @Param("size") int size); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/mapper/KnowledgeItemMapper.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.mapper; 2 | 3 | import com.honvay.flychat.knowledge.infra.po.KnowledgeItemPo; 4 | 5 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; 6 | import org.apache.ibatis.annotations.Mapper; 7 | 8 | /** 9 | * @description Mapper for KnowledgeItem 10 | * @createDate ${.now?string('yyyy-MM-dd HH:mm:ss')} 11 | * @Entity KnowledgeItem 12 | */ 13 | @Mapper 14 | public interface KnowledgeItemMapper extends BaseMapper { 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/po/ApplicationKnowledgePo.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.po; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.Data; 7 | 8 | @Data 9 | @TableName("application_knowledge") 10 | public class ApplicationKnowledgePo { 11 | 12 | @TableId(type = IdType.AUTO) 13 | private Long id; 14 | 15 | private Long applicationId; 16 | 17 | private Long knowledgeBaseId; 18 | } 19 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/po/ApplicationPo.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.po; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.Data; 7 | 8 | @Data 9 | @TableName("application") 10 | public class ApplicationPo { 11 | 12 | @TableId(type = IdType.AUTO) 13 | private Long id; 14 | 15 | private String name; 16 | 17 | private String logo; 18 | 19 | private String introduction; 20 | 21 | private Long owner; 22 | 23 | private String modelName; 24 | 25 | private String limitPrompt; 26 | 27 | private Double temperature; 28 | 29 | private Integer topk; 30 | 31 | private Double similarity; 32 | 33 | private int relevantSize; 34 | 35 | } 36 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/po/KnowledgeBasePo.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.po; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.Data; 7 | 8 | import java.util.Date; 9 | @Data 10 | @TableName("knowledge_base") 11 | public class KnowledgeBasePo { 12 | 13 | /** 14 | * 主键ID 15 | */ 16 | @TableId(type = IdType.AUTO) 17 | private Long id; 18 | 19 | /** 20 | * 名称 21 | */ 22 | private String name; 23 | 24 | /** 25 | * 标签 26 | */ 27 | private String tags; 28 | 29 | /** 30 | * LOGO 31 | */ 32 | private String logo; 33 | 34 | /** 35 | * 创建用户ID 36 | */ 37 | private Long userId; 38 | 39 | /** 40 | * 修改时间 41 | */ 42 | private Date updateTime; 43 | 44 | /** 45 | * 创建时间 46 | */ 47 | private Date createTime; 48 | 49 | } 50 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/po/KnowledgeDetailPo.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.po; 2 | 3 | import com.baomidou.mybatisplus.annotation.IdType; 4 | import com.baomidou.mybatisplus.annotation.TableId; 5 | import com.baomidou.mybatisplus.annotation.TableName; 6 | import lombok.Data; 7 | 8 | @Data 9 | @TableName("knowledge_detail") 10 | public class KnowledgeDetailPo { 11 | 12 | /** 13 | * 主键ID 14 | */ 15 | @TableId(type = IdType.AUTO) 16 | private Long id; 17 | 18 | /** 19 | * 知识库ID 20 | */ 21 | private Long knowledgeId; 22 | 23 | /** 24 | * 知识库明细 25 | */ 26 | private Long itemId; 27 | 28 | /** 29 | * 源文件名称 30 | */ 31 | private String source; 32 | 33 | /** 34 | * 状态 35 | */ 36 | private Integer status; 37 | 38 | /** 39 | * 段落 40 | */ 41 | private String segment; 42 | 43 | /** 44 | * 词向量 45 | */ 46 | private float[] embedding; 47 | 48 | } 49 | 50 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/po/KnowledgeItemPo.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.po; 2 | 3 | import com.baomidou.mybatisplus.annotation.TableName; 4 | import lombok.Data; 5 | 6 | import java.util.Date; 7 | 8 | @Data 9 | @TableName("knowledge_item") 10 | public class KnowledgeItemPo { 11 | 12 | private Long id; 13 | 14 | private Long knowledgeId; 15 | 16 | private Integer splitType; 17 | 18 | private Integer splitStep; 19 | 20 | private String source; 21 | 22 | private Date createTime; 23 | 24 | private Date updateTime; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/repository/impl/ApplicationKnowledgeRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.repository.impl; 2 | 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; 4 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; 5 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 6 | import com.honvay.flychat.knowledge.domain.model.Application; 7 | import com.honvay.flychat.knowledge.domain.model.ApplicationKnowledge; 8 | import com.honvay.flychat.knowledge.domain.model.KnowledgeBase; 9 | import com.honvay.flychat.knowledge.domain.repository.ApplicationKnowledgeRepository; 10 | import com.honvay.flychat.knowledge.infra.mapper.ApplicationKnowledgeMapper; 11 | import com.honvay.flychat.knowledge.infra.po.ApplicationKnowledgePo; 12 | import org.springframework.stereotype.Repository; 13 | 14 | import java.util.List; 15 | 16 | @Repository 17 | public class ApplicationKnowledgeRepositoryImpl extends ServiceImpl implements ApplicationKnowledgeRepository { 18 | @Override 19 | public List findByApplication(Application application) { 20 | LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); 21 | wrapper.eq(ApplicationKnowledgePo::getApplicationId,application.getId()); 22 | return this.getBaseMapper() 23 | .selectList(wrapper) 24 | .stream() 25 | .map(applicationKnowledgePo -> KnowledgeBase.of(applicationKnowledgePo.getKnowledgeBaseId())) 26 | .toList(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/repository/impl/KnowledgeItemRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.repository.impl; 2 | 3 | import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; 4 | import com.honvay.flychat.knowledge.infra.mapper.KnowledgeItemMapper; 5 | import com.honvay.flychat.knowledge.infra.po.KnowledgeItemPo; 6 | import com.honvay.flychat.knowledge.domain.repository.KnowledgeItemRepository; 7 | import org.springframework.stereotype.Repository; 8 | 9 | @Repository 10 | public class KnowledgeItemRepositoryImpl extends ServiceImpl implements KnowledgeItemRepository { 11 | } 12 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/splitter/FixedTextSplitter.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.splitter; 2 | 3 | import com.honvay.flychat.knowledge.domain.model.SplitType; 4 | import dev.langchain4j.data.document.Document; 5 | import dev.langchain4j.data.document.DocumentSplitter; 6 | import dev.langchain4j.data.document.splitter.CharacterSplitter; 7 | import dev.langchain4j.data.segment.TextSegment; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.stream.Collectors; 13 | 14 | @Component 15 | public class FixedTextSplitter implements TextSplitter { 16 | 17 | @Override 18 | public SplitType getSplitType() { 19 | return SplitType.FIXED; 20 | } 21 | 22 | public List split(Document document, Map properties) { 23 | DocumentSplitter splitter = new CharacterSplitter((int)properties.get("size"),0); 24 | List documentSegments = splitter.split(document); 25 | return documentSegments.stream() 26 | .map(TextSegment::text) 27 | .collect(Collectors.toList()); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/splitter/ParagraphTextSplitter.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.splitter; 2 | 3 | import com.honvay.flychat.knowledge.domain.model.SplitType; 4 | import dev.langchain4j.data.document.Document; 5 | import dev.langchain4j.data.document.DocumentSplitter; 6 | import dev.langchain4j.data.document.splitter.ParagraphSplitter; 7 | import dev.langchain4j.data.segment.TextSegment; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.stream.Collectors; 13 | 14 | @Component 15 | public class ParagraphTextSplitter implements TextSplitter { 16 | 17 | 18 | @Override 19 | public SplitType getSplitType() { 20 | return SplitType.SENTENCE; 21 | } 22 | 23 | @Override 24 | public List split(Document document, Map properties) { 25 | DocumentSplitter splitter = new ParagraphSplitter(); 26 | List documentSegments = splitter.split(document); 27 | return documentSegments.stream() 28 | .map(TextSegment::text) 29 | .collect(Collectors.toList()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/splitter/SentenceTextSplitter.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.splitter; 2 | 3 | import com.honvay.flychat.knowledge.domain.model.SplitType; 4 | import dev.langchain4j.data.document.Document; 5 | import dev.langchain4j.data.document.DocumentSplitter; 6 | import dev.langchain4j.data.document.splitter.SentenceSplitter; 7 | import dev.langchain4j.data.segment.TextSegment; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | import java.util.stream.Collectors; 13 | 14 | @Component 15 | public class SentenceTextSplitter implements TextSplitter { 16 | 17 | 18 | @Override 19 | public SplitType getSplitType() { 20 | return SplitType.SENTENCE; 21 | } 22 | 23 | @Override 24 | public List split(Document document,Map properties) { 25 | DocumentSplitter splitter = new SentenceSplitter(); 26 | List documentSegments = splitter.split(document); 27 | return documentSegments.stream() 28 | .map(TextSegment::text) 29 | .collect(Collectors.toList()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/infra/splitter/TextSplitter.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.infra.splitter; 2 | 3 | import com.honvay.flychat.knowledge.domain.model.SplitType; 4 | import dev.langchain4j.data.document.Document; 5 | 6 | import java.util.List; 7 | import java.util.Map; 8 | 9 | public interface TextSplitter { 10 | 11 | List split(Document document, Map properties); 12 | 13 | SplitType getSplitType(); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/web/converter/KnowledgeAssembler.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.web.converter; 2 | 3 | import com.honvay.flychat.knowledge.domain.model.KnowledgeBase; 4 | import com.honvay.flychat.knowledge.domain.model.KnowledgeItem; 5 | import com.honvay.flychat.knowledge.domain.model.Owner; 6 | import com.honvay.flychat.knowledge.domain.model.SplitType; 7 | import com.honvay.flychat.knowledge.web.model.dto.KnowledgeDto; 8 | import com.honvay.flychat.knowledge.web.model.dto.KnowledgeItemDto; 9 | import org.springframework.stereotype.Component; 10 | 11 | @Component 12 | public class KnowledgeAssembler { 13 | 14 | public KnowledgeBase assemble(KnowledgeDto knowledgeDto){ 15 | KnowledgeBase knowledgeBase = new KnowledgeBase(); 16 | knowledgeBase.setId(knowledgeDto.getId()); 17 | knowledgeBase.setName(knowledgeDto.getName()); 18 | knowledgeBase.setTags(knowledgeDto.getTags()); 19 | knowledgeBase.setLogo(knowledgeDto.getLogo()); 20 | Owner owner = new Owner(); 21 | owner.setId(1L); 22 | knowledgeBase.setOwner(owner); 23 | return knowledgeBase; 24 | } 25 | 26 | public KnowledgeItem getKnowledgeItem(KnowledgeItemDto knowledgeItemDto){ 27 | KnowledgeItem knowledgeItem = new KnowledgeItem(); 28 | knowledgeItem.setId(knowledgeItemDto.getId()); 29 | knowledgeItem.setName(knowledgeItemDto.getName()); 30 | knowledgeItem.setSplitType(SplitType.of(knowledgeItemDto.getSplitType())); 31 | return knowledgeItem; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/web/converter/KnowledgeChatAssembler.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.web.converter; 2 | 3 | import com.honvay.flychat.chat.domain.model.User; 4 | import com.honvay.flychat.knowledge.domain.model.Application; 5 | import com.honvay.flychat.knowledge.domain.model.KnowledgeChat; 6 | import com.honvay.flychat.knowledge.web.model.dto.KnowledgeChatDto; 7 | import org.springframework.stereotype.Component; 8 | 9 | @Component 10 | public class KnowledgeChatAssembler { 11 | 12 | public KnowledgeChat assemble(KnowledgeChatDto knowledgeChatDto){ 13 | KnowledgeChat knowledgeChat = new KnowledgeChat(); 14 | knowledgeChat.setId(knowledgeChatDto.getChatId()); 15 | Application application = new Application(); 16 | application.setId(knowledgeChatDto.getApplicationId()); 17 | knowledgeChat.setApplication(application); 18 | User owner = new User(); 19 | owner.setId(knowledgeChatDto.getUserId()); 20 | knowledgeChat.setUser(owner); 21 | return knowledgeChat; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/web/model/dto/KnowledgeChatDto.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.web.model.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class KnowledgeChatDto { 7 | 8 | private Long chatId; 9 | 10 | private Long applicationId; 11 | 12 | private String question; 13 | 14 | private Long userId; 15 | } 16 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/web/model/dto/KnowledgeDto.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.web.model.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class KnowledgeDto { 7 | 8 | private Long id; 9 | 10 | private String name; 11 | 12 | private String tags; 13 | 14 | private String logo; 15 | 16 | private Integer category; 17 | 18 | private String introduction; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /knowledge/src/main/java/com/honvay/flychat/knowledge/web/model/dto/KnowledgeItemDto.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.web.model.dto; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class KnowledgeItemDto { 7 | 8 | public final static int TEXT_TYPE = 1; 9 | 10 | public final static int FILE_TYPE = 2; 11 | 12 | private Long knowledgeId; 13 | 14 | private Long id; 15 | 16 | private String name; 17 | 18 | private Integer splitType; 19 | 20 | private Integer type; 21 | 22 | private String source; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /knowledge/src/main/resources/mapper/KnowledgeDetailMapper.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 18 | -------------------------------------------------------------------------------- /knowledge/src/test/java/com/honvay/flychat/knowledge/llama/OpenAiEmbeddingServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.llama; 2 | 3 | import com.honvay.flychat.knowledge.infra.splitter.SentenceTextSplitter; 4 | import com.honvay.flychat.langchain.llama.embedding.OpenAiEmbeddingService; 5 | import dev.langchain4j.data.document.Document; 6 | import dev.langchain4j.data.document.DocumentSource; 7 | import dev.langchain4j.data.document.FileSystemDocumentLoader; 8 | import dev.langchain4j.data.document.source.FileSystemSource; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.io.File; 12 | import java.nio.file.Paths; 13 | import java.util.Arrays; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | 19 | class OpenAiEmbeddingServiceImplTest { 20 | 21 | @Test 22 | void embed(){ 23 | File file = new File("/Users/user/workspace/ChatGPT/flychat/chat/src/test/resources/story-about-happy-carrot.pdf"); 24 | 25 | DocumentSource documentSource = FileSystemSource.from(file); 26 | Document document = FileSystemDocumentLoader.loadDocument(Paths.get("/Users/liqiu/workspace/ChatGPT/flychat/chat/src/test/resources/story-about-happy-carrot.pdf")); 27 | 28 | SentenceTextSplitter splitter = new SentenceTextSplitter(); 29 | Map properties = new HashMap<>(); 30 | List segments = splitter.split(document, properties); 31 | 32 | OpenAiEmbeddingService openAiService = new OpenAiEmbeddingService(System.getenv("OPENAI_API_KEY")); 33 | List embeddings = openAiService.embed(segments); 34 | System.out.println(embeddings); 35 | } 36 | 37 | @Test 38 | void embedText(){ 39 | String text = "Who is Charlie? Answer in 10 words."; 40 | OpenAiEmbeddingService openAiService = new OpenAiEmbeddingService(System.getenv("OPENAI_API_KEY")); 41 | float[] embeddings = openAiService.embed(text); 42 | System.out.println(Arrays.toString(embeddings)); 43 | } 44 | 45 | } -------------------------------------------------------------------------------- /knowledge/src/test/java/com/honvay/flychat/knowledge/splitter/FixedTextSplitterTest.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.splitter; 2 | 3 | import com.honvay.flychat.knowledge.infra.splitter.FixedTextSplitter; 4 | import dev.langchain4j.data.document.Document; 5 | import dev.langchain4j.data.document.DocumentSource; 6 | import dev.langchain4j.data.document.FileSystemDocumentLoader; 7 | import dev.langchain4j.data.document.source.FileSystemSource; 8 | import org.junit.jupiter.api.Assertions; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.io.File; 12 | import java.nio.file.Paths; 13 | import java.util.HashMap; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | class FixedTextSplitterTest { 18 | 19 | @Test 20 | void split() { 21 | File file = new File("/Users/user/workspace/ChatGPT/flychat/chat/src/test/resources/story-about-happy-carrot.pdf"); 22 | 23 | DocumentSource documentSource = FileSystemSource.from(file); 24 | Document document = FileSystemDocumentLoader.loadDocument(Paths.get("/Users/liqiu/workspace/ChatGPT/flychat/chat/src/test/resources/story-about-happy-carrot.pdf")); 25 | 26 | FixedTextSplitter splitter = new FixedTextSplitter(); 27 | Map properties = new HashMap<>(); 28 | properties.put("size",200); 29 | List segments = splitter.split(document, properties); 30 | for (String segment : segments) { 31 | Assertions.assertTrue(segment.length() <= 200); 32 | System.out.println(segment + " : " + segment.length()); 33 | } 34 | } 35 | 36 | @Test 37 | void testSplit() { 38 | } 39 | } -------------------------------------------------------------------------------- /knowledge/src/test/java/com/honvay/flychat/knowledge/splitter/ParagraphTextSplitterTest.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.splitter; 2 | 3 | import com.honvay.flychat.knowledge.infra.splitter.ParagraphTextSplitter; 4 | import dev.langchain4j.data.document.Document; 5 | import dev.langchain4j.data.document.DocumentSource; 6 | import dev.langchain4j.data.document.FileSystemDocumentLoader; 7 | import dev.langchain4j.data.document.source.FileSystemSource; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.io.File; 11 | import java.nio.file.Paths; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | class ParagraphTextSplitterTest { 17 | 18 | @Test 19 | void testSplit(){ 20 | File file = new File("/Users/user/workspace/ChatGPT/flychat/chat/src/test/resources/story-about-happy-carrot.pdf"); 21 | 22 | DocumentSource documentSource = FileSystemSource.from(file); 23 | Document document = FileSystemDocumentLoader.loadDocument(Paths.get("/Users/liqiu/workspace/ChatGPT/flychat/chat/src/test/resources/story-about-happy-carrot.pdf")); 24 | 25 | ParagraphTextSplitter splitter = new ParagraphTextSplitter(); 26 | Map properties = new HashMap<>(); 27 | List segments = splitter.split(document, properties); 28 | for (String segment : segments) { 29 | System.out.println(segment + " : " + segment.length()); 30 | } 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /knowledge/src/test/java/com/honvay/flychat/knowledge/splitter/SentenceTextSplitterTest.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.knowledge.splitter; 2 | 3 | import com.honvay.flychat.knowledge.infra.splitter.SentenceTextSplitter; 4 | import dev.langchain4j.data.document.Document; 5 | import dev.langchain4j.data.document.DocumentSource; 6 | import dev.langchain4j.data.document.FileSystemDocumentLoader; 7 | import dev.langchain4j.data.document.source.FileSystemSource; 8 | import org.junit.jupiter.api.Test; 9 | 10 | import java.io.File; 11 | import java.nio.file.Paths; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | class SentenceTextSplitterTest { 17 | 18 | @Test 19 | void testSplit(){ 20 | File file = new File("/Users/user/workspace/ChatGPT/flychat/chat/src/test/resources/story-about-happy-carrot.pdf"); 21 | 22 | DocumentSource documentSource = FileSystemSource.from(file); 23 | Document document = FileSystemDocumentLoader.loadDocument(Paths.get("/Users/liqiu/workspace/ChatGPT/flychat/chat/src/test/resources/story-about-happy-carrot.pdf")); 24 | 25 | SentenceTextSplitter splitter = new SentenceTextSplitter(); 26 | Map properties = new HashMap<>(); 27 | List segments = splitter.split(document, properties); 28 | for (String segment : segments) { 29 | System.out.println(segment + " : " + segment.length()); 30 | } 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /knowledge/src/test/resources/story-about-happy-carrot.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leecho/flychat/d238fbd2a60673243686764d8aaff3ae956177af/knowledge/src/test/resources/story-about-happy-carrot.pdf -------------------------------------------------------------------------------- /langchain/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.honvay 8 | flychat 9 | 0.0.1-SNAPSHOT 10 | 11 | 12 | langchain 13 | 14 | 15 | 17 16 | 17 17 | UTF-8 18 | 19 | 20 | 21 | 22 | dev.langchain4j 23 | langchain4j 24 | 0.16.0 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter 29 | 30 | 31 | org.projectlombok 32 | lombok 33 | 34 | 35 | org.apache.poi 36 | poi 37 | 5.2.3 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-test 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /langchain/src/main/java/com/honvay/flychat/langchain/chat/ChatModelService.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.langchain.chat; 2 | 3 | import dev.langchain4j.data.message.ChatMessage; 4 | 5 | import java.util.List; 6 | 7 | public interface ChatModelService { 8 | String chat(String promptText, ChatSetup setup); 9 | 10 | String chat(List messages, ChatSetup setup); 11 | 12 | int estimateTokenCount(String content, String modelName); 13 | 14 | void chat(String promptText, ChatSetup setup, StreamChatObserver observer); 15 | 16 | void chat(List messages, ChatSetup setup, StreamChatObserver observer); 17 | } 18 | -------------------------------------------------------------------------------- /langchain/src/main/java/com/honvay/flychat/langchain/chat/ChatSetup.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.langchain.chat; 2 | 3 | import lombok.Data; 4 | 5 | @Data 6 | public class ChatSetup { 7 | 8 | private String modelName; 9 | 10 | private Double temperature; 11 | 12 | private String apiKey; 13 | 14 | private Integer maxTokens; 15 | 16 | private Double topP; 17 | 18 | private Double presencePenalty; 19 | 20 | private Double frequencyPenalty; 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /langchain/src/main/java/com/honvay/flychat/langchain/chat/DefaultStreamChatObserver.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.langchain.chat; 2 | 3 | import java.util.function.Consumer; 4 | 5 | public class DefaultStreamChatObserver implements StreamChatObserver { 6 | 7 | private final Consumer resultHandler; 8 | 9 | private final Consumer completeHandler; 10 | 11 | private final Consumer errorHandler; 12 | 13 | public DefaultStreamChatObserver(Consumer resultHandler, 14 | Consumer completeHandler, 15 | Consumer errorHandler) { 16 | this.resultHandler = resultHandler; 17 | this.completeHandler = completeHandler; 18 | this.errorHandler = errorHandler; 19 | } 20 | 21 | 22 | @Override 23 | public void onResult(String result) { 24 | resultHandler.accept(result); 25 | } 26 | 27 | @Override 28 | public void onError(Throwable throwable) { 29 | errorHandler.accept(throwable); 30 | } 31 | 32 | @Override 33 | public void onComplete() { 34 | completeHandler.accept(null); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /langchain/src/main/java/com/honvay/flychat/langchain/chat/StreamChatObserver.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.langchain.chat; 2 | 3 | public interface StreamChatObserver { 4 | 5 | void onResult(String result); 6 | 7 | void onError(Throwable throwable); 8 | 9 | void onComplete(); 10 | } 11 | -------------------------------------------------------------------------------- /langchain/src/main/java/com/honvay/flychat/langchain/document/StreamDocumentSource.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.langchain.document; 2 | 3 | import dev.langchain4j.data.document.DocumentSource; 4 | import dev.langchain4j.data.document.Metadata; 5 | 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | 9 | public class StreamDocumentSource implements DocumentSource { 10 | 11 | private final InputStream source; 12 | 13 | private final Metadata metadata; 14 | 15 | public StreamDocumentSource(InputStream source, 16 | Metadata metadata) { 17 | this.source = source; 18 | this.metadata = metadata; 19 | } 20 | 21 | public StreamDocumentSource(InputStream source) { 22 | this.source = source; 23 | this.metadata = new Metadata(); 24 | } 25 | 26 | 27 | @Override 28 | public InputStream inputStream() throws IOException { 29 | return source; 30 | } 31 | 32 | @Override 33 | public Metadata sourceMetadata() { 34 | return metadata; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /langchain/src/main/java/com/honvay/flychat/langchain/llama/embedding/EmbeddingService.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.langchain.llama.embedding; 2 | 3 | import java.util.List; 4 | 5 | public interface EmbeddingService { 6 | 7 | List embed(List texts); 8 | 9 | float[] embed(String text); 10 | } 11 | -------------------------------------------------------------------------------- /langchain/src/main/java/com/honvay/flychat/langchain/llama/embedding/OpenAiEmbeddingService.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.langchain.llama.embedding; 2 | 3 | import dev.langchain4j.data.embedding.Embedding; 4 | import dev.langchain4j.data.segment.TextSegment; 5 | import dev.langchain4j.model.embedding.EmbeddingModel; 6 | import dev.langchain4j.model.openai.OpenAiEmbeddingModel; 7 | import dev.langchain4j.model.openai.OpenAiModelName; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.stereotype.Service; 10 | 11 | import java.time.Duration; 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | 15 | @Service 16 | public class OpenAiEmbeddingService implements EmbeddingService { 17 | 18 | private final String apiKey; 19 | 20 | public OpenAiEmbeddingService(@Value("${openai.apiKey:}") String apiKey) { 21 | this.apiKey = apiKey; 22 | } 23 | 24 | @Override 25 | public List embed(List texts){ 26 | 27 | List segments = texts.stream() 28 | .map(TextSegment::from) 29 | .collect(Collectors.toList()); 30 | 31 | EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder() 32 | .apiKey(apiKey) // https://platform.openai.com/account/api-keys 33 | .modelName(OpenAiModelName.TEXT_EMBEDDING_ADA_002) 34 | .timeout(Duration.ofSeconds(15)) 35 | .build(); 36 | 37 | List embeddings = embeddingModel.embedAll(segments); 38 | return embeddings 39 | .stream() 40 | .map(Embedding::vector) 41 | .collect(Collectors.toList()); 42 | } 43 | 44 | @Override 45 | public float[] embed(String text) { 46 | 47 | EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder() 48 | .apiKey(apiKey) // https://platform.openai.com/account/api-keys 49 | .modelName(OpenAiModelName.TEXT_EMBEDDING_ADA_002) 50 | .timeout(Duration.ofSeconds(15)) 51 | .build(); 52 | 53 | Embedding embedding = embeddingModel.embed(text); 54 | return embedding.vector(); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /langchain/src/main/java/com/honvay/flychat/langchain/parser/WordDocumentParser.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.langchain.parser; 2 | 3 | import dev.langchain4j.data.document.Document; 4 | import dev.langchain4j.data.document.DocumentParser; 5 | import dev.langchain4j.data.document.Metadata; 6 | import org.apache.poi.extractor.ExtractorFactory; 7 | import org.apache.poi.extractor.POITextExtractor; 8 | 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | 12 | public class WordDocumentParser implements DocumentParser { 13 | @Override 14 | public Document parse(InputStream inputStream) { 15 | POITextExtractor extractor; 16 | try { 17 | extractor = ExtractorFactory.createExtractor(inputStream); 18 | } catch (IOException e) { 19 | throw new RuntimeException(e); 20 | } 21 | return new Document(extractor.getText(),new Metadata()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /langchain/src/test/java/com/honvay/flychat/langchain/chat/OpenAiChatModelServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.honvay.flychat.langchain.chat; 2 | 3 | import dev.langchain4j.model.openai.OpenAiTokenizer; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.junit.jupiter.api.Assertions.*; 7 | 8 | class OpenAiChatModelServiceTest { 9 | 10 | @Test 11 | void countTokenSize(){ 12 | long start = System.currentTimeMillis(); 13 | OpenAiTokenizer openAiTokenizer = new OpenAiTokenizer("gpt-3.5-turbo"); 14 | openAiTokenizer.countTokens("Hello! How can I assist you today?"); 15 | System.out.println((System.currentTimeMillis() - start) / 1000); 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.1.1 9 | 10 | 11 | com.honvay 12 | flychat 13 | 0.0.1-SNAPSHOT 14 | pom 15 | flychat 16 | 17 | framework 18 | knowledge 19 | bootstrap 20 | chat 21 | langchain 22 | conversation 23 | 24 | flychat 25 | 26 | 17 27 | 28 | 29 | 30 | 31 | 32 | com.honvay 33 | framework-core 34 | 0.0.1-SNAPSHOT 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-maven-plugin 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /screenshots/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/leecho/flychat/d238fbd2a60673243686764d8aaff3ae956177af/screenshots/img.png --------------------------------------------------------------------------------