Ver Fonte

change client service

tycoding há 1 ano atrás
pai
commit
878f9c93df
55 ficheiros alterados com 543 adições e 1152 exclusões
  1. 2 0
      langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcOssController.java
  2. 2 1
      langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/entity/AigcOss.java
  3. 1 0
      langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/service/impl/AigcMessageServiceImpl.java
  4. 3 3
      langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/utils/AigcAuthUtil.java
  5. 0 3
      langchat-auth/src/main/java/cn/tycoding/langchat/auth/config/AuthConfiguration.java
  6. 9 0
      langchat-auth/src/main/java/cn/tycoding/langchat/auth/service/GlobalExceptionTranslator.java
  7. 0 5
      langchat-common/src/main/java/cn/tycoding/langchat/common/constant/CommonConst.java
  8. 2 2
      langchat-common/src/main/java/cn/tycoding/langchat/common/dto/ChatRes.java
  9. 22 111
      langchat-common/src/main/java/cn/tycoding/langchat/common/dto/PromptConst.java
  10. 31 0
      langchat-common/src/main/java/cn/tycoding/langchat/common/exception/AuthException.java
  11. 1 1
      langchat-common/src/main/java/cn/tycoding/langchat/common/utils/PromptUtil.java
  12. 5 5
      langchat-core/src/main/java/cn/tycoding/langchat/core/properties/chat/OpenaiProps.java
  13. 3 1
      langchat-core/src/main/java/cn/tycoding/langchat/core/provider/ModelProvider.java
  14. 3 3
      langchat-core/src/main/java/cn/tycoding/langchat/core/service/Assistant.java
  15. 1 5
      langchat-core/src/main/java/cn/tycoding/langchat/core/service/LangChatService.java
  16. 1 2
      langchat-core/src/main/java/cn/tycoding/langchat/core/service/LangDocService.java
  17. 5 18
      langchat-core/src/main/java/cn/tycoding/langchat/core/service/impl/LangChatServiceImpl.java
  18. 11 12
      langchat-core/src/main/java/cn/tycoding/langchat/core/service/impl/LangDocServiceImpl.java
  19. 49 20
      langchat-server/src/main/java/cn/tycoding/langchat/aigc/endpoint/AigcChatEndpoint.java
  20. 2 3
      langchat-server/src/main/java/cn/tycoding/langchat/aigc/service/ChatService.java
  21. 17 0
      langchat-server/src/main/java/cn/tycoding/langchat/aigc/service/EmbeddingService.java
  22. 18 20
      langchat-server/src/main/java/cn/tycoding/langchat/aigc/service/impl/ChatServiceImpl.java
  23. 18 7
      langchat-server/src/main/java/cn/tycoding/langchat/aigc/service/impl/EmbeddingServiceImpl.java
  24. 0 2
      langchat-ui-client/package.json
  25. 28 554
      langchat-ui-client/pnpm-lock.yaml
  26. 3 37
      langchat-ui-client/src/api/chat.ts
  27. 6 8
      langchat-ui-client/src/api/docs.ts
  28. 2 0
      langchat-ui-client/src/layout/Sider.vue
  29. 1 1
      langchat-ui-client/src/locales/zh-CN.ts
  30. 0 3
      langchat-ui-client/src/main.ts
  31. 0 9
      langchat-ui-client/src/router/index.ts
  32. 1 1
      langchat-ui-client/src/styles/lib/github-markdown.less
  33. 2 10
      langchat-ui-client/src/utils/request/index.ts
  34. 23 7
      langchat-ui-client/src/views/modules/chat/Header.vue
  35. 73 84
      langchat-ui-client/src/views/modules/chat/index.vue
  36. 3 13
      langchat-ui-client/src/views/modules/chat/message/Avatar.vue
  37. 1 1
      langchat-ui-client/src/views/modules/chat/message/Message.vue
  38. 4 4
      langchat-ui-client/src/views/modules/chat/sider/List.vue
  39. 1 2
      langchat-ui-client/src/views/modules/chat/sider/index.vue
  40. 4 5
      langchat-ui-client/src/views/modules/chat/store/chat.d.ts
  41. 7 38
      langchat-ui-client/src/views/modules/chat/store/useChatStore.ts
  42. 43 13
      langchat-ui-client/src/views/modules/doc/components/Chat.vue
  43. 3 3
      langchat-ui-client/src/views/modules/doc/components/FileList.vue
  44. 15 41
      langchat-ui-client/src/views/modules/doc/components/FileView.vue
  45. 2 2
      langchat-ui-client/src/views/modules/doc/components/Message.vue
  46. 1 2
      langchat-ui-client/src/views/modules/doc/index.vue
  47. 4 4
      langchat-ui-client/src/views/modules/image/component/DALL.vue
  48. 11 9
      langchat-ui-client/src/views/modules/image/index.vue
  49. 6 6
      langchat-ui-client/src/views/modules/mermaid/components/Mermaid.vue
  50. 19 14
      langchat-ui-client/src/views/modules/mermaid/components/Sider.vue
  51. 40 27
      langchat-ui-client/src/views/modules/mermaid/index.vue
  52. 2 4
      langchat-ui-client/src/views/modules/mindmap/components/MindMap.vue
  53. 0 1
      langchat-ui-client/src/views/modules/mindmap/components/Sider.vue
  54. 17 6
      langchat-ui-client/src/views/modules/mindmap/index.vue
  55. 15 19
      langchat-upms/src/main/java/cn/tycoding/langchat/upms/service/impl/SysUserServiceImpl.java

+ 2 - 0
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/controller/AigcOssController.java

@@ -2,6 +2,7 @@ package cn.tycoding.langchat.aigc.controller;
 
 import cn.tycoding.langchat.aigc.entity.AigcOss;
 import cn.tycoding.langchat.aigc.service.AigcOssService;
+import cn.tycoding.langchat.aigc.utils.AigcAuthUtil;
 import cn.tycoding.langchat.common.utils.R;
 import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import lombok.AllArgsConstructor;
@@ -24,6 +25,7 @@ public class AigcOssController {
     @GetMapping("/list")
     public R list() {
         List<AigcOss> list = aigcOssService.list(Wrappers.<AigcOss>lambdaQuery()
+                .eq(AigcOss::getUserId, AigcAuthUtil.getUserId())
                 .orderByDesc(AigcOss::getCreateTime)
         );
         return R.ok(list);

+ 2 - 1
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/entity/AigcOss.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.annotation.IdType;
 import com.baomidou.mybatisplus.annotation.TableId;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
+import lombok.experimental.Accessors;
 
 /**
  * @author tycoding
@@ -12,8 +13,8 @@ import lombok.EqualsAndHashCode;
  */
 @EqualsAndHashCode(callSuper = true)
 @Data
+@Accessors(chain = true)
 public class AigcOss extends OssR {
-
     private static final long serialVersionUID = -250127374910520163L;
 
     /**

+ 1 - 0
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/service/impl/AigcMessageServiceImpl.java

@@ -76,6 +76,7 @@ public class AigcMessageServiceImpl extends ServiceImpl<AigcMessageMapper, AigcM
     @Override
     public AigcConversation addConversation(AigcConversation conversation) {
         conversation.setCreateTime(new Date());
+        conversation.setUserId(AigcAuthUtil.getUserId());
         aigcConversationMapper.insert(conversation);
         return conversation;
     }

+ 3 - 3
langchat-aigc/src/main/java/cn/tycoding/langchat/aigc/utils/AigcAuthUtil.java

@@ -4,13 +4,13 @@ import cn.dev33.satoken.secure.SaSecureUtil;
 import cn.dev33.satoken.stp.StpUtil;
 import cn.tycoding.langchat.aigc.entity.AigcUser;
 import cn.tycoding.langchat.common.constant.CacheConst;
+import cn.tycoding.langchat.common.exception.AuthException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
+import java.util.Objects;
 import org.springframework.web.context.request.RequestContextHolder;
 import org.springframework.web.context.request.ServletRequestAttributes;
 
-import java.util.Objects;
-
 /**
  * 权限相关方法
  *
@@ -50,7 +50,7 @@ public class AigcAuthUtil {
         try {
             return (AigcUser) StpUtil.getSession().get(CacheConst.AUTH_USER_INFO_KEY);
         } catch (Exception e) {
-            return null;
+            throw new AuthException();
         }
     }
 

+ 0 - 3
langchat-auth/src/main/java/cn/tycoding/langchat/auth/config/AuthConfiguration.java

@@ -31,9 +31,6 @@ public class AuthConfiguration {
     private final AuthProps authProps;
     private final String[] skipUrl = new String[]{
             "/auth/login",
-            "/aigc/conversation/list",
-            "/aigc/file/list",
-            "/aigc/prompt/page",
             "/aigc/auth/**",
     };
 

+ 9 - 0
langchat-auth/src/main/java/cn/tycoding/langchat/auth/service/GlobalExceptionTranslator.java

@@ -2,6 +2,7 @@ package cn.tycoding.langchat.auth.service;
 
 import cn.dev33.satoken.exception.NotLoginException;
 import cn.dev33.satoken.exception.SaTokenException;
+import cn.tycoding.langchat.common.exception.AuthException;
 import cn.tycoding.langchat.common.exception.ServiceException;
 import cn.tycoding.langchat.common.utils.R;
 import lombok.RequiredArgsConstructor;
@@ -36,6 +37,14 @@ public class GlobalExceptionTranslator {
         return R.fail(e.getCode(), e.getMessage());
     }
 
+    @ExceptionHandler({AuthException.class})
+    @ResponseStatus(HttpStatus.UNAUTHORIZED)
+    public R handleError(AuthException e) {
+        log.error("----------授权错误----------");
+        e.printStackTrace();
+        return R.fail(e.getCode(), e.getMessage());
+    }
+
     @ExceptionHandler({AccessDeniedException.class})
     @ResponseStatus(HttpStatus.FORBIDDEN)
     public R handleError(AccessDeniedException e) {

+ 0 - 5
langchat-common/src/main/java/cn/tycoding/langchat/common/constant/CommonConst.java

@@ -26,10 +26,5 @@ public interface CommonConst {
      */
     String MENU_ICON = "alert";
 
-    /**
-     * 默认用户头像路径
-     */
-    String DEFAULT_AVATAR = "/default.png";
-
     String LAYOUT = "LAYOUT";
 }

+ 2 - 2
langchat-common/src/main/java/cn/tycoding/langchat/common/dto/ChatRes.java

@@ -19,8 +19,8 @@ public class ChatRes {
 
     private long time;
 
-    public ChatRes(String content) {
-        this.message = content;
+    public ChatRes(String message) {
+        this.message = message;
     }
 
     public ChatRes(Integer usedToken, long startTime) {

+ 22 - 111
langchat-common/src/main/java/cn/tycoding/langchat/common/dto/PromptConst.java

@@ -8,8 +8,7 @@ public interface PromptConst {
 
     String QUESTION = "question";
 
-    String EMPTY = "Answer the following question to the best of your ability, question: [{{question}}]";
-    String DOCUMENT = "Please answer the relevant questions based on the following references, question: [{{question}}]";
+    String DOCUMENT = "You are good at analyzing documents. Please analyze my questions according to the following documents, question: [{{question}}], [docs]";
 
     String CHART_LINE = """
             # 角色
@@ -77,23 +76,23 @@ public interface PromptConst {
             """;
 
     String MINDMAP = """
-            # 角色
-            你是一位专注于回答用户问题的Markdown大纲格式工程师。你能迅速准确地将用户的问题转化成精炼的Markdown大纲标题,并细化每个标题的具体细节信息。
-                        
-            ## 技能
-            ### 技能1:识别用户问题意图
-            - 准确理解用户问题的具体内容和需求。
-            ### 技能2:转化成Markdown大纲
-            - 将用户的问题简化为Markdown大纲风格的标题。
-            ### 技能3:返回给用户
-            - 将优化后的大纲返回给用户。
-                        
-            ## 约束
-            - 只返回整理后的Markdown格式内容,不用做其他的解释信息
-            - 使用用户所使用的语言回答问题。
-            - 按照Markdown风格返回答案,主标题尽量简洁;子标题细化每个主标题的具体步骤信息。
-                        
-            用户输入的内容如下:{content}
+            # Role
+            You are a Markdown outline format engineer who focuses on answering user questions. You can quickly and accurately convert user questions into refined Markdown outline titles, and refine the specific details of each title.
+            
+            ## Skills
+            ### Skill 1: Identify user question intent
+            - Accurately understand the specific content and needs of user questions.
+            ### Skill 2: Convert to Markdown outline
+            - Simplify user questions into Markdown outline-style titles.
+            ### Skill 3: Return to user
+            - Return the optimized outline to the user.
+            
+            ## Constraints
+            - Only return the organized Markdown format content, without other explanation information
+            - Answer the question in the language used by the user.
+            - Return the answer in Markdown style, keep the main title as concise as possible; and refine the specific step information of each main title in the subtitle.
+            
+            The user input is as follows: [{{question}}]
             """;
 
     String WRITE = """
@@ -167,96 +166,8 @@ public interface PromptConst {
             {{content}}
             """;
 
-    String MERMAID = """
-            You are MermaidGPT, whose sole purpose is to create Mermaid.js diagrams. You are not allowed to answer with anything else except valid Mermaid.js diagram syntax code. The diagrams should be clean, but include everything that's required. Do not wrap the response in a code block. Directly return content that conforms to mermaid.js syntax without any explanation.: 
-            {{content}}
-            """;
-
-    String PROMPT = """
-            # # Role:Prompt工程师
-            1. Don't break character under any circumstance.
-            2. Don't talk nonsense and make up facts.
-                        
-            ## Profile:
-            - Author:pp
-            - Version:1.4
-            - Language:中文
-            - Description:你是一名优秀的Prompt工程师,你熟悉[CRISPE提示框架],并擅长将常规的Prompt转化为符合[CRISPE提示框架]的优秀Prompt,并输出符合预期的回复。
-                        
-            ## Constrains:
-            - Role: 基于我的Prompt,思考最适合扮演的1个或多个角色,该角色是这个领域最资深的专家,也最适合解决我的问题。
-            - Profile: 基于我的Prompt,思考我为什么会提出这个问题,陈述我提出这个问题的原因、背景、上下文。
-            - Goals: 基于我的Prompt,思考我需要提给chatGPT的任务清单,完成这些任务,便可以解决我的问题。
-            - Skill:基于我的Prompt,思考我需要提给chatGPT的任务清单,完成这些任务,便可以解决我的问题。
-            - OutputFormat: 基于我的Prompt,基于我OutputFormat实例进行输出。
-            - Workflow: 基于我的Prompt,要求提供几个不同的例子,更好的进行解释。
-            - Don't break character under any circumstance.
-            - Don't talk nonsense and make up facts.
-                        
-            ## Skill:
-            1. 熟悉[CRISPE提示框架]。
-            2. 能够将常规的Prompt转化为符合[CRISPE提示框架]的优秀Prompt。
-                        
-            ## Workflow:
-            1. 分析我的问题(Prompt)。
-            2. 根据[CRISPE提示框架]的要求,确定最适合扮演的角色。
-            3. 根据我的问题(Prompt)的原因、背景和上下文,构建一个符合[CRISPE提示框架]的优秀Prompt。
-            4. Workflow,基于我的问题进行写出Workflow,回复不低于5个步骤
-            5. Initialization,内容一定要是基于我提问的问题
-            6. 生成回复,确保回复符合预期。
-                        
-            ## OutputFormat:
-                、、、
-                # Role:角色名称
-                        
-                ## Profile:
-                - Author: YZFly
-                - Version: 0.1
-                - Language: 中文
-                - Description: Describe your role. Give an overview of the character's characteristics and skills
-                        
-                ### Skill:
-                1.技能描述1
-                2.技能描述2
-                3.技能描述3
-                4.技能描述4
-                5.技能描述5
-                        
-                ## Goals:
-                1.目标1
-                2.目标2
-                3.目标3
-                4.目标4
-                5.目标5
-                        
-                ## Constrains:
-                1.约束条件1
-                2.约束条件2
-                3.约束条件3
-                4.约束条件4
-                5.约束条件5
-                        
-                ## OutputFormat:
-                1.输出要求1
-                2.输出要求2
-                3.输出要求3
-                4.输出要求4
-                5.输出要求5
-                        
-                ## Workflow:
-                1. First, xxx
-                2. Then, xxx
-                3. Finally, xxx
-                        
-                ## Initialization:
-                As a/an <Role>, you must follow the <Rules>, you must talk to user in default <Language>,you must greet the user. Then introduce yourself and introduce the <Workflow>.
-                、、、
-                        
-            ## Initialization:
-                接下来我会给出我的问题(Prompt),请根据我的Prompt
-                1.基于[CRISPE提示框架],请一步一步进行输出,直到最终输出[优化Promot];
-                2.输出完毕之后,请咨询我是否有需要改进的意见,如果有建议,请结合建议重新基于[CRISPE提示框架]输出。
-                要求:请避免讨论[CRISPE提示框架]里的内容;
-                不需要重复内容,如果你准备好了,告诉我。
-            """;
+    String IMAGE = """
+        Please generate the corresponding pictures according to the following requirements.
+        : [{{question}}]
+    """;
 }

+ 31 - 0
langchat-common/src/main/java/cn/tycoding/langchat/common/exception/AuthException.java

@@ -0,0 +1,31 @@
+package cn.tycoding.langchat.common.exception;
+
+import lombok.Getter;
+import org.springframework.http.HttpStatus;
+
+/**
+ * @author tycoding
+ * @since 2024/1/2
+ */
+@Getter
+public class AuthException extends RuntimeException {
+
+    private static final long serialVersionUID = -1068765335343416833L;
+
+    private final int code;
+
+    public AuthException() {
+        super(HttpStatus.UNAUTHORIZED.getReasonPhrase());
+        this.code = HttpStatus.UNAUTHORIZED.value();
+    }
+
+    public AuthException(String message) {
+        super(message);
+        this.code = HttpStatus.INTERNAL_SERVER_ERROR.value();
+    }
+
+    public AuthException(int code, String message) {
+        super(message);
+        this.code = code;
+    }
+}

+ 1 - 1
langchat-common/src/main/java/cn/tycoding/langchat/common/utils/PromptUtil.java

@@ -17,7 +17,7 @@ public class PromptUtil {
     }
 
     public static Prompt build(String message, String promptText) {
-        return new PromptTemplate(promptText + PromptConst.EMPTY).apply(Map.of(PromptConst.QUESTION, message));
+        return new PromptTemplate(promptText).apply(Map.of(PromptConst.QUESTION, message));
     }
 
     public static Prompt buildDocs(String message) {

+ 5 - 5
langchat-core/src/main/java/cn/tycoding/langchat/core/properties/chat/OpenaiProps.java

@@ -24,12 +24,12 @@ public class OpenaiProps {
     private String apiKey;
     private String organizationId;
     private String modelName;
-    private Double temperature;
-    private Double topP;
+    private Double temperature = 1.0;
+    private Double topP = 1.0;
     private List<String> stop;
-    private Integer maxTokens;
-    private Double presencePenalty;
-    private Double frequencyPenalty;
+    private Integer maxTokens = 4096;
+    private Double presencePenalty = 0.0;
+    private Double frequencyPenalty = 0.0;
     private Map<String, Integer> logitBias;
     private String responseFormat;
     private Integer seed;

+ 3 - 1
langchat-core/src/main/java/cn/tycoding/langchat/core/provider/ModelProvider.java

@@ -1,5 +1,7 @@
 package cn.tycoding.langchat.core.provider;
 
+import static cn.tycoding.langchat.core.consts.ModelConst.TEXT_SUFFIX;
+
 import dev.langchain4j.model.chat.ChatLanguageModel;
 import dev.langchain4j.model.chat.StreamingChatLanguageModel;
 import dev.langchain4j.model.image.ImageModel;
@@ -30,7 +32,7 @@ public class ModelProvider {
 
     public ChatLanguageModel text(String model) {
         if (context.containsBean(model)) {
-            return (ChatLanguageModel) context.getBean(model);
+            return (ChatLanguageModel) context.getBean(model + TEXT_SUFFIX);
         } else {
             throw new RuntimeException("No matching model information found, please check the model configuration.");
         }

+ 3 - 3
langchat-core/src/main/java/cn/tycoding/langchat/core/service/Assistant.java

@@ -1,6 +1,8 @@
 package cn.tycoding.langchat.core.service;
 
+import dev.langchain4j.service.MemoryId;
 import dev.langchain4j.service.TokenStream;
+import dev.langchain4j.service.UserMessage;
 
 /**
  * @author tycoding
@@ -8,7 +10,5 @@ import dev.langchain4j.service.TokenStream;
  */
 public interface Assistant {
 
-    TokenStream stream(String message);
-
-    String chat(String message);
+    TokenStream stream(@MemoryId String id, @UserMessage String message);
 }

+ 1 - 5
langchat-core/src/main/java/cn/tycoding/langchat/core/service/LangChatService.java

@@ -2,9 +2,7 @@ package cn.tycoding.langchat.core.service;
 
 import cn.tycoding.langchat.common.dto.ChatReq;
 import cn.tycoding.langchat.common.dto.ImageR;
-import cn.tycoding.langchat.common.dto.TextR;
 import dev.langchain4j.data.image.Image;
-import dev.langchain4j.data.message.AiMessage;
 import dev.langchain4j.model.output.Response;
 import dev.langchain4j.service.TokenStream;
 
@@ -16,9 +14,7 @@ public interface LangChatService {
 
     TokenStream chat(ChatReq req);
 
-    Response<AiMessage> text(TextR req);
+    String text(ChatReq req);
 
     Response<Image> image(ImageR req);
-
-    Response<AiMessage> textImage(TextR req);
 }

+ 1 - 2
langchat-core/src/main/java/cn/tycoding/langchat/core/service/LangDocService.java

@@ -22,8 +22,7 @@ public interface LangDocService {
     List<EmbeddingR> embeddingDocs(ChatReq req);
 
     /**
-     * 文本向量搜索
+     * 文档对话
      */
     TokenStream chat(ChatReq req);
-
 }

+ 5 - 18
langchat-core/src/main/java/cn/tycoding/langchat/core/service/impl/LangChatServiceImpl.java

@@ -2,12 +2,10 @@ package cn.tycoding.langchat.core.service.impl;
 
 import cn.tycoding.langchat.common.dto.ChatReq;
 import cn.tycoding.langchat.common.dto.ImageR;
-import cn.tycoding.langchat.common.dto.TextR;
 import cn.tycoding.langchat.core.provider.ModelProvider;
 import cn.tycoding.langchat.core.service.Assistant;
 import cn.tycoding.langchat.core.service.LangChatService;
 import dev.langchain4j.data.image.Image;
-import dev.langchain4j.data.message.AiMessage;
 import dev.langchain4j.memory.chat.MessageWindowChatMemory;
 import dev.langchain4j.model.chat.ChatLanguageModel;
 import dev.langchain4j.model.chat.StreamingChatLanguageModel;
@@ -55,22 +53,22 @@ public class LangChatServiceImpl implements LangChatService {
             assistant = AiServices.builder(Assistant.class)
                     .streamingChatLanguageModel(model)
                     .retrievalAugmentor(retrievalAugmentor)
-                    .chatMemory(MessageWindowChatMemory.withMaxMessages(5))
+                    .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(5))
                     .build();
         } else {
             assistant = AiServices.builder(Assistant.class)
                     .streamingChatLanguageModel(model)
-                    .chatMemory(MessageWindowChatMemory.withMaxMessages(5))
+                    .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(5))
                     .build();
         }
-        return assistant.stream(req.getMessage());
+        return assistant.stream(req.getConversationId(), req.getPrompt().text());
     }
 
     @Override
-    public Response<AiMessage> text(TextR req) {
+    public String text(ChatReq req) {
         try {
             ChatLanguageModel model = provider.text(req.getModel());
-            return model.generate(req.getPrompt().toUserMessage());
+            return model.generate(req.getPrompt().text());
         } catch (Exception e) {
             e.printStackTrace();
             return null;
@@ -87,15 +85,4 @@ public class LangChatServiceImpl implements LangChatService {
             return null;
         }
     }
-
-    @Override
-    public Response<AiMessage> textImage(TextR req) {
-        try {
-            ChatLanguageModel model = provider.text(req.getModel());
-            return model.generate(req.getPrompt().toUserMessage());
-        } catch (Exception e) {
-            e.printStackTrace();
-            return null;
-        }
-    }
 }

+ 11 - 12
langchat-core/src/main/java/cn/tycoding/langchat/core/service/impl/LangDocServiceImpl.java

@@ -1,5 +1,11 @@
 package cn.tycoding.langchat.core.service.impl;
 
+import static cn.tycoding.langchat.core.consts.EmbedConst.FILENAME;
+import static cn.tycoding.langchat.core.consts.EmbedConst.KNOWLEDGE;
+import static dev.langchain4j.data.document.Metadata.metadata;
+import static dev.langchain4j.model.openai.OpenAiModelName.GPT_3_5_TURBO;
+import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;
+
 import cn.hutool.core.util.StrUtil;
 import cn.tycoding.langchat.aigc.service.AigcExcelColService;
 import cn.tycoding.langchat.aigc.service.AigcExcelRowService;
@@ -28,19 +34,12 @@ import dev.langchain4j.service.AiServices;
 import dev.langchain4j.service.TokenStream;
 import dev.langchain4j.store.embedding.filter.Filter;
 import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore;
-import lombok.AllArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-
 import java.util.ArrayList;
 import java.util.List;
 import java.util.function.Function;
-
-import static cn.tycoding.langchat.core.consts.EmbedConst.FILENAME;
-import static cn.tycoding.langchat.core.consts.EmbedConst.KNOWLEDGE;
-import static dev.langchain4j.data.document.Metadata.metadata;
-import static dev.langchain4j.model.openai.OpenAiModelName.GPT_3_5_TURBO;
-import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;
+import lombok.AllArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
 
 /**
  * @author tycoding
@@ -95,7 +94,7 @@ public class LangDocServiceImpl implements LangDocService {
     public TokenStream chat(ChatReq req) {
         StreamingChatLanguageModel chatLanguageModel = modelProvider.stream(req.getModel());
         AiServices<Assistant> aiServices = AiServices.builder(Assistant.class)
-                .chatMemory(MessageWindowChatMemory.withMaxMessages(5))
+                .chatMemoryProvider(memoryId -> MessageWindowChatMemory.withMaxMessages(5))
                 .streamingChatLanguageModel(chatLanguageModel);
 
         if (StrUtil.isNotBlank(req.getDocsId())) {
@@ -113,6 +112,6 @@ public class LangDocServiceImpl implements LangDocService {
         }
 
         Assistant assistant = aiServices.build();
-        return assistant.stream(req.getMessage());
+        return assistant.stream(req.getConversationId(), req.getMessage());
     }
 }

+ 49 - 20
langchat-server/src/main/java/cn/tycoding/langchat/aigc/endpoint/AigcChatEndpoint.java

@@ -1,18 +1,26 @@
 package cn.tycoding.langchat.aigc.endpoint;
 
 import cn.tycoding.langchat.aigc.entity.AigcOss;
+import cn.tycoding.langchat.aigc.service.AigcOssService;
 import cn.tycoding.langchat.aigc.service.ChatService;
+import cn.tycoding.langchat.aigc.service.EmbeddingService;
 import cn.tycoding.langchat.aigc.utils.AigcAuthUtil;
-import cn.tycoding.langchat.common.dto.*;
+import cn.tycoding.langchat.common.dto.ChatReq;
+import cn.tycoding.langchat.common.dto.ChatRes;
+import cn.tycoding.langchat.common.dto.ImageR;
+import cn.tycoding.langchat.common.dto.PromptConst;
 import cn.tycoding.langchat.common.utils.PromptUtil;
 import cn.tycoding.langchat.common.utils.R;
 import cn.tycoding.langchat.common.utils.StreamEmitter;
 import cn.tycoding.langchat.core.consts.ModelConst;
 import lombok.AllArgsConstructor;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
 import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 
 /**
@@ -25,6 +33,8 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
 public class AigcChatEndpoint {
 
     private final ChatService chatService;
+    private final AigcOssService aigcOssService;
+    private final EmbeddingService embeddingDocs;
 
     @PostMapping
     public Object chat(@RequestBody ChatReq req) {
@@ -35,7 +45,8 @@ public class AigcChatEndpoint {
         req.setPrompt(PromptUtil.build(req.getMessage()));
 
         if (req.getModel().endsWith(ModelConst.IMAGE_SUFFIX)) {
-            AigcOss oss = chatService.image(new ImageR().setPrompt(req.getPrompt()).setModel(req.getModel()));
+            AigcOss oss = chatService.image(
+                    new ImageR().setPrompt(req.getPrompt()).setModel(req.getModel()));
             emitter.send("Image:" + oss);
             emitter.complete();
         } else {
@@ -44,8 +55,40 @@ public class AigcChatEndpoint {
         return emitter.get();
     }
 
+    @PostMapping("/docs/{id}")
+    public Object docs(@RequestBody ChatReq req, @PathVariable String id) {
+        StreamEmitter emitter = new StreamEmitter();
+        req.setEmitter(emitter);
+        req.setUserId(AigcAuthUtil.getUserId());
+        req.setUsername(AigcAuthUtil.getUsername());
+        req.setPrompt(PromptUtil.buildDocs(req.getMessage()));
+        req.setKnowledgeId(id);
+
+        chatService.docsChat(req);
+        return emitter.get();
+    }
+
+    @PostMapping("/docs/upload")
+    public R docs(MultipartFile file) {
+        AigcOss oss = aigcOssService.upload(file);
+        embeddingDocs.embedDocs(
+                new ChatReq()
+                        .setDocsName(oss.getTargetName())
+                        .setKnowledgeId(oss.getId())
+                        .setPath(oss.getPath()));
+        return R.ok(oss);
+    }
+
+    @DeleteMapping("/docs/{id}")
+    public R docs(@PathVariable String id) {
+        aigcOssService.removeById(id);
+        // del vector store
+        embeddingDocs.deleteVector(id);
+        return R.ok();
+    }
+
     @PostMapping("/translate")
-    public SseEmitter translate(@RequestBody TextR req) {
+    public SseEmitter translate(@RequestBody ChatReq req) {
         StreamEmitter emitter = new StreamEmitter();
         req.setEmitter(emitter);
         req.setPrompt(PromptUtil.build(req.getMessage(), PromptConst.TRANSLATE));
@@ -54,7 +97,7 @@ public class AigcChatEndpoint {
     }
 
     @PostMapping("/write")
-    public SseEmitter write(@RequestBody TextR req) {
+    public SseEmitter write(@RequestBody ChatReq req) {
         StreamEmitter emitter = new StreamEmitter();
         req.setEmitter(emitter);
         req.setPrompt(PromptUtil.build(req.getMessage(), PromptConst.CHART_LINE));
@@ -63,28 +106,14 @@ public class AigcChatEndpoint {
     }
 
     @PostMapping("/mindmap")
-    public R mindmap(@RequestBody TextR req) {
+    public R mindmap(@RequestBody ChatReq req) {
         req.setPrompt(PromptUtil.build(req.getMessage(), PromptConst.MINDMAP));
         return R.ok(new ChatRes(chatService.text(req)));
     }
 
-    @PostMapping("/mermaid")
-    public SseEmitter mermaid(@RequestBody TextR req) {
-        StreamEmitter emitter = new StreamEmitter();
-        req.setEmitter(emitter);
-        req.setPrompt(PromptUtil.build(req.getMessage(), PromptConst.MERMAID));
-        chatService.singleChat(req);
-        return emitter.get();
-    }
-
-    @PostMapping("/chart")
-    public R chart(@RequestBody TextR req) {
-        req.setPrompt(PromptUtil.build(req.getMessage(), PromptConst.CHART_LINE));
-        return R.ok(new ChatRes(chatService.text(req)));
-    }
-
     @PostMapping("/image")
     public R image(@RequestBody ImageR req) {
+        req.setPrompt(PromptUtil.build(req.getMessage(), PromptConst.IMAGE));
         return R.ok(chatService.image(req));
     }
 

+ 2 - 3
langchat-server/src/main/java/cn/tycoding/langchat/aigc/service/ChatService.java

@@ -3,7 +3,6 @@ package cn.tycoding.langchat.aigc.service;
 import cn.tycoding.langchat.aigc.entity.AigcOss;
 import cn.tycoding.langchat.common.dto.ChatReq;
 import cn.tycoding.langchat.common.dto.ImageR;
-import cn.tycoding.langchat.common.dto.TextR;
 
 /**
  * @author tycoding
@@ -19,12 +18,12 @@ public interface ChatService {
     /**
      * 流式请求
      */
-    void singleChat(TextR req);
+    void singleChat(ChatReq req);
 
     /**
      * 文本请求
      */
-    String text(TextR req);
+    String text(ChatReq req);
 
     /**
      * 文生图

+ 17 - 0
langchat-server/src/main/java/cn/tycoding/langchat/aigc/service/EmbeddingService.java

@@ -2,6 +2,7 @@ package cn.tycoding.langchat.aigc.service;
 
 import cn.tycoding.langchat.aigc.entity.AigcDocs;
 
+import cn.tycoding.langchat.common.dto.ChatReq;
 import java.util.List;
 import java.util.Map;
 
@@ -11,7 +12,23 @@ import java.util.Map;
  */
 public interface EmbeddingService {
 
+    /**
+     * 只向量化文档,不存储切片数据
+     */
+    void embedDocs(ChatReq data);
+
+    /**
+     * 向量化并存储切片数据
+     */
     void embedDocsSlice(AigcDocs data, String path);
 
+    /**
+     * 向量化数据搜索
+     */
     List<Map<String, Object>> search(AigcDocs data);
+
+    /**
+     * 删除vector store
+     */
+    void deleteVector(String knowledgeId);
 }

+ 18 - 20
langchat-server/src/main/java/cn/tycoding/langchat/aigc/service/impl/ChatServiceImpl.java

@@ -8,14 +8,11 @@ import cn.tycoding.langchat.common.constant.RoleEnum;
 import cn.tycoding.langchat.common.dto.ChatReq;
 import cn.tycoding.langchat.common.dto.ChatRes;
 import cn.tycoding.langchat.common.dto.ImageR;
-import cn.tycoding.langchat.common.dto.TextR;
 import cn.tycoding.langchat.common.utils.ServletUtil;
 import cn.tycoding.langchat.common.utils.StreamEmitter;
-import cn.tycoding.langchat.core.consts.ModelConst;
 import cn.tycoding.langchat.core.service.LangChatService;
 import cn.tycoding.langchat.core.service.LangDocService;
 import dev.langchain4j.data.image.Image;
-import dev.langchain4j.data.message.AiMessage;
 import dev.langchain4j.model.output.Response;
 import dev.langchain4j.model.output.TokenUsage;
 import lombok.AllArgsConstructor;
@@ -61,7 +58,8 @@ public class ChatServiceImpl implements ChatService {
                         if (req.getConversationId() != null) {
                             req.setMessage(text.toString());
                             req.setRole(RoleEnum.ASSISTANT.getName());
-                            saveMessage(req, tokenUsage.inputTokenCount(), tokenUsage.outputTokenCount());
+                            saveMessage(req, tokenUsage.inputTokenCount(),
+                                    tokenUsage.outputTokenCount());
                         }
                     })
                     .onError((e) -> {
@@ -101,7 +99,8 @@ public class ChatServiceImpl implements ChatService {
                         if (req.getConversationId() != null) {
                             req.setMessage(text.toString());
                             req.setRole(RoleEnum.ASSISTANT.getName());
-                            saveMessage(req, tokenUsage.inputTokenCount(), tokenUsage.outputTokenCount());
+                            saveMessage(req, tokenUsage.inputTokenCount(),
+                                    tokenUsage.outputTokenCount());
                         }
                     })
                     .onError((e) -> {
@@ -126,13 +125,11 @@ public class ChatServiceImpl implements ChatService {
     }
 
     @Override
-    public void singleChat(TextR req) {
+    public void singleChat(ChatReq req) {
         StreamEmitter emitter = req.getEmitter();
         long startTime = System.currentTimeMillis();
-        ChatReq chat = new ChatReq().setModel(req.getModel()).setPrompt(req.getPrompt());
-
         try {
-            langChatService.chat(chat)
+            langChatService.chat(req)
                     .onNext(e -> {
                         emitter.send(new ChatRes(e));
                     })
@@ -153,23 +150,24 @@ public class ChatServiceImpl implements ChatService {
     }
 
     @Override
-    public String text(TextR req) {
-        return langChatService.text(req).content().text();
+    public String text(ChatReq req) {
+        String text;
+        try {
+            text = langChatService.text(req);
+        } catch (Exception e) {
+            e.printStackTrace();
+            throw new RuntimeException(e.getMessage());
+        }
+        return text;
     }
 
     @Override
     public AigcOss image(ImageR req) {
-        if (req.getModel().equals(ModelConst.GEMINI_IMAGE)) {
-            Response<AiMessage> text = langChatService.textImage(
-                    new TextR().setPrompt(req.getPrompt()).setModel(req.getModel()));
-            log.info("生成图片:{}", text);
-        } else {
-            Response<Image> image = langChatService.image(req);
-            log.info("生成图片:{}", image);
-        }
+        Response<Image> res = langChatService.image(req);
 
+        String path = res.content().url().toString();
         AigcOss oss = new AigcOss();
-//        ossMapper.insert(oss);
+        oss.setUrl(path);
         return oss;
     }
 

+ 18 - 7
langchat-server/src/main/java/cn/tycoding/langchat/aigc/service/impl/EmbeddingServiceImpl.java

@@ -1,5 +1,8 @@
 package cn.tycoding.langchat.aigc.service.impl;
 
+import static cn.tycoding.langchat.core.consts.EmbedConst.KNOWLEDGE;
+import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;
+
 import cn.tycoding.langchat.aigc.entity.AigcDocs;
 import cn.tycoding.langchat.aigc.entity.AigcDocsSlice;
 import cn.tycoding.langchat.aigc.service.AigcKnowledgeService;
@@ -15,18 +18,14 @@ import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
 import dev.langchain4j.store.embedding.EmbeddingSearchResult;
 import dev.langchain4j.store.embedding.filter.Filter;
 import dev.langchain4j.store.embedding.milvus.MilvusEmbeddingStore;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
 import lombok.AllArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.scheduling.annotation.Async;
 import org.springframework.stereotype.Service;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import static cn.tycoding.langchat.core.consts.EmbedConst.KNOWLEDGE;
-import static dev.langchain4j.store.embedding.filter.MetadataFilterBuilder.metadataKey;
-
 /**
  * @author tycoding
  * @since 2024/6/6
@@ -41,6 +40,12 @@ public class EmbeddingServiceImpl implements EmbeddingService {
     private final AigcKnowledgeService aigcKnowledgeService;
     private final MilvusEmbeddingStore embeddingStore;
 
+    @Async
+    @Override
+    public void embedDocs(ChatReq data) {
+        langDocService.embeddingDocs(data);
+    }
+
     @Async
     @Override
     public void embedDocsSlice(AigcDocs data, String path) {
@@ -81,4 +86,10 @@ public class EmbeddingServiceImpl implements EmbeddingService {
         });
         return result;
     }
+
+    @Override
+    public void deleteVector(String knowledgeId) {
+        Filter filter = metadataKey(KNOWLEDGE).isEqualTo(knowledgeId);
+        embeddingStore.removeAll(filter);
+    }
 }

+ 0 - 2
langchat-ui-client/package.json

@@ -25,7 +25,6 @@
     "@codemirror/lang-javascript": "^6.2.1",
     "@codemirror/lang-json": "^6.0.1",
     "@codemirror/theme-one-dark": "^6.1.2",
-    "@flyfish-group/file-viewer3": "^1.0.3",
     "@traptitech/markdown-it-katex": "^3.6.0",
     "@vueuse/core": "^9.13.0",
     "codemirror": "^6.0.1",
@@ -52,7 +51,6 @@
     "vue-demi": "^0.14.6",
     "vue-echarts": "^6.6.4",
     "vue-i18n": "^9.2.2",
-    "vue-mermaid-render": "^0.1.3",
     "vue-router": "^4.1.6"
   },
   "devDependencies": {

Diff do ficheiro suprimidas por serem muito extensas
+ 28 - 554
langchat-ui-client/pnpm-lock.yaml


+ 3 - 37
langchat-ui-client/src/api/chat.ts

@@ -16,23 +16,6 @@ export function chat(
   });
 }
 
-/**
- * @description: 翻译
- */
-export function genTranslate(
-  data: ChatR,
-  onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void
-) {
-  return http.post({
-    url: `/aigc/chat/translate`,
-    data: data,
-    onDownloadProgress: onDownloadProgress,
-  });
-}
-
-/**
- * @description: 生成文章
- */
 export function genWrite(
   data: ChatR,
   onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void
@@ -44,19 +27,6 @@ export function genWrite(
   });
 }
 
-/**
- * @description: 生成思维导图
- */
-export function genMindMap(data: ChatR) {
-  return http.post({
-    url: '/langchat/chat/mindmap',
-    data: data,
-  });
-}
-
-/**
- * @description: 生成思维导图
- */
 export function genChart(data: ChatR) {
   return http.post({
     url: '/aigc/chat/chart',
@@ -75,15 +45,11 @@ export function genImage(data: ImageR): Promise<Oss> {
 }
 
 /**
- * @description: generate Mermaid
+ * @description: 生成思维导图
  */
-export function genMermaid(
-  data: ChatR,
-  onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void
-) {
+export function genMindMap(data: ChatR) {
   return http.post({
-    url: '/aigc/chat/mermaid',
+    url: '/aigc/chat/mindmap',
     data: data,
-    onDownloadProgress: onDownloadProgress,
   });
 }

+ 6 - 8
langchat-ui-client/src/api/docs.ts

@@ -7,7 +7,7 @@ import { AxiosProgressEvent } from 'axios';
  */
 export function upload(data: any, onUploadProgress?: (progressEvent: AxiosProgressEvent) => void) {
   return http.post({
-    url: `/aigc/docs/upload`,
+    url: `/aigc/chat/docs/upload`,
     data,
     headers: {
       'Content-Type': 'multipart/form-data',
@@ -20,15 +20,13 @@ export function upload(data: any, onUploadProgress?: (progressEvent: AxiosProgre
  * @description: Doc聊天
  */
 export function chat(
-  data: {
-    id: string;
-    message: string;
-  },
+  knowledgeId: string,
+  data: any,
   onDownloadProgress?: (progressEvent: AxiosProgressEvent) => void
 ) {
   return http.post({
-    url: `/aigc/docs/chat`,
-    data: data,
+    url: `/aigc/chat/docs/${knowledgeId}`,
+    data,
     onDownloadProgress: onDownloadProgress,
   });
 }
@@ -48,7 +46,7 @@ export function update(data: Oss) {
 
 export function del(id?: number) {
   return http.delete({
-    url: `/aigc/file/${id}`,
+    url: `/aigc/chat/docs/${id}`,
   });
 }
 

+ 2 - 0
langchat-ui-client/src/layout/Sider.vue

@@ -7,6 +7,7 @@
   import { useAppStore, useUserStore } from '@/store';
   import type { Language } from '@/store/modules/app/helper';
   import { t } from '@/locales';
+  import defaultAvatar from '@/assets/avatar.jpg';
 
   const appStore = useAppStore();
   const dialog = useDialog();
@@ -131,6 +132,7 @@
               @click="router.push({ name: 'Profile' })"
               class="cursor-pointer"
               :src="user.avatar ?? '/avatar.jpg'"
+              :fallback-src="defaultAvatar"
               round
             />
           </template>

+ 1 - 1
langchat-ui-client/src/locales/zh-CN.ts

@@ -112,7 +112,7 @@ export default {
     user: '用户中心',
   },
   chat: {
-    placeholder: '请输入您的问题...(Shift + Enter 换行,按下 Enter 发送)',
+    placeholder: '请输入您的问题)',
     placeholderMobile: '来说点什么...',
     copy: '复制',
     copied: '复制成功',

+ 0 - 3
langchat-ui-client/src/main.ts

@@ -5,7 +5,6 @@ import { setupAssets, setupScrollbarStyle } from './plugins';
 import { setupStore } from './store';
 import { setupRouter } from './router';
 import { setupNaive } from '@/plugins/naive';
-import FileViewer from '@flyfish-group/file-viewer3';
 
 async function bootstrap() {
   const app = createApp(App);
@@ -22,8 +21,6 @@ async function bootstrap() {
 
   await setupRouter(app);
 
-  app.use(FileViewer);
-
   app.mount('#app');
 }
 

+ 0 - 9
langchat-ui-client/src/router/index.ts

@@ -75,15 +75,6 @@ const routes: RouteRecordRaw[] = [
         },
         component: () => import('@/views/modules/ppt/index.vue'),
       },
-      {
-        path: '/mermaid',
-        name: 'Mermaid',
-        meta: {
-          label: t('menu.mermaid'),
-          icon: 'flowbite:chart-mixed-outline',
-        },
-        component: () => import('@/views/modules/mermaid/index.vue'),
-      },
       {
         path: '/mindmap',
         name: 'MindMap',

+ 1 - 1
langchat-ui-client/src/styles/lib/github-markdown.less

@@ -196,7 +196,7 @@ html {
   border-style: none;
   max-width: 100%;
   box-sizing: content-box;
-  background-color: var(--color-canvas-default);
+  //background-color: var(--color-canvas-default);
 }
 
 .markdown-body code,

+ 2 - 10
langchat-ui-client/src/utils/request/index.ts

@@ -48,6 +48,7 @@ function axios<T = any>({
 
     if (res.data.code === 401) {
       const message = res.data.message ?? '没有操作权限';
+      $message?.destroyAll();
       $message!.error(message);
       return Promise.reject(res.data);
     }
@@ -64,18 +65,9 @@ function axios<T = any>({
 
   const failHandler = (error: any) => {
     console.error(error);
-    const $dialog = window['$dialog'];
     const { status } = error.response;
     if (status === 401) {
-      $dialog!.warning({
-        title: '提示',
-        content: '您还没有登陆或者登陆已失效',
-        positiveText: '去登录',
-        negativeText: '取消',
-        onPositiveClick: async () => {
-          await router.push({ name: 'Login' });
-        },
-      });
+      router.push({ name: 'Login' });
       return;
     }
 

+ 23 - 7
langchat-ui-client/src/views/modules/chat/Header.vue

@@ -2,11 +2,11 @@
   import { SvgIcon } from '@/components/common';
   import { useBasicLayout } from '@/hooks/useBasicLayout';
   import { useChatStore } from '@/views/modules/chat/store/useChatStore';
-  import { onMounted, ref } from 'vue';
   import { useDialog, useMessage } from 'naive-ui';
   import { t } from '@/locales';
   import { clearMessage } from '@/api/conversation';
   import html2canvas from 'html2canvas';
+  // @ts-ignore
   import { modelList } from '@/api/models/index.d.ts';
 
   const { isMobile } = useBasicLayout();
@@ -67,9 +67,12 @@
 
 <template>
   <header
-    class="sticky px-6 z-30 border-b dark:border-neutral-800 bg-white/80 dark:bg-black/20 backdrop-blur"
+    class="sticky z-30 border-b dark:border-neutral-800 bg-white/80 dark:bg-black/20 backdrop-blur"
+    :class="isMobile ? 'px-1' : 'px-6'"
   >
-    <div class="relative flex items-center justify-between min-w-0 overflow-hidden h-14 ml-2 mr-2">
+    <div
+      class="relative flex items-center justify-between min-w-0 overflow-hidden h-14 ml-2 mr-2 gap-2"
+    >
       <div class="flex items-center gap-2">
         <n-button v-if="isMobile" text @click="chatStore.setSiderCollapsed(false)">
           <SvgIcon class="text-2xl" icon="solar:list-bold-duotone" />
@@ -78,17 +81,30 @@
           size="small"
           v-model:value="chatStore.model"
           :options="modelList"
-          class="!w-[200px] tracking-widest"
+          class="tracking-widest"
+          :class="isMobile ? 'w-auto' : '!w-[200px]'"
         />
+        <n-tag
+          checkable
+          v-model:checked="chatStore.isGoogleSearch"
+          :bordered="false"
+          type="primary"
+          class="border"
+        >
+          <div class="text-sm flex items-center gap-1">
+            <SvgIcon icon="devicon:google" />
+            <div>Google Search</div>
+          </div>
+        </n-tag>
       </div>
 
-      <div class="flex items-center space-x-2">
-        <n-button type="warning" tertiary round @click="onClear">
+      <div class="flex items-center space-x-2 overflow-x-auto">
+        <n-button type="warning" tertiary size="small" @click="onClear">
           <SvgIcon class="text-lg" icon="fluent:delete-28-regular" />
           <span>{{ t('chat.clearChat') }}</span>
         </n-button>
 
-        <n-button type="info" tertiary round @click="handleExport">
+        <n-button type="info" tertiary size="small" @click="handleExport">
           <SvgIcon class="text-xl" icon="material-symbols:download" />
           <span>{{ t('chat.exportImage') }}</span>
         </n-button>

+ 73 - 84
langchat-ui-client/src/views/modules/chat/index.vue

@@ -26,14 +26,30 @@
   const aiChatId = ref<string>('');
   const inputRef = ref();
 
+  onMounted(async () => {
+    if (inputRef.value && !isMobile.value) {
+      inputRef.value?.focus();
+    }
+    await chatStore.loadData();
+    if (chatStore.conversations.length == 0) {
+      await chatStore.addConversation({ title: 'New Chat' });
+      await chatStore.loadData();
+    }
+  });
+  onUnmounted(() => {
+    if (loading.value) {
+      controller.abort();
+    }
+  });
+  onUpdated(() => {
+    scrollToBottomIfAtBottom();
+  });
+
   const dataSources = computed(() => {
     // 获取当前聊天窗口的数据
     scrollToBottom();
     return chatStore.messages;
   });
-  onUpdated(() => {
-    chatStore.replaceUrl();
-  });
 
   async function handleSubmit() {
     let message = prompt.value;
@@ -52,13 +68,6 @@
     loading.value = true;
     prompt.value = '';
 
-    if (chatStore.conversations.length == 0) {
-      await chatStore.loadData();
-    }
-
-    // add conversation
-    await chatStore.addConversation({});
-
     // ai
     await scrollToBottom();
     const { conversationId } = await addMessage(data);
@@ -71,68 +80,62 @@
 
   async function onChat(message: string, conversationId?: string) {
     try {
-      // 定义接口
-      const fetchChatAPIOnce = async () => {
-        await chat(
-          {
-            chatId: chatId.value,
-            message,
-            role: 'user',
-            model: chatStore.model,
-            conversationId: conversationId,
-          },
-          async ({ event }) => {
-            const list = event.target.responseText.split('\n\n');
-
-            let text = '';
-            let isRun = true;
-            list.forEach((i: any) => {
-              if (i.startsWith('data:Error')) {
-                isRun = false;
-                text += i.substring(5, i.length);
-                chatStore.updateMessage(aiChatId.value, text, true);
-                return;
-              }
-              if (!i.startsWith('data:{')) {
-                return;
-              }
+      await chat(
+        {
+          chatId: chatId.value,
+          message,
+          role: 'user',
+          model: chatStore.model,
+          conversationId: conversationId,
+        },
+        async ({ event }) => {
+          const list = event.target.responseText.split('\n\n');
 
-              const { done, message } = JSON.parse(i.substring(5, i.length));
-              if (done) {
-                if (chatStore.curConversation?.id == undefined) {
-                  chatStore.curConversation = { id: String(conversationId) };
-                  chatStore.selectConversation({ id: conversationId });
-                }
-                return;
-              }
-              text += message;
-            });
-            if (!isRun) {
-              await scrollToBottomIfAtBottom();
+          let text = '';
+          let isRun = true;
+          list.forEach((i: any) => {
+            if (i.startsWith('data:Error')) {
+              isRun = false;
+              text += i.substring(5, i.length);
+              chatStore.updateMessage(aiChatId.value, text, true);
               return;
             }
-            await chatStore.updateMessage(aiChatId.value, text, false);
-            await scrollToBottomIfAtBottom();
-          }
-        )
-          .catch((e: any) => {
-            loading.value = false;
-            if (e.message !== undefined) {
-              chatStore.updateMessage(aiChatId.value, e.message, true);
+            if (!i.startsWith('data:{')) {
               return;
             }
-            if (e.startsWith('data:Error')) {
-              chatStore.updateMessage(aiChatId.value, e.substring(5, e.length), true);
+
+            const { done, message } = JSON.parse(i.substring(5, i.length));
+            if (done) {
+              if (chatStore.curConversation?.id == undefined) {
+                chatStore.curConversation = { id: String(conversationId) };
+                chatStore.selectConversation({ id: conversationId });
+              }
               return;
             }
-          })
-          .finally(() => {
-            scrollToBottomIfAtBottom();
+            text += message;
           });
-      };
-
-      // 调用接口
-      await fetchChatAPIOnce();
+          if (!isRun) {
+            await scrollToBottomIfAtBottom();
+            return;
+          }
+          await chatStore.updateMessage(aiChatId.value, text, false);
+          await scrollToBottomIfAtBottom();
+        }
+      )
+        .catch((e: any) => {
+          loading.value = false;
+          if (e.message !== undefined) {
+            chatStore.updateMessage(aiChatId.value, e.message, true);
+            return;
+          }
+          if (e.startsWith('data:Error')) {
+            chatStore.updateMessage(aiChatId.value, e.substring(5, e.length), true);
+            return;
+          }
+        })
+        .finally(() => {
+          scrollToBottomIfAtBottom();
+        });
     } finally {
       loading.value = false;
     }
@@ -201,23 +204,6 @@
   const getContainerClass = computed(() => {
     return ['h-full', { 'pl-[260px]': !isMobile.value && !collapsed.value }];
   });
-
-  onMounted(() => {
-    chatStore.loadData();
-    if (inputRef.value && !isMobile.value) {
-      inputRef.value?.focus();
-    }
-  });
-
-  onUnmounted(() => {
-    if (loading.value) {
-      controller.abort();
-    }
-  });
-
-  onUpdated(() => {
-    scrollToBottomIfAtBottom();
-  });
 </script>
 
 <template>
@@ -240,8 +226,8 @@
               <div
                 v-else
                 ref="scrollRef"
-                class="max-w-screen-2xl m-auto px-10"
-                :class="[isMobile ? 'p-2' : 'p-5']"
+                class="max-w-screen-2xl m-auto"
+                :class="[isMobile ? 'p-2' : 'p-5 !px-12']"
               >
                 <Message
                   v-for="(item, index) of dataSources"
@@ -266,7 +252,10 @@
           </main>
 
           <footer :class="footerClass">
-            <div class="w-full max-w-screen-2xl m-auto px-20 pb-10 relative">
+            <div
+              class="w-full max-w-screen-2xl m-auto relative"
+              :class="isMobile ? 'pb-2' : ' px-20 pb-10 '"
+            >
               <div class="flex items-center justify-between">
                 <n-input
                   ref="inputRef"

+ 3 - 13
langchat-ui-client/src/views/modules/chat/message/Avatar.vue

@@ -4,6 +4,7 @@
   import { isString } from '@/utils/is';
   import { useUserStore } from '@/store/modules/user';
   import defaultAvatar from '@/assets/avatar.jpg';
+  import logoAvatar from '@/assets/login/logo.png';
 
   interface Props {
     image?: boolean;
@@ -29,19 +30,8 @@
     />
     <NAvatar v-else round :src="defaultAvatar" />
   </template>
-  <span v-else class="text-[28px]">
-    <svg
-      xmlns="http://www.w3.org/2000/svg"
-      viewBox="0 0 32 32"
-      aria-hidden="true"
-      width="1em"
-      height="1em"
-    >
-      <path
-        d="M29.71,13.09A8.09,8.09,0,0,0,20.34,2.68a8.08,8.08,0,0,0-13.7,2.9A8.08,8.08,0,0,0,2.3,18.9,8,8,0,0,0,3,25.45a8.08,8.08,0,0,0,8.69,3.87,8,8,0,0,0,6,2.68,8.09,8.09,0,0,0,7.7-5.61,8,8,0,0,0,5.33-3.86A8.09,8.09,0,0,0,29.71,13.09Zm-12,16.82a6,6,0,0,1-3.84-1.39l.19-.11,6.37-3.68a1,1,0,0,0,.53-.91v-9l2.69,1.56a.08.08,0,0,1,.05.07v7.44A6,6,0,0,1,17.68,29.91ZM4.8,24.41a6,6,0,0,1-.71-4l.19.11,6.37,3.68a1,1,0,0,0,1,0l7.79-4.49V22.8a.09.09,0,0,1,0,.08L13,26.6A6,6,0,0,1,4.8,24.41ZM3.12,10.53A6,6,0,0,1,6.28,7.9v7.57a1,1,0,0,0,.51.9l7.75,4.47L11.85,22.4a.14.14,0,0,1-.09,0L5.32,18.68a6,6,0,0,1-2.2-8.18Zm22.13,5.14-7.78-4.52L20.16,9.6a.08.08,0,0,1,.09,0l6.44,3.72a6,6,0,0,1-.9,10.81V16.56A1.06,1.06,0,0,0,25.25,15.67Zm2.68-4-.19-.12-6.36-3.7a1,1,0,0,0-1.05,0l-7.78,4.49V9.2a.09.09,0,0,1,0-.09L19,5.4a6,6,0,0,1,8.91,6.21ZM11.08,17.15,8.38,15.6a.14.14,0,0,1-.05-.08V8.1a6,6,0,0,1,9.84-4.61L18,3.6,11.61,7.28a1,1,0,0,0-.53.91ZM12.54,14,16,12l3.47,2v4L16,20l-3.47-2Z"
-        fill="currentColor"
-      />
-    </svg>
+  <span v-else class="">
+    <n-avatar :src="logoAvatar" object-fit="contain" />
   </span>
 </template>
 

+ 1 - 1
langchat-ui-client/src/views/modules/chat/message/Message.vue

@@ -108,7 +108,7 @@
         <div class="flex flex-row justify-start items-start gap-1">
           <n-popover
             v-for="item in options"
-            :key="item"
+            :key="item.key"
             :trigger="isMobile ? 'click' : 'hover'"
             class="custom-popover"
             placement="bottom"

+ 4 - 4
langchat-ui-client/src/views/modules/chat/sider/List.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-  import { computed, nextTick } from 'vue';
+  import { computed } from 'vue';
   import { NInput, NPopconfirm, NScrollbar } from 'naive-ui';
   import { SvgIcon } from '@/components/common';
   import { useChatStore } from '../store/useChatStore';
@@ -13,17 +13,17 @@
   });
 
   async function handleSelect(item: Conversation) {
-    if (isActive(item.id)) return;
+    if (isActive(item.id!)) return;
     await chatStore.selectConversation(item);
   }
 
   function handleEdit({ id }: Conversation, isEdit: boolean, event?: MouseEvent) {
-    chatStore.setEdit(id);
+    chatStore.setEdit(id!);
     event?.stopPropagation();
   }
 
   async function handleDelete(item: Conversation, event?: MouseEvent | TouchEvent) {
-    await chatStore.delConversation(item.id);
+    await chatStore.delConversation(item.id!);
     event?.stopPropagation();
   }
 

+ 1 - 2
langchat-ui-client/src/views/modules/chat/sider/index.vue

@@ -53,7 +53,6 @@
 
   async function onAddConversation() {
     chatStore.sideIsLoading = true;
-    console.log('xxx');
     await addConversation({
       title: 'New Chat' + Number(chatStore.conversations.length + 1),
     });
@@ -79,7 +78,7 @@
       </div>
       <main v-else class="flex flex-col flex-1 min-h-0">
         <div class="p-4 pt-3 flex justify-between items-center gap-2">
-          <n-button @click="onAddConversation" block size="" type="success" secondary>
+          <n-button @click="onAddConversation" block type="success" secondary>
             <SvgIcon icon="ic:round-plus" />
             <span>{{ t('chat.newChatButton') }}</span>
           </n-button>

+ 4 - 5
langchat-ui-client/src/views/modules/chat/store/chat.d.ts

@@ -1,13 +1,12 @@
-import { Conversation, Message } from '@/typings/chat';
-
 export interface ChatState {
   model: string;
+  isGoogleSearch: boolean;
   isEdit: string; //当前编辑的id
   active: string; //当前激活的id
   siderCollapsed: boolean; //侧边栏展开状态
   sideIsLoading: boolean; //侧边栏加载状态
   chatIsLoading: boolean; //会话窗口加载状态
-  conversations: Conversation[]; //左侧会话窗口列表
-  curConversation: Conversation | undefined; //当前选中的会话窗口
-  messages: Message[]; //当前选中的消息内容
+  conversations: any[]; //左侧会话窗口列表
+  curConversation: any; //当前选中的会话窗口
+  messages: any[]; //当前选中的消息内容
 }

+ 7 - 38
langchat-ui-client/src/views/modules/chat/store/useChatStore.ts

@@ -8,8 +8,6 @@ import {
 } from '@/api/conversation';
 import { ChatState } from './chat';
 import { formatToDateTime } from '@/utils/dateUtil';
-import { Conversation, Message } from '@/typings/chat';
-import { router } from '@/router';
 import { toRaw } from 'vue';
 
 export const useChatStore = defineStore('chat-store', {
@@ -18,6 +16,7 @@ export const useChatStore = defineStore('chat-store', {
       model: 'gpt-4o',
       active: '',
       isEdit: '',
+      isGoogleSearch: false,
       siderCollapsed: true,
       sideIsLoading: true,
       chatIsLoading: true,
@@ -45,18 +44,10 @@ export const useChatStore = defineStore('chat-store', {
     async loadData() {
       try {
         const data = await getConversations({});
-        const conversationId = router.currentRoute.value.query.conversationId as string;
-        if (conversationId !== undefined && conversationId !== null) {
-          this.active = conversationId;
-          await this.selectConversation({ id: conversationId });
-        }
         if (data && data.length > 0) {
           this.conversations = data;
           this.curConversation = data[0];
-        } else {
-          this.active = '';
-          this.conversations = [];
-          await router.replace({ path: '/chat', query: {} });
+          await this.selectConversation({ id: data[0].id });
         }
       } finally {
         this.sideIsLoading = false;
@@ -64,18 +55,11 @@ export const useChatStore = defineStore('chat-store', {
       }
     },
 
-    /**
-     * 设置当前会话窗口数据
-     */
-    setConversation(params: Conversation) {
-      this.curConversation = params;
-    },
-
     /**
      * 选择会话窗口
      */
-    async selectConversation(params: Conversation) {
-      console.log('点击开始');
+    async selectConversation(params: any) {
+      console.log('选择窗口');
       this.chatIsLoading = true;
       this.messages = [];
       if (params.id == undefined) {
@@ -84,7 +68,6 @@ export const useChatStore = defineStore('chat-store', {
       if (this.active !== '') {
         getMessages(params.id)
           .then((res: any) => {
-            console.log('ge', res);
             this.messages = res.reverse();
           })
           .finally(() => {
@@ -95,26 +78,12 @@ export const useChatStore = defineStore('chat-store', {
       await this.setEdit('');
       this.curConversation = params;
       this.chatIsLoading = false;
-      await this.replaceUrl();
-    },
-
-    async replaceUrl() {
-      if (this.curConversation == undefined) {
-        return;
-      }
-      const { id, promptId } = this.curConversation;
-      // replace url path
-      const query: any = {};
-      if (promptId) {
-        query.promptId = promptId;
-      }
-      await router.replace({ path: `/chat/${id}`, query });
     },
 
     /**
      * 添加会话窗口
      */
-    async addConversation(params: Partial<Conversation>) {
+    async addConversation(params: any) {
       await addConversations(params);
       await this.loadData();
     },
@@ -122,7 +91,7 @@ export const useChatStore = defineStore('chat-store', {
     /**
      * 更新会话信息
      */
-    async updateConversation(params: Partial<Conversation>) {
+    async updateConversation(params: any) {
       await updateConversations(toRaw(params));
       await this.setEdit('');
       await this.loadData();
@@ -167,7 +136,7 @@ export const useChatStore = defineStore('chat-store', {
     /**
      * 删除消息
      */
-    async delMessage(item: Message) {
+    async delMessage(item: any) {
       this.messages = this.messages.filter((i) => i.chatId !== item.chatId);
     },
   },

+ 43 - 13
langchat-ui-client/src/views/modules/doc/components/Chat.vue

@@ -1,8 +1,7 @@
 <script lang="ts" setup>
-  import { onMounted, ref, watch } from 'vue';
+  import { ref } from 'vue';
   import { chat } from '@/api/docs';
   import { v4 as uuid } from 'uuid';
-  import { useRouter } from 'vue-router';
   import MarkdownIt from 'markdown-it';
   import hljs from 'highlight.js';
   import mila from 'markdown-it-link-attributes';
@@ -10,11 +9,18 @@
   import Message from './Message.vue';
   import { SvgIcon } from '@/components/common';
   import { useDocStore } from '@/views/modules/doc/store';
+  import { t } from '@/locales';
+  import Header from '@/views/modules/chat/Header.vue';
+  import { useBasicLayout } from '@/hooks/useBasicLayout';
+  // @ts-ignore
+  import { modelList } from '@/api/models/index.d.ts';
 
+  const { isMobile } = useBasicLayout();
   const emits = defineEmits(['focus-active']);
   const messageRef = ref();
-  const router = useRouter();
+  const model = ref('gpt-4o');
   const message = ref('');
+  const isGoogleSearch = ref(false);
   const loading = ref(false);
   const docStore = useDocStore();
 
@@ -82,9 +88,12 @@
       });
       const items = messages.value.filter((i) => i.id == id);
       await chat(
+        docStore.file?.id,
         {
-          id: docStore.file?.id,
+          conversationId: docStore.file?.id,
           message: message.value,
+          model: model.value,
+          isGoogleSearch: isGoogleSearch.value,
         },
         ({ event }) => {
           const list = event.target.responseText.split('\n\n');
@@ -135,26 +144,47 @@
 </script>
 
 <template>
-  <div class="container relative h-full card-shadow rounded-xl mb-2">
+  <div class="container relative h-full card-shadow rounded-xl mb-2 flex flex-col">
+    <header
+      class="sticky z-30 border-b dark:border-neutral-800 border-l-0 bg-white/80 dark:bg-black/20 backdrop-blur"
+      :class="isMobile ? 'px-1' : 'px-2'"
+    >
+      <div
+        class="relative flex items-center justify-between min-w-0 overflow-hidden h-12 ml-2 mr-2 gap-2"
+      >
+        <n-select size="small" v-model:value="model" :options="modelList" class="!w-[160px]" />
+        <n-tag
+          checkable
+          v-model:checked="isGoogleSearch"
+          :bordered="false"
+          type="primary"
+          class="border"
+        >
+          <div class="text-sm flex items-center gap-1">
+            <SvgIcon icon="devicon:google" />
+            <div>Google Search</div>
+          </div>
+        </n-tag>
+      </div>
+    </header>
     <Message ref="messageRef" :messages="messages" />
 
-    <div class="bottom absolute bottom-2 pt-5 left-0 w-full h-[60px] z-10">
+    <div v-if="docStore.file.id" class="pt-2 left-0 w-full z-10">
       <div class="px-8 flex justify-center items-center space-x-2 w-full">
         <n-input
-          v-model:value="message"
-          :autosize="{ minRows: 1, maxRows: 5 }"
           :disabled="loading"
-          class="w-full ]text-xs rounded-md"
+          v-model:value="message"
           type="textarea"
           @focus="handleFocus"
           @keypress="handleEnter"
+          :autosize="{ minRows: 1, maxRows: 3 }"
+          class="!rounded-full px-2 py-1 mb-2"
+          :placeholder="t('chat.placeholder')"
         >
           <template #suffix>
-            <n-button :loading="loading" size="small" text @click="handleSubmit">
+            <n-button text :loading="loading" @click="handleSubmit">
               <template #icon>
-                <n-icon color="#18a058">
-                  <SvgIcon icon="mingcute:send-line" />
-                </n-icon>
+                <SvgIcon icon="mdi:sparkles-outline" />
               </template>
             </n-button>
           </template>

+ 3 - 3
langchat-ui-client/src/views/modules/doc/components/FileList.vue

@@ -1,6 +1,6 @@
 <script setup lang="ts">
   import { SvgIcon } from '@/components/common';
-  import { UploadCustomRequestOptions, useMessage, type UploadFileInfo } from 'naive-ui';
+  import { UploadCustomRequestOptions, useMessage } from 'naive-ui';
   import { list, upload, del, update, task as getTask } from '@/api/docs';
   import { onMounted, ref } from 'vue';
   import { Oss } from '@/api/models';
@@ -45,7 +45,7 @@
         ms.success(t('common.importSuccess'));
         onFinish();
         fetchData();
-        startTask();
+        // startTask();
       })
       .catch(() => {
         ms.error(t('common.wrong'));
@@ -98,7 +98,7 @@
       <n-upload
         :show-file-list="false"
         :custom-request="onUpload"
-        :accept="'application/pdf,.doc,.docx,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,.xls,.xlsx,.csv,application/excel,application/vnd.ms-excel,application/vnd.msexcel,text/plain,.md'"
+        :accept="'application/pdf,.doc,.docx,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document'"
         :trigger-style="{ width: '100%' }"
       >
         <n-button dashed type="success" block>{{ t('doc.upload') }}</n-button>

+ 15 - 41
langchat-ui-client/src/views/modules/doc/components/FileView.vue

@@ -1,51 +1,25 @@
 <script setup lang="ts">
-  import '@flyfish-group/file-viewer3/dist/style.css';
-  defineProps({
+  const props = defineProps({
     url: {
       type: String,
     },
   });
+
+  // 在生产环境下使用微软的office在线预览方式,导入支持office和word导入,在线预览不支持开发环境
+  const path =
+    process.env.NODE_ENV === 'development'
+      ? props.url
+      : `http://view.officeapps.live.com/op/view.aspx?src=${props.url}`;
 </script>
 
 <template>
-  <file-viewer :url="url" />
+  <iframe
+    :src="path"
+    width="100%"
+    height="100%"
+    frameborder="0"
+    class="!z-0 !pointer-events-none"
+  ></iframe>
 </template>
 
-<style scoped lang="less">
-  ::v-deep(.name) {
-    display: none !important;
-  }
-  ::v-deep(.pdf_down) {
-    display: none !important;
-  }
-  ::v-deep(.docx) {
-    color: #343639;
-    //background: #f2f2f2 !important;
-  }
-  ::v-deep(.docx span) {
-    font-size: medium !important;
-  }
-  ::v-deep(.docx-wrapper) {
-    padding: 20px !important;
-    background: transparent !important;
-    .docx {
-      box-shadow: none !important;
-      width: auto !important;
-      padding: 10pt 15pt !important;
-      //background: #f2f2f2 !important;
-    }
-  }
-  ::v-deep(.code-area) {
-    width: auto !important;
-    background: none !important;
-    color: #101014 !important;
-    padding: 10px 17px;
-    font-size: medium;
-    font-family: normal !important;
-    padding-bottom: 30px;
-  }
-  ::v-deep(.code-area:is(.dark *)) {
-    background: #101014 !important;
-    color: white !important;
-  }
-</style>
+<style scoped lang="less"></style>

+ 2 - 2
langchat-ui-client/src/views/modules/doc/components/Message.vue

@@ -76,7 +76,7 @@
 </script>
 
 <template>
-  <div ref="textRef" class="middle absolute top-6 left-0 w-full bottom-[65px] z-0 overflow-y-auto">
+  <div ref="textRef" class="middle w-full flex-1 py-3 z-0 overflow-y-auto">
     <div v-if="messages.length == 0" class="flex-1 flex h-full justify-center">
       <div class="w-1/2 flex flex-col justify-center text-xs items-center gap-2">
         <n-icon color="#e4e4e7" size="70">
@@ -112,7 +112,7 @@
                   <n-button text type="success">
                     <SvgIcon icon="mdi:success" />
                   </n-button>
-                  <span>{{ (item.time / 1000).toFixed(1) }}s</span>
+                  <span>{{ Number(Number(item.time) / 1000).toFixed(1) }}s</span>
                   <n-divider class="ml-1 mr-1" vertical />
                   <span>{{ item.usedToken }} Token</span>
                 </div>

+ 1 - 2
langchat-ui-client/src/views/modules/doc/index.vue

@@ -33,10 +33,9 @@
           <div class="w-full h-full">
             <div
               v-if="docStore.file.fileName"
-              class="text-gray-700 text-[17px] border-b px-4 font-bold h-12 flex justify-between items-center dark:text-white"
+              class="text-gray-700 text-[17px] border-b px-2 font-bold h-12 flex justify-between items-center dark:text-white"
             >
               <div>{{ docStore.file.fileName }}.{{ docStore.file.type }}</div>
-              <div>OpenAI</div>
             </div>
             <n-empty
               v-if="docStore.file.url === undefined"

+ 4 - 4
langchat-ui-client/src/views/modules/image/component/DALL.vue

@@ -1,7 +1,7 @@
 <script lang="ts" setup>
   import { SvgIcon } from '@/components/common';
   import { genImage } from '@/api/chat';
-  import { ref } from 'vue';
+  import { ref, toRaw } from 'vue';
   import { isBlank } from '@/utils/is';
   import { useMessage } from 'naive-ui';
   import { ImageR } from '@/api/models';
@@ -11,8 +11,7 @@
   const ms = useMessage();
   const message = ref('');
   const form = ref<ImageR>({
-    message: '',
-    model: 'dall-e-2',
+    model: 'dall-e-3',
     quality: 'standard',
     size: '1024x1024',
     style: 'vivid',
@@ -27,6 +26,7 @@
     ms.success('图片生成中,请稍后...');
     const data = await genImage({
       message: message.value,
+      ...toRaw(form.value),
     })
       .catch(() => {
         ms.error('图片生成失败');
@@ -85,7 +85,7 @@
       <div class="flex justify-start gap-2">
         <n-button
           v-for="item in modelList"
-          :key="item"
+          :key="item.label"
           :type="form.model == item.value ? 'success' : 'default'"
           secondary
           size="small"

+ 11 - 9
langchat-ui-client/src/views/modules/image/index.vue

@@ -2,11 +2,11 @@
   import { SvgIcon } from '@/components/common';
   import { ref } from 'vue';
   import DALL from './component/DALL.vue';
-  import { Oss } from '@/api/models';
   import { downloadByUrl } from '@/utils/downloadFile';
+  import { t } from '@/locales';
 
   const image = ref<string>('');
-  function onOk(data: Oss) {
+  function onOk(data: any) {
     if (data.url !== undefined) {
       image.value = data.url;
     }
@@ -31,12 +31,14 @@
           <n-tab-pane name="chap1" tab="OpenAI DALL·E" display-directive="show">
             <DALL @ok="onOk" />
           </n-tab-pane>
-          <n-tab-pane name="chap2" tab="More" display-directive="show" />
+          <n-tab-pane name="chap2" tab="Mj & More..." display-directive="show">
+            <n-empty description="更多文生图模型后续即将支持..." />
+          </n-tab-pane>
         </n-tabs>
       </div>
     </n-layout-sider>
 
-    <div class="flex justify-center items-center w-full mt-4">
+    <div class="flex justify-center items-center w-full mt-4 dot-bg">
       <div class="p-8 w-full h-full mb-14">
         <div class="mb-2 flex flex-wrap justify-between items-center">
           <div class="font-bold flex justify-center items-center flex-wrap gap-2">
@@ -60,11 +62,11 @@
           >
             <img :src="image" class="max-w-full max-h-full" />
           </div>
-          <n-empty v-else description="在左侧输入图片描述,开始生成图片吧!">
-            <template #extra>
-              <n-button size="small" type="success"> 立即开始 </n-button>
-            </template>
-          </n-empty>
+          <div class="h-full w-full flex flex-col justify-center items-center gap-3" v-else>
+            <SvgIcon class="text-6xl" icon="ri:mind-map" />
+            <div class="text-2xl font-bold">{{ t('mindmap.title') }}</div>
+            <div class="text-gray-400">{{ t('mindmap.titleDes') }}</div>
+          </div>
         </div>
       </div>
     </div>

+ 6 - 6
langchat-ui-client/src/views/modules/mermaid/components/Mermaid.vue

@@ -1,17 +1,17 @@
 <script setup lang="ts">
   import { SvgIcon } from '@/components/common';
-  import { onMounted, ref, watch } from 'vue';
+  import { ref, watch } from 'vue';
   import { t } from '@/locales';
   import { VueMermaidRender } from 'vue-mermaid-render';
   import { downloadPdf, downloadPng, downloadSvg } from '@/utils/downloadFile';
 
   const props = defineProps<{
-    genText: string;
+    text: string;
   }>();
   const width = ref(80);
 
   watch(
-    () => props.genText,
+    () => props.text,
     (val) => {}
   );
 
@@ -38,7 +38,7 @@
 </script>
 
 <template>
-  <div class="dot-bg w-full h-full" :class="genText == '' ? 'overflow-hidden' : ''">
+  <div class="dot-bg w-full h-full" :class="text == '' ? 'overflow-hidden' : ''">
     <div class="absolute top-0 z-10 p-2 flex flex-wrap justify-center gap-2">
       <n-button @click="onZoomIn" text>
         <SvgIcon class="text-2xl" icon="basil:zoom-in-outline" />
@@ -69,7 +69,7 @@
       </n-button>
     </div>
 
-    <div class="h-full w-full flex flex-col justify-center items-center gap-3" v-if="genText == ''">
+    <div class="h-full w-full flex flex-col justify-center items-center gap-3" v-if="text == ''">
       <SvgIcon class="text-6xl" icon="flowbite:chart-mixed-outline" />
       <div class="text-2xl font-bold">{{ t('mermaid.title') }}</div>
       <div class="text-gray-400">{{ t('mermaid.titleDes') }}</div>
@@ -78,7 +78,7 @@
 
     <div class="h-full w-full flex justify-center items-center">
       <div :style="'width: ' + width + 'vh'" id="mermaid-view">
-        <VueMermaidRender :content="props.genText" :config="{}" />
+        <VueMermaidRender :content="props.text" :config="{}" />
       </div>
     </div>
   </div>

+ 19 - 14
langchat-ui-client/src/views/modules/mermaid/components/Sider.vue

@@ -5,7 +5,7 @@
 
   const props = defineProps<{
     loading: boolean;
-    genText: string;
+    mermaidText: string;
   }>();
   const loading = computed(() => {
     return props.loading;
@@ -15,7 +15,7 @@
   const gen = ref('');
 
   watch(
-    () => props.genText,
+    () => props.mermaidText,
     (val) => {
       gen.value = val;
     }
@@ -23,17 +23,23 @@
 
   function onCase() {
     text.value = `Sequence diagrams`;
-    gen.value = `sequenceDiagram
-    participant Alice
-    participant Bob
-    Alice->>John: Hello John, how are you?
-    loop HealthCheck
-        John->>John: Fight against hypochondria
-    end
-    Note right of John: Rational thoughts <br/>prevail!
-    John-->>Alice: Great!
-    John->>Bob: How about you?
-    Bob-->>John: Jolly good!
+    gen.value = `
+stateDiagram-v2
+[*] --> LoginScreen
+LoginScreen --> EnterUsername: Username Entered
+LoginScreen --> EnterPassword: Password Entered
+EnterUsername --> ValidateUsername: Validating
+EnterPassword --> ValidatePassword: Validating
+ValidateUsername --> InvalidUsername: Invalid
+ValidatePassword --> InvalidPassword: Invalid
+ValidateUsername --> ValidatedUsername: Valid
+ValidatePassword --> ValidatedPassword: Valid
+ValidatedUsername --> SubmitForm: Ready to Submit
+ValidatedPassword --> SubmitForm
+SubmitForm --> LoginSuccess: Success
+SubmitForm --> LoginFailure: Failure
+InvalidUsername --> LoginScreen: Retry
+InvalidPassword --> LoginScreen: Retry
 		`;
     emit('case', gen.value);
   }
@@ -45,7 +51,6 @@
 
 <template>
   <div class="p-4">
-    <div class="pb-2">{{ t('mermaid.des') }}</div>
     <n-input
       :disabled="loading"
       v-model:value="text"

+ 40 - 27
langchat-ui-client/src/views/modules/mermaid/index.vue

@@ -6,42 +6,51 @@
   import { genMermaid } from '@/api/chat';
   import { isBlank } from '@/utils/is';
   import { t } from '@/locales';
+  // @ts-ignore
+  import { modelList } from '@/api/models/index.d.ts';
 
   const ms = useMessage();
+  const model = ref('gpt-4o');
   const loading = ref(false);
-  const genText = ref('');
-  async function onGenerate(text: string) {
-    if (isBlank(text)) {
+  const mermaidText = ref('');
+  async function onGenerate(val: string) {
+    if (isBlank(val)) {
       ms.warning(t('common.emptyTips'));
       return;
     }
-    genText.value = '';
+    let text = '';
     loading.value = true;
-    await genMermaid(
-      {
-        message: text,
-      },
-      ({ event }) => {
-        const list = event.target.responseText.split('\n\n');
-        list.forEach((i: any) => {
-          if (!i.startsWith('data:{')) {
-            return;
-          }
-          const { usedToken, done, message, time } = JSON.parse(i.substring(5, i.length));
-          if (done || message == null) {
-            loading.value = false;
-          } else {
-            genText.value += message;
-          }
-        });
-      }
-    ).finally(() => {
+    try {
+      await genMermaid(
+        {
+          message: val,
+          model: model.value,
+        },
+        ({ event }) => {
+          const list = event.target.responseText.split('\n\n');
+          list.forEach((i: any) => {
+            if (!i.startsWith('data:{')) {
+              return;
+            }
+
+            const { done, message } = JSON.parse(i.substring(5, i.length));
+            if (done || message == null) {
+              loading.value = false;
+              mermaidText.value = text;
+              return;
+            }
+            text += message;
+            console.log('现在的消息', text);
+          });
+        }
+      );
+    } finally {
       loading.value = false;
-    });
+    }
   }
 
   function onCase(text: string) {
-    genText.value = text;
+    mermaidText.value = text;
   }
 </script>
 
@@ -55,10 +64,14 @@
       collapse-mode="width"
       show-trigger="arrow-circle"
     >
-      <Sider :genText="genText" :loading="loading" @case="onCase" @generate="onGenerate" />
+      <div class="px-4 pt-2 flex items-center justify-between">
+        <div>{{ t('mermaid.des') }}</div>
+        <n-select size="small" v-model:value="model" :options="modelList" class="!w-[140px]" />
+      </div>
+      <Sider :mermaidText="mermaidText" :loading="loading" @case="onCase" @generate="onGenerate" />
     </n-layout-sider>
 
-    <Mermaid :genText="genText" :loading="loading" />
+    <Mermaid :text="mermaidText" :loading="loading" />
   </n-layout>
 </template>
 

+ 2 - 4
langchat-ui-client/src/views/modules/mindmap/components/MindMap.vue

@@ -2,8 +2,7 @@
   import { SvgIcon } from '@/components/common';
   import { Transformer } from 'markmap-lib';
   import { Markmap } from 'markmap-view';
-  import { onMounted, ref, watch } from 'vue';
-  import html2canvas from 'html2canvas';
+  import { onMounted, watch } from 'vue';
   import { t } from '@/locales';
   import { downloadPdf, downloadPng, downloadSvg } from '@/utils/downloadFile';
 
@@ -50,7 +49,7 @@
 
 <template>
   <div class="dot-bg w-full h-full" :class="genText == '' ? 'overflow-hidden' : ''">
-    <div v-if="genText !== ''" class="absolute top-0 z-10 p-2 flex flex-wrap justify-center gap-2">
+    <div class="absolute top-0 z-10 p-2 flex flex-wrap justify-center gap-2">
       <n-button @click="onZoomIn" text>
         <SvgIcon class="text-2xl" icon="basil:zoom-in-outline" />
       </n-button>
@@ -84,7 +83,6 @@
       <SvgIcon class="text-6xl" icon="ri:mind-map" />
       <div class="text-2xl font-bold">{{ t('mindmap.title') }}</div>
       <div class="text-gray-400">{{ t('mindmap.titleDes') }}</div>
-      <n-button type="success">{{ t('mindmap.begin') }}</n-button>
     </div>
 
     <div class="h-full w-full" id="mindmap-view">

+ 0 - 1
langchat-ui-client/src/views/modules/mindmap/components/Sider.vue

@@ -60,7 +60,6 @@
 
 <template>
   <div class="p-4">
-    <div class="pb-2">{{ t('mindmap.des') }}</div>
     <n-input
       :disabled="loading"
       v-model:value="text"

+ 17 - 6
langchat-ui-client/src/views/modules/mindmap/index.vue

@@ -6,22 +6,29 @@
   import { genMindMap } from '@/api/chat';
   import { isBlank } from '@/utils/is';
   import { t } from '@/locales';
+  // @ts-ignore
+  import { modelList } from '@/api/models/index.d.ts';
 
+  const model = ref('gpt-4o');
   const ms = useMessage();
   const loading = ref(false);
   const genText = ref('');
+
   async function onGenerate(text: string) {
     if (isBlank(text)) {
       ms.warning(t('common.emptyTips'));
       return;
     }
     loading.value = true;
-    const { message } = await genMindMap({
-      message: text,
-    });
-    genText.value = message;
-
-    loading.value = false;
+    try {
+      const { message } = await genMindMap({
+        message: text,
+        model: model.value,
+      });
+      genText.value = message;
+    } finally {
+      loading.value = false;
+    }
   }
 
   function onCase(text: string) {
@@ -39,6 +46,10 @@
       collapse-mode="width"
       show-trigger="arrow-circle"
     >
+      <div class="px-4 pt-2 flex items-center justify-between">
+        <div>{{ t('mindmap.des') }}</div>
+        <n-select size="small" v-model:value="model" :options="modelList" class="!w-[140px]" />
+      </div>
       <Sider :genText="genText" :loading="loading" @case="onCase" @generate="onGenerate" />
     </n-layout-sider>
 

+ 15 - 19
langchat-upms/src/main/java/cn/tycoding/langchat/upms/service/impl/SysUserServiceImpl.java

@@ -2,30 +2,36 @@ package cn.tycoding.langchat.upms.service.impl;
 
 import cn.hutool.core.bean.BeanUtil;
 import cn.tycoding.langchat.common.constant.CacheConst;
-import cn.tycoding.langchat.common.constant.CommonConst;
 import cn.tycoding.langchat.common.exception.ServiceException;
 import cn.tycoding.langchat.common.properties.AuthProps;
 import cn.tycoding.langchat.common.utils.MybatisUtil;
 import cn.tycoding.langchat.common.utils.QueryPage;
 import cn.tycoding.langchat.upms.dto.UserInfo;
-import cn.tycoding.langchat.upms.entity.*;
+import cn.tycoding.langchat.upms.entity.SysDept;
+import cn.tycoding.langchat.upms.entity.SysMenu;
+import cn.tycoding.langchat.upms.entity.SysRole;
+import cn.tycoding.langchat.upms.entity.SysUser;
+import cn.tycoding.langchat.upms.entity.SysUserRole;
 import cn.tycoding.langchat.upms.mapper.SysUserMapper;
-import cn.tycoding.langchat.upms.service.*;
+import cn.tycoding.langchat.upms.service.SysDeptService;
+import cn.tycoding.langchat.upms.service.SysMenuService;
+import cn.tycoding.langchat.upms.service.SysRoleService;
+import cn.tycoding.langchat.upms.service.SysUserRoleService;
+import cn.tycoding.langchat.upms.service.SysUserService;
 import cn.tycoding.langchat.upms.utils.AuthUtil;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.metadata.IPage;
 import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
-import lombok.RequiredArgsConstructor;
-import org.apache.commons.lang3.StringUtils;
-import org.springframework.cache.annotation.CacheEvict;
-import org.springframework.stereotype.Service;
-import org.springframework.transaction.annotation.Transactional;
-
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
+import lombok.RequiredArgsConstructor;
+import org.apache.commons.lang3.StringUtils;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 /**
  * 用户表(User)表服务实现类
@@ -139,11 +145,6 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
         user.setCreateTime(new Date());
         user.setPassword(AuthUtil.encode(authProps.getSaltKey(), user.getPassword()));
 
-        // 设置默认头像
-        if (StringUtils.isEmpty(user.getAvatar())) {
-            user.setAvatar(CommonConst.DEFAULT_AVATAR);
-        }
-
         // 设置角色
         if (user.getRoleIds() == null || user.getRoleIds().isEmpty()) {
             throw new ServiceException("用户角色不能为空");
@@ -173,11 +174,6 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
         if (!checkName(user)) {
             throw new ServiceException("该用户名已存在,请重新输入!");
         }
-
-        // 设置默认头像
-        if (StringUtils.isEmpty(user.getAvatar())) {
-            user.setAvatar(CommonConst.DEFAULT_AVATAR);
-        }
         user.setPassword(null);
         baseMapper.updateById(user);
         addRole(user);

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff