1811872455@163.com 1 tháng trước cách đây
mục cha
commit
ab0113df4c
19 tập tin đã thay đổi với 672 bổ sung113 xóa
  1. 27 10
      .idea/workspace.xml
  2. 85 83
      storlead-ai-api/src/main/java/com/storlead/ai/config/properties/OpenAiProperties.java
  3. 75 0
      storlead-ai-api/src/main/java/com/storlead/ai/config/properties/StorleadAiProperties.java
  4. 2 2
      storlead-ai-api/src/main/java/com/storlead/ai/controller/AiChatController.java
  5. 1 1
      storlead-ai-api/src/main/java/com/storlead/ai/core/AiProviderType.java
  6. 10 1
      storlead-ai-api/src/main/java/com/storlead/ai/factory/AiServiceFactoryManager.java
  7. 3 1
      storlead-ai-api/src/main/java/com/storlead/ai/factory/impl/OpenAiServiceFactory.java
  8. 35 0
      storlead-ai-api/src/main/java/com/storlead/ai/factory/impl/StorleadAiServiceFactory.java
  9. 320 0
      storlead-ai-api/src/main/java/com/storlead/ai/service/impl/StorleadAiService.java
  10. 2 2
      storlead-ai-api/src/main/java/com/storlead/ai/websocket/AiChatWebSocketHandler.java
  11. 18 1
      storlead-ai-api/src/main/resources/application-dev.yml
  12. 76 11
      storlead-ai-api/target/classes/META-INF/spring-configuration-metadata.json
  13. 18 1
      storlead-ai-api/target/classes/application-dev.yml
  14. BIN
      storlead-ai-api/target/classes/com/storlead/ai/config/properties/OpenAiProperties.class
  15. BIN
      storlead-ai-api/target/classes/com/storlead/ai/controller/AiChatController.class
  16. BIN
      storlead-ai-api/target/classes/com/storlead/ai/core/AiProviderType.class
  17. BIN
      storlead-ai-api/target/classes/com/storlead/ai/factory/AiServiceFactoryManager.class
  18. BIN
      storlead-ai-api/target/classes/com/storlead/ai/factory/impl/OpenAiServiceFactory.class
  19. BIN
      storlead-ai-api/target/classes/com/storlead/ai/websocket/AiChatWebSocketHandler.class

+ 27 - 10
.idea/workspace.xml

@@ -5,18 +5,25 @@
   </component>
   <component name="ChangeListManager">
     <list default="true" id="2dc641fc-1465-479e-874d-97069c194ded" name="Changes" comment="ai项目">
-      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/controller/WebSocketEndpointController.java" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/service/WebSocketService.java" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/websocket/AiChatWebSocketHandler.java" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/websocket/MessageType.java" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/websocket/WebSocketConfig.java" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/websocket/WebSocketConnectionManager.java" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/websocket/WebSocketMessage.java" afterDir="false" />
-      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/websocket/WebSocketSessionManager.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/config/properties/StorleadAiProperties.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/factory/impl/StorleadAiServiceFactory.java" afterDir="false" />
+      <change afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/service/impl/StorleadAiService.java" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/storlead-ai-api/pom.xml" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/pom.xml" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/config/properties/OpenAiProperties.java" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/config/properties/OpenAiProperties.java" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/controller/AiChatController.java" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/controller/AiChatController.java" afterDir="false" />
       <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/core/AiProviderType.java" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/core/AiProviderType.java" afterDir="false" />
-      <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/service/impl/OpenAiService.class" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/service/impl/OpenAiService.class" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/factory/AiServiceFactoryManager.java" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/factory/AiServiceFactoryManager.java" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/factory/impl/OpenAiServiceFactory.java" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/factory/impl/OpenAiServiceFactory.java" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/websocket/AiChatWebSocketHandler.java" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/websocket/AiChatWebSocketHandler.java" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/src/main/resources/application-dev.yml" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/src/main/resources/application-dev.yml" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/META-INF/spring-configuration-metadata.json" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/META-INF/spring-configuration-metadata.json" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/application-dev.yml" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/application-dev.yml" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/config/properties/OpenAiProperties.class" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/config/properties/OpenAiProperties.class" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/controller/AiChatController.class" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/controller/AiChatController.class" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/core/AiProviderType.class" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/core/AiProviderType.class" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/factory/AiServiceFactoryManager.class" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/factory/AiServiceFactoryManager.class" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/factory/impl/OpenAiServiceFactory.class" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/factory/impl/OpenAiServiceFactory.class" afterDir="false" />
+      <change beforePath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/websocket/AiChatWebSocketHandler.class" beforeDir="false" afterPath="$PROJECT_DIR$/storlead-ai-api/target/classes/com/storlead/ai/websocket/AiChatWebSocketHandler.class" afterDir="false" />
     </list>
     <option name="SHOW_DIALOG" value="false" />
     <option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -124,6 +131,8 @@
       <workItem from="1758505253719" duration="15768000" />
       <workItem from="1758591153350" duration="8846000" />
       <workItem from="1758780969521" duration="11096000" />
+      <workItem from="1758849538252" duration="14257000" />
+      <workItem from="1759021657878" duration="3410000" />
     </task>
     <task id="LOCAL-00001" summary="ai项目">
       <option name="closed" value="true" />
@@ -187,6 +196,14 @@
           <line>1</line>
           <option name="timeStamp" value="13" />
         </line-breakpoint>
+        <line-breakpoint enabled="true" type="java-line">
+          <url>file://$PROJECT_DIR$/storlead-ai-api/src/main/java/com/storlead/ai/service/impl/OpenAiService.java</url>
+          <line>64</line>
+          <properties>
+            <option name="lambda-ordinal" value="-1" />
+          </properties>
+          <option name="timeStamp" value="19" />
+        </line-breakpoint>
       </breakpoints>
     </breakpoint-manager>
   </component>

+ 85 - 83
storlead-ai-api/src/main/java/com/storlead/ai/config/properties/OpenAiProperties.java

@@ -1,5 +1,6 @@
 package com.storlead.ai.config.properties;
 
+import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.stereotype.Component;
 
@@ -12,7 +13,8 @@ import java.util.List;
  * @since 1.0.0
  */
 @Component
-@ConfigurationProperties(prefix = "ai.providers.openai")
+@ConfigurationProperties(prefix = "ai.providers.storleadai")
+@Data
 public class OpenAiProperties {
 
     /**
@@ -33,7 +35,7 @@ public class OpenAiProperties {
     /**
      * 默认模型
      */
-    private String defaultModel = "gpt-3.5-turbo";
+    private String defaultModel = "deepseek-r1:14b";
 
     /**
      * 支持的模型列表
@@ -70,85 +72,85 @@ public class OpenAiProperties {
      * 是否启用流式响应
      */
     private boolean streamEnabled = true;
-
-    // Getters and Setters
-    public boolean isEnabled() {
-        return enabled;
-    }
-
-    public void setEnabled(boolean enabled) {
-        this.enabled = enabled;
-    }
-
-    public String getApiKey() {
-        return apiKey;
-    }
-
-    public void setApiKey(String apiKey) {
-        this.apiKey = apiKey;
-    }
-
-    public String getBaseUrl() {
-        return baseUrl;
-    }
-
-    public void setBaseUrl(String baseUrl) {
-        this.baseUrl = baseUrl;
-    }
-
-    public String getDefaultModel() {
-        return defaultModel;
-    }
-
-    public void setDefaultModel(String defaultModel) {
-        this.defaultModel = defaultModel;
-    }
-
-    public List<String> getSupportedModels() {
-        return supportedModels;
-    }
-
-    public void setSupportedModels(List<String> supportedModels) {
-        this.supportedModels = supportedModels;
-    }
-
-    public int getTimeoutSeconds() {
-        return timeoutSeconds;
-    }
-
-    public void setTimeoutSeconds(int timeoutSeconds) {
-        this.timeoutSeconds = timeoutSeconds;
-    }
-
-    public int getMaxRetries() {
-        return maxRetries;
-    }
-
-    public void setMaxRetries(int maxRetries) {
-        this.maxRetries = maxRetries;
-    }
-
-    public double getDefaultTemperature() {
-        return defaultTemperature;
-    }
-
-    public void setDefaultTemperature(double defaultTemperature) {
-        this.defaultTemperature = defaultTemperature;
-    }
-
-    public int getDefaultMaxTokens() {
-        return defaultMaxTokens;
-    }
-
-    public void setDefaultMaxTokens(int defaultMaxTokens) {
-        this.defaultMaxTokens = defaultMaxTokens;
-    }
-
-    public boolean isStreamEnabled() {
-        return streamEnabled;
-    }
-
-    public void setStreamEnabled(boolean streamEnabled) {
-        this.streamEnabled = streamEnabled;
-    }
+//
+//    // Getters and Setters
+//    public boolean isEnabled() {
+//        return enabled;
+//    }
+//
+//    public void setEnabled(boolean enabled) {
+//        this.enabled = enabled;
+//    }
+//
+//    public String getApiKey() {
+//        return apiKey;
+//    }
+//
+//    public void setApiKey(String apiKey) {
+//        this.apiKey = apiKey;
+//    }
+//
+//    public String getBaseUrl() {
+//        return baseUrl;
+//    }
+//
+//    public void setBaseUrl(String baseUrl) {
+//        this.baseUrl = baseUrl;
+//    }
+//
+//    public String getDefaultModel() {
+//        return defaultModel;
+//    }
+//
+//    public void setDefaultModel(String defaultModel) {
+//        this.defaultModel = defaultModel;
+//    }
+//
+//    public List<String> getSupportedModels() {
+//        return supportedModels;
+//    }
+//
+//    public void setSupportedModels(List<String> supportedModels) {
+//        this.supportedModels = supportedModels;
+//    }
+//
+//    public int getTimeoutSeconds() {
+//        return timeoutSeconds;
+//    }
+//
+//    public void setTimeoutSeconds(int timeoutSeconds) {
+//        this.timeoutSeconds = timeoutSeconds;
+//    }
+//
+//    public int getMaxRetries() {
+//        return maxRetries;
+//    }
+//
+//    public void setMaxRetries(int maxRetries) {
+//        this.maxRetries = maxRetries;
+//    }
+//
+//    public double getDefaultTemperature() {
+//        return defaultTemperature;
+//    }
+//
+//    public void setDefaultTemperature(double defaultTemperature) {
+//        this.defaultTemperature = defaultTemperature;
+//    }
+//
+//    public int getDefaultMaxTokens() {
+//        return defaultMaxTokens;
+//    }
+//
+//    public void setDefaultMaxTokens(int defaultMaxTokens) {
+//        this.defaultMaxTokens = defaultMaxTokens;
+//    }
+//
+//    public boolean isStreamEnabled() {
+//        return streamEnabled;
+//    }
+//
+//    public void setStreamEnabled(boolean streamEnabled) {
+//        this.streamEnabled = streamEnabled;
+//    }
 }

+ 75 - 0
storlead-ai-api/src/main/java/com/storlead/ai/config/properties/StorleadAiProperties.java

@@ -0,0 +1,75 @@
+package com.storlead.ai.config.properties;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+/**
+ * @program: storlead-ai-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-09-26 09:59
+ */
+@Component
+@ConfigurationProperties(prefix = "ai.providers.storlead")
+@Data
+public class StorleadAiProperties {
+
+    /**
+     * 是否启用OpenAI
+     */
+    private boolean enabled = false;
+
+    /**
+     * API密钥
+     */
+    private String apiKey;
+
+    /**
+     * API基础URL
+     */
+    private String baseUrl = "http://47.112.196.2:11434";
+
+    /**
+     * 默认模型
+     */
+    private String defaultModel = "gpt-3.5-turbo";
+
+    /**
+     * 支持的模型列表
+     */
+    private List<String> supportedModels = List.of(
+            "gpt-3.5-turbo",
+            "gpt-3.5-turbo-16k",
+            "gpt-4",
+            "gpt-4-32k",
+            "gpt-4-turbo-preview"
+    );
+
+    /**
+     * 请求超时时间(秒)
+     */
+    private int timeoutSeconds = 30;
+
+    /**
+     * 最大重试次数
+     */
+    private int maxRetries = 3;
+
+    /**
+     * 默认温度参数
+     */
+    private double defaultTemperature = 0.7;
+
+    /**
+     * 默认最大令牌数
+     */
+    private int defaultMaxTokens = 1000;
+
+    /**
+     * 是否启用流式响应
+     */
+    private boolean streamEnabled = true;
+}

+ 2 - 2
storlead-ai-api/src/main/java/com/storlead/ai/controller/AiChatController.java

@@ -45,7 +45,7 @@ public class AiChatController {
     @GetMapping("/chat")
     @Operation(summary = "同步聊天", description = "使用指定的AI提供商进行一次性聊天")
     public Mono<ChatResponse> chat(@RequestBody ChatRequest request) {
-        String provider = "openai";
+        String provider = "storleadai";
         return aiChatService.chat(provider, request);
     }
 
@@ -55,7 +55,7 @@ public class AiChatController {
     @GetMapping(value = "/chat/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
     @Operation(summary = "流式聊天", description = "使用指定的AI提供商进行流式聊天")
     public Flux<String> chatStream(@RequestBody ChatRequest request) {
-        String provider = "openai";
+        String provider = "storleadai";
         logger.info("收到流式聊天请求 - 提供商: {}", provider);
         return aiChatService.chatStream(provider, request)
                 .delayElements(Duration.ofMillis(50)); // 添加小延迟以改善流式体验

+ 1 - 1
storlead-ai-api/src/main/java/com/storlead/ai/core/AiProviderType.java

@@ -8,7 +8,7 @@ package com.storlead.ai.core;
  */
 public enum AiProviderType {
 
-    STROLEAAI("storleadai", "Ollama"),
+    STROLEAAI("storleadai", "StorleadAI"),
     /**
      * OpenAI GPT系列
      */

+ 10 - 1
storlead-ai-api/src/main/java/com/storlead/ai/factory/AiServiceFactoryManager.java

@@ -31,6 +31,10 @@ public class AiServiceFactoryManager {
     @Resource
     private OpenAiServiceFactory openAiFactory;
 
+//    // 可选注入,如果某个工厂不可用则忽略
+    @Resource
+    private StorleadAiServiceFactory storeladAiFactory;
+
     /**
      * 初始化注册所有可用的工厂
      */
@@ -42,6 +46,12 @@ public class AiServiceFactoryManager {
         if (openAiFactory != null) {
             registerFactory(openAiFactory);
         }
+
+        // 注册可用的工厂
+        if (storeladAiFactory != null) {
+            registerFactory(storeladAiFactory);
+        }
+
         logger.info("AI服务工厂管理器初始化完成,已注册 {} 个工厂", factories.size());
         factories.keySet().forEach(type ->
                 logger.info("- 已注册工厂: {}", type.getDisplayName()));
@@ -56,7 +66,6 @@ public class AiServiceFactoryManager {
         if (factory == null) {
             return;
         }
-
         try {
             AiProviderType type = factory.getSupportedType();
             factories.put(type, factory);

+ 3 - 1
storlead-ai-api/src/main/java/com/storlead/ai/factory/impl/OpenAiServiceFactory.java

@@ -8,6 +8,8 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
 import org.springframework.stereotype.Component;
 
+import javax.annotation.Resource;
+
 /**
  * OpenAI服务工厂
  *
@@ -18,7 +20,7 @@ import org.springframework.stereotype.Component;
 @ConditionalOnProperty(prefix = "ai.providers.openai", name = "enabled", havingValue = "true")
 public class OpenAiServiceFactory extends AiServiceFactory {
 
-    @Autowired
+    @Resource
     private OpenAiService openAiService;
 
     @Override

+ 35 - 0
storlead-ai-api/src/main/java/com/storlead/ai/factory/impl/StorleadAiServiceFactory.java

@@ -0,0 +1,35 @@
+package com.storlead.ai.factory.impl;
+
+import com.storlead.ai.core.AiProviderType;
+import com.storlead.ai.core.AiService;
+import com.storlead.ai.factory.AiServiceFactory;
+import com.storlead.ai.service.impl.StorleadAiService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * @program: storlead-ai-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-09-26 10:23
+ */
+@Component
+@ConditionalOnProperty(prefix = "ai.providers.storleadai", name = "enabled", havingValue = "true")
+public class StorleadAiServiceFactory extends AiServiceFactory {
+
+    @Resource
+    private StorleadAiService storleadAiService;
+
+    @Override
+    public AiService createAiService() {
+        return storleadAiService;
+    }
+
+    @Override
+    public AiProviderType getSupportedType() {
+        return AiProviderType.STROLEAAI;
+    }
+}

+ 320 - 0
storlead-ai-api/src/main/java/com/storlead/ai/service/impl/StorleadAiService.java

@@ -0,0 +1,320 @@
+package com.storlead.ai.service.impl;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.storlead.ai.config.properties.OpenAiProperties;
+import com.storlead.ai.core.AiProviderType;
+import com.storlead.ai.core.AiService;
+import com.storlead.ai.core.ChatRequest;
+import com.storlead.ai.core.ChatResponse;
+import com.storlead.ai.exception.AiServiceException;
+import com.storlead.ai.util.HttpClientUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferUtils;
+import org.springframework.stereotype.Service;
+import org.springframework.web.reactive.function.client.WebClient;
+import reactor.core.publisher.Flux;
+import reactor.core.publisher.Mono;
+
+import javax.annotation.PostConstruct;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @program: storlead-ai-platform
+ * @description:
+ * @author: chenkq
+ * @create: 2025-09-26 09:39
+ */
+@Service
+@ConditionalOnProperty(prefix = "ai.providers.storleadai", name = "enabled", havingValue = "true")
+public class StorleadAiService implements AiService {
+
+    private static final Logger logger = LoggerFactory.getLogger(OpenAiService.class);
+
+    @Autowired
+    private OpenAiProperties properties;
+
+    @Autowired
+    private HttpClientUtil httpClientUtil;
+
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    private WebClient webClient;
+
+    @PostConstruct
+    public void init() {
+        this.webClient = httpClientUtil.createWebClient(
+                properties.getBaseUrl(),
+                properties.getTimeoutSeconds()
+        );
+        logger.info("OpenAI服务初始化完成 - baseUrl: {}, model: {}",
+                properties.getBaseUrl(), properties.getDefaultModel());
+    }
+
+    @Override
+    public Mono<ChatResponse> chat(ChatRequest request) {
+        return Mono.fromCallable(() -> buildRequestBody(request))
+                .flatMap(requestBody -> {
+                    return httpClientUtil.postForObject(
+                            webClient,
+                            "/v1/chat/completions",
+                            requestBody,
+                            JsonNode.class,
+                            httpClientUtil.createBearerAuthHeaders(properties.getApiKey()),
+                            properties.getMaxRetries()
+                    );
+                })
+                .map(this::parseResponse)
+                .onErrorMap(this::handleException);
+    }
+
+    @Override
+    public Flux<String> chatStream(ChatRequest request) {
+        logger.debug("开始构建流式请求");
+
+        Map<String, Object> requestBody = buildStreamRequestBody(request);
+        logger.debug("流式请求体: {}", requestBody);
+
+        return webClient.post()
+                .uri("/v1/chat/completions")
+                .headers(httpHeaders -> {
+                    httpHeaders.setBearerAuth(properties.getApiKey());
+                    httpHeaders.set("Accept", "text/event-stream");
+                })
+                .bodyValue(requestBody)
+                .retrieve()
+                .bodyToFlux(DataBuffer.class)
+                .map(dataBuffer -> {
+                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
+                    dataBuffer.read(bytes);
+                    DataBufferUtils.release(dataBuffer);
+                    return new String(bytes, StandardCharsets.UTF_8);
+                })
+                .flatMapIterable(data -> Arrays.asList(data.split("\n")))
+                .filter(line -> !line.trim().isEmpty())
+                .filter(line -> line.startsWith("data: ") && !line.equals("data: [DONE]"))
+                .map(line -> line.substring(6).trim())
+                .filter(this::isValidJson)  // 添加JSON有效性检查
+                .mapNotNull(this::parseStreamChunk)
+                .onErrorMap(this::handleException);
+    }
+
+    /**
+     * 检查JSON是否有效且完整
+     */
+    private boolean isValidJson(String json) {
+        if (json.trim().isEmpty()) return false;
+        try {
+            objectMapper.readTree(json);
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
+    }
+
+    @Override
+    public AiProviderType getProviderType() {
+        return AiProviderType.OPENAI;
+    }
+
+    @Override
+    public boolean isAvailable() {
+        try {
+            // 简单的健康检查 - 发送一个极简的请求
+            Map<String, Object> testRequest = Map.of(
+                    "model", properties.getDefaultModel(),
+                    "messages", List.of(Map.of("role", "user", "content", "test")),
+                    "max_tokens", 1
+            );
+
+            return httpClientUtil.postForObject(
+                    webClient,
+                    "/v1/chat/completions",
+                    testRequest,
+                    JsonNode.class,
+                    httpClientUtil.createBearerAuthHeaders(properties.getApiKey()),
+                    1
+            ).block() != null;
+        } catch (Exception e) {
+            logger.warn("OpenAI服务健康检查失败: {}", e.getMessage());
+            return false;
+        }
+    }
+
+    @Override
+    public List<String> getSupportedModels() {
+        return properties.getSupportedModels();
+    }
+
+    @Override
+    public String getDefaultModel() {
+        return properties.getDefaultModel();
+    }
+
+    /**
+     * 构建请求体
+     */
+    private Map<String, Object> buildRequestBody(ChatRequest request) {
+        Map<String, Object> requestBody = new HashMap<>();
+
+        // 基础参数
+        requestBody.put("model", getModelName(request));
+        requestBody.put("messages", buildMessages(request));
+
+        // 可选参数
+        Map<String, Object> params = request.getParameters();
+        if (params != null) {
+            requestBody.put("temperature", params.getOrDefault("temperature", properties.getDefaultTemperature()));
+            requestBody.put("max_tokens", params.getOrDefault("max_tokens", properties.getDefaultMaxTokens()));
+
+            if (params.containsKey("top_p")) {
+                requestBody.put("top_p", params.get("top_p"));
+            }
+            if (params.containsKey("frequency_penalty")) {
+                requestBody.put("frequency_penalty", params.get("frequency_penalty"));
+            }
+            if (params.containsKey("presence_penalty")) {
+                requestBody.put("presence_penalty", params.get("presence_penalty"));
+            }
+        } else {
+            requestBody.put("temperature", properties.getDefaultTemperature());
+            requestBody.put("max_tokens", properties.getDefaultMaxTokens());
+        }
+
+        return requestBody;
+    }
+
+    /**
+     * 构建流式请求体
+     */
+    private Map<String, Object> buildStreamRequestBody(ChatRequest request) {
+        Map<String, Object> requestBody = buildRequestBody(request);
+        requestBody.put("stream", true);
+        return requestBody;
+    }
+
+    /**
+     * 构建消息列表
+     */
+    private List<Map<String, String>> buildMessages(ChatRequest request) {
+        List<Map<String, String>> messages = new java.util.ArrayList<>();
+
+        // 系统提示词
+        if (request.getSystemPrompt() != null && !request.getSystemPrompt().trim().isEmpty()) {
+            messages.add(Map.of("role", "system", "content", request.getSystemPrompt()));
+        }
+
+        // 用户消息
+        messages.add(Map.of("role", "user", "content", request.getMessage()));
+
+        return messages;
+    }
+
+    /**
+     * 获取模型名称
+     */
+    private String getModelName(ChatRequest request) {
+        if (request.getModel() != null && !request.getModel().trim().isEmpty()) {
+            return request.getModel();
+        }
+        return properties.getDefaultModel();
+    }
+
+    /**
+     * 解析响应
+     */
+    private ChatResponse parseResponse(JsonNode responseJson) {
+        try {
+            if (responseJson.has("error")) {
+                JsonNode error = responseJson.get("error");
+                String errorMessage = error.get("message").asText();
+                String errorCode = error.has("code") ? error.get("code").asText() : "OPENAI_ERROR";
+                throw AiServiceException.apiError(AiProviderType.OPENAI, errorMessage);
+            }
+
+            JsonNode choices = responseJson.get("choices");
+            if (choices == null || choices.isEmpty()) {
+                throw AiServiceException.apiError(AiProviderType.OPENAI, "响应中没有选择项");
+            }
+
+            JsonNode firstChoice = choices.get(0);
+            JsonNode message = firstChoice.get("message");
+            String content = message.get("content").asText();
+
+            String model = responseJson.has("model") ? responseJson.get("model").asText() : properties.getDefaultModel();
+
+            // 构建元数据
+            Map<String, Object> metadata = new HashMap<>();
+            if (responseJson.has("usage")) {
+                metadata.put("usage", responseJson.get("usage"));
+            }
+            if (firstChoice.has("finish_reason")) {
+                metadata.put("finishReason", firstChoice.get("finish_reason").asText());
+            }
+
+            ChatResponse response = ChatResponse.success(content, model);
+            response.setProviderType(AiProviderType.OPENAI);
+            response.setMetadata(metadata);
+
+            return response;
+        } catch (Exception e) {
+            logger.error("解析OpenAI响应失败", e);
+            throw AiServiceException.apiError(AiProviderType.OPENAI, "响应解析失败: " + e.getMessage());
+        }
+    }
+
+    /**
+     * 解析流式响应块
+     */
+    private String parseStreamChunk(String chunk) {
+        try {
+            JsonNode chunkJson = objectMapper.readTree(chunk);
+
+            if (chunkJson.has("choices")) {
+                JsonNode choices = chunkJson.get("choices");
+                if (!choices.isEmpty()) {
+                    JsonNode firstChoice = choices.get(0);
+                    if (firstChoice.has("delta")) {
+                        JsonNode delta = firstChoice.get("delta");
+                        if (delta.has("content")) {
+                            String content = delta.get("content").asText();
+                            // 只返回纯文本内容,不包含任何JSON结构
+                            return content;
+                        }
+                    }
+                }
+            }
+            return null; // 如果没有content,返回null会被mapNotNull过滤掉
+        } catch (Exception e) {
+            logger.warn("解析流式响应块失败: {}", chunk, e);
+            return null;
+        }
+    }
+
+    /**
+     * 异常处理
+     */
+    private Throwable handleException(Throwable throwable) {
+        String errorMessage = httpClientUtil.parseWebClientError(throwable);
+        logger.error("OpenAI API调用失败: {}", errorMessage, throwable);
+
+        if (errorMessage.contains("401")) {
+            return AiServiceException.authenticationError(AiProviderType.OPENAI);
+        } else if (errorMessage.contains("429")) {
+            return AiServiceException.rateLimitError(AiProviderType.OPENAI);
+        } else if (errorMessage.contains("quota")) {
+            return AiServiceException.quotaExceededError(AiProviderType.OPENAI);
+        } else {
+            return AiServiceException.networkError(AiProviderType.OPENAI, throwable);
+        }
+    }
+}

+ 2 - 2
storlead-ai-api/src/main/java/com/storlead/ai/websocket/AiChatWebSocketHandler.java

@@ -325,7 +325,7 @@ public class AiChatWebSocketHandler implements WebSocketHandler {
             logger.debug("not found provider : {}", session.getId(), e);
         }
         // 如果无法提取用户ID,使用会话ID生成一个
-        return "Storlead-Ai";
+        return "storleadai";
     }
 
     /**
@@ -345,7 +345,7 @@ public class AiChatWebSocketHandler implements WebSocketHandler {
             logger.debug("not found provider : {}", session.getId(), e);
         }
         // 如果无法提取用户ID,使用会话ID生成一个
-        return "all";
+        return "deepseek-r1:14b";
     }
 
 

+ 18 - 1
storlead-ai-api/src/main/resources/application-dev.yml

@@ -135,7 +135,7 @@ ai:
     openai:
       enabled: true
       api-key:
-      base-url: http://47.112.196.2:11434
+      base-url: http://192.168.1.77:11434
       default-model: deepseek-r1:14b
       timeout-seconds: 30
       max-retries: 3
@@ -146,6 +146,23 @@ ai:
         - deepseek-r1:14b
         - deepseek-r1:8b
         - gpt-oss:20b
+    # StorledAI配置
+    storleadai:
+      enabled: true
+      api-key:
+      base-url: http://192.168.1.77:11434
+      default-model: deepseek-r1:14b
+      timeout-seconds: 30
+      max-retries: 3
+      default-temperature: 0.7
+      default-max-tokens: 1000
+      stream-enabled: true
+      supported-models:
+        - deepseek-r1:14b
+        - deepseek-r1:8b
+        - gpt-oss:20b
+
+
 
 #mybatis plus 设置
 mybatis-plus:

+ 76 - 11
storlead-ai-api/target/classes/META-INF/spring-configuration-metadata.json

@@ -1,68 +1,133 @@
 {
   "groups": [
     {
-      "name": "ai.providers.openai",
+      "name": "ai.providers.storlead",
+      "type": "com.storlead.ai.config.properties.StorleadAiProperties",
+      "sourceType": "com.storlead.ai.config.properties.StorleadAiProperties"
+    },
+    {
+      "name": "ai.providers.storleadai",
       "type": "com.storlead.ai.config.properties.OpenAiProperties",
       "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
     }
   ],
   "properties": [
     {
-      "name": "ai.providers.openai.api-key",
+      "name": "ai.providers.storlead.api-key",
+      "type": "java.lang.String",
+      "description": "API密钥",
+      "sourceType": "com.storlead.ai.config.properties.StorleadAiProperties"
+    },
+    {
+      "name": "ai.providers.storlead.base-url",
+      "type": "java.lang.String",
+      "description": "API基础URL",
+      "sourceType": "com.storlead.ai.config.properties.StorleadAiProperties"
+    },
+    {
+      "name": "ai.providers.storlead.default-max-tokens",
+      "type": "java.lang.Integer",
+      "description": "默认最大令牌数",
+      "sourceType": "com.storlead.ai.config.properties.StorleadAiProperties"
+    },
+    {
+      "name": "ai.providers.storlead.default-model",
+      "type": "java.lang.String",
+      "description": "默认模型",
+      "sourceType": "com.storlead.ai.config.properties.StorleadAiProperties"
+    },
+    {
+      "name": "ai.providers.storlead.default-temperature",
+      "type": "java.lang.Double",
+      "description": "默认温度参数",
+      "sourceType": "com.storlead.ai.config.properties.StorleadAiProperties"
+    },
+    {
+      "name": "ai.providers.storlead.enabled",
+      "type": "java.lang.Boolean",
+      "description": "是否启用OpenAI",
+      "sourceType": "com.storlead.ai.config.properties.StorleadAiProperties"
+    },
+    {
+      "name": "ai.providers.storlead.max-retries",
+      "type": "java.lang.Integer",
+      "description": "最大重试次数",
+      "sourceType": "com.storlead.ai.config.properties.StorleadAiProperties"
+    },
+    {
+      "name": "ai.providers.storlead.stream-enabled",
+      "type": "java.lang.Boolean",
+      "description": "是否启用流式响应",
+      "sourceType": "com.storlead.ai.config.properties.StorleadAiProperties"
+    },
+    {
+      "name": "ai.providers.storlead.supported-models",
+      "type": "java.util.List<java.lang.String>",
+      "description": "支持的模型列表",
+      "sourceType": "com.storlead.ai.config.properties.StorleadAiProperties"
+    },
+    {
+      "name": "ai.providers.storlead.timeout-seconds",
+      "type": "java.lang.Integer",
+      "description": "请求超时时间(秒)",
+      "sourceType": "com.storlead.ai.config.properties.StorleadAiProperties"
+    },
+    {
+      "name": "ai.providers.storleadai.api-key",
       "type": "java.lang.String",
       "description": "API密钥",
       "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
     },
     {
-      "name": "ai.providers.openai.base-url",
+      "name": "ai.providers.storleadai.base-url",
       "type": "java.lang.String",
       "description": "API基础URL",
       "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
     },
     {
-      "name": "ai.providers.openai.default-max-tokens",
+      "name": "ai.providers.storleadai.default-max-tokens",
       "type": "java.lang.Integer",
       "description": "默认最大令牌数",
       "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
     },
     {
-      "name": "ai.providers.openai.default-model",
+      "name": "ai.providers.storleadai.default-model",
       "type": "java.lang.String",
       "description": "默认模型",
       "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
     },
     {
-      "name": "ai.providers.openai.default-temperature",
+      "name": "ai.providers.storleadai.default-temperature",
       "type": "java.lang.Double",
       "description": "默认温度参数",
       "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
     },
     {
-      "name": "ai.providers.openai.enabled",
+      "name": "ai.providers.storleadai.enabled",
       "type": "java.lang.Boolean",
       "description": "是否启用OpenAI",
       "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
     },
     {
-      "name": "ai.providers.openai.max-retries",
+      "name": "ai.providers.storleadai.max-retries",
       "type": "java.lang.Integer",
       "description": "最大重试次数",
       "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
     },
     {
-      "name": "ai.providers.openai.stream-enabled",
+      "name": "ai.providers.storleadai.stream-enabled",
       "type": "java.lang.Boolean",
       "description": "是否启用流式响应",
       "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
     },
     {
-      "name": "ai.providers.openai.supported-models",
+      "name": "ai.providers.storleadai.supported-models",
       "type": "java.util.List<java.lang.String>",
       "description": "支持的模型列表",
       "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"
     },
     {
-      "name": "ai.providers.openai.timeout-seconds",
+      "name": "ai.providers.storleadai.timeout-seconds",
       "type": "java.lang.Integer",
       "description": "请求超时时间(秒)",
       "sourceType": "com.storlead.ai.config.properties.OpenAiProperties"

+ 18 - 1
storlead-ai-api/target/classes/application-dev.yml

@@ -135,7 +135,7 @@ ai:
     openai:
       enabled: true
       api-key:
-      base-url: http://47.112.196.2:11434
+      base-url: http://192.168.1.77:11434
       default-model: deepseek-r1:14b
       timeout-seconds: 30
       max-retries: 3
@@ -146,6 +146,23 @@ ai:
         - deepseek-r1:14b
         - deepseek-r1:8b
         - gpt-oss:20b
+    # StorledAI配置
+    storleadai:
+      enabled: true
+      api-key:
+      base-url: http://192.168.1.77:11434
+      default-model: deepseek-r1:14b
+      timeout-seconds: 30
+      max-retries: 3
+      default-temperature: 0.7
+      default-max-tokens: 1000
+      stream-enabled: true
+      supported-models:
+        - deepseek-r1:14b
+        - deepseek-r1:8b
+        - gpt-oss:20b
+
+
 
 #mybatis plus 设置
 mybatis-plus:

BIN
storlead-ai-api/target/classes/com/storlead/ai/config/properties/OpenAiProperties.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/controller/AiChatController.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/core/AiProviderType.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/factory/AiServiceFactoryManager.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/factory/impl/OpenAiServiceFactory.class


BIN
storlead-ai-api/target/classes/com/storlead/ai/websocket/AiChatWebSocketHandler.class