diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/config/CorsConfig.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/config/CorsConfig.java new file mode 100644 index 0000000..6f93d68 --- /dev/null +++ b/prompto-lab-app/src/main/java/io/github/timemachinelab/config/CorsConfig.java @@ -0,0 +1,50 @@ +package io.github.timemachinelab.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.Arrays; + +@Configuration +public class CorsConfig implements WebMvcConfigurer { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOriginPatterns("http://localhost:*", "http://127.0.0.1:*") + .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") + .allowedHeaders("*") + .allowCredentials(true) + .maxAge(3600); + } + + @Bean + public CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + + // 允许的源 + configuration.setAllowedOriginPatterns(Arrays.asList("http://localhost:*", "http://127.0.0.1:*")); + + // 允许的HTTP方法 + configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); + + // 允许的请求头 + configuration.setAllowedHeaders(Arrays.asList("*")); + + // 允许携带凭证 + configuration.setAllowCredentials(true); + + // 预检请求的缓存时间 + configuration.setMaxAge(3600L); + + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + + return source; + } +} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/Session.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/Session.java index e84b984..64d3149 100644 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/Session.java +++ b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/Session.java @@ -1,4 +1,7 @@ package io.github.timemachinelab.core.session; public class Session { + + + } diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/application/ConversationService.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/application/ConversationService.java new file mode 100644 index 0000000..95959b7 --- /dev/null +++ b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/application/ConversationService.java @@ -0,0 +1,111 @@ +package io.github.timemachinelab.core.session.application; + +import io.github.timemachinelab.core.session.domain.entity.ConversationSession; +import io.github.timemachinelab.core.session.infrastructure.web.dto.MessageResponse; +import io.github.timemachinelab.core.session.infrastructure.ai.ConversationOperation; +import io.github.timemachinelab.sfchain.core.AIService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.Map; +import java.util.List; +import java.util.ArrayList; +import java.util.function.Consumer; + +@Service +@RequiredArgsConstructor +@Slf4j +public class ConversationService { + + private final AIService aiService; + + private final Map sessions = new ConcurrentHashMap<>(); + + public ConversationSession createSession(String userId) { + ConversationSession session = new ConversationSession(userId); + sessions.put(session.getSessionId(), session); + return session; + } + + public ConversationSession getSession(String sessionId) { + return sessions.get(sessionId); + } + + public void processUserMessage(String sessionId, String userMessage, Consumer sseCallback) { + ConversationSession session = sessions.get(sessionId); + if (session == null) { + log.warn("会话不存在: {}", sessionId); + return; + } + + // 1. 添加用户消息到会话历史 + session.addMessage(userMessage, "user"); + + // 2. 发送用户消息确认 + sseCallback.accept(MessageResponse.userAnswer("user_" + System.currentTimeMillis(), userMessage)); + + // 3. 调用AI服务获取回复 + processAIResponse(session, userMessage, sseCallback); + } + + private void processAIResponse(ConversationSession session, String userMessage, Consumer sseCallback) { + try { + // 构建对话历史 + List history = buildConversationHistory(session); + + // 创建AI请求 + ConversationOperation.ConversationRequest request = new ConversationOperation.ConversationRequest( + session.getSessionId(), + "current", + userMessage + ); + request.setConversationHistory(history); + + // 调用AI服务 + ConversationOperation.ConversationResponse aiResponse = aiService.execute("CONVERSATION_OP", request); + log.info("AI服务调用成功: {}", aiResponse); + + // 添加AI回复到会话历史 + session.addMessage(aiResponse.getAnswer(), "assistant"); + + // 根据响应类型处理AI回复 + String nodeId = "ai_" + System.currentTimeMillis(); + sseCallback.accept(MessageResponse.aiAnswer("ai_" + System.currentTimeMillis(), aiResponse.getAnswer())); +// if (aiResponse.getResponseType() == ConversationOperation.ResponseType.SELECTION) { +// // 选择题类型 +// sseCallback.accept(MessageResponse.aiSelectionQuestion(nodeId, aiResponse.getAnswer(), aiResponse.getOptions())); +// } else { +// // 普通文本回复 +// sseCallback.accept(MessageResponse.aiQuestion(nodeId, aiResponse.getAnswer())); +// } + + } catch (Exception e) { + log.error("AI服务调用失败: {}", e.getMessage(), e); + // 降级处理 + String fallbackResponse = "抱歉,我暂时无法处理您的请求,请稍后再试。"; + session.addMessage(fallbackResponse, "assistant"); + String nodeId = "ai_" + System.currentTimeMillis(); + sseCallback.accept(MessageResponse.aiQuestion(nodeId, fallbackResponse)); + } + } + + private List buildConversationHistory(ConversationSession session) { + List history = new ArrayList<>(); + + // 从会话消息构建对话历史 + for (ConversationSession.ConversationMessage message : session.getMessages()) { + ConversationOperation.ConversationHistory historyItem = new ConversationOperation.ConversationHistory( + message.getRole(), + message.getContent(), + message.getRole() + "_" + message.getTimestamp().toString() + ); + history.add(historyItem); + } + + return history; + } + + +} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/domain/entity/ConversationSession.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/domain/entity/ConversationSession.java new file mode 100644 index 0000000..9e77f51 --- /dev/null +++ b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/domain/entity/ConversationSession.java @@ -0,0 +1,45 @@ +package io.github.timemachinelab.core.session.domain.entity; + +import lombok.Getter; + +import java.time.LocalDateTime; +import java.util.UUID; +import java.util.List; +import java.util.ArrayList; + +@Getter +public class ConversationSession { + + private final String sessionId; + private final String userId; + private final LocalDateTime createTime; + private LocalDateTime updateTime; + private final List messages; + + public ConversationSession(String userId) { + this.sessionId = UUID.randomUUID().toString(); + this.userId = userId; + this.createTime = LocalDateTime.now(); + this.updateTime = LocalDateTime.now(); + this.messages = new ArrayList<>(); + } + + public void addMessage(String content, String role) { + ConversationMessage message = new ConversationMessage(content, role); + this.messages.add(message); + this.updateTime = LocalDateTime.now(); + } + + @Getter + public static class ConversationMessage { + private final String content; + private final String role; // "user" or "assistant" + private final LocalDateTime timestamp; + + public ConversationMessage(String content, String role) { + this.content = content; + this.role = role; + this.timestamp = LocalDateTime.now(); + } + } +} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/ai/ConversationOperation.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/ai/ConversationOperation.java new file mode 100644 index 0000000..bed5b31 --- /dev/null +++ b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/ai/ConversationOperation.java @@ -0,0 +1,191 @@ +package io.github.timemachinelab.core.session.infrastructure.ai; + +import io.github.timemachinelab.sfchain.annotation.AIOp; +import io.github.timemachinelab.sfchain.core.BaseAIOperation; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@AIOp(value = "CONVERSATION_OP", + description = "基于QATree的智能会话操作,支持上下文理解和结构化对话", + defaultModel = "gpt-4-turbo", + requireJsonOutput = true, + supportThinking = true, + defaultMaxTokens = 2048, + defaultTemperature = 0.8) +@Component +@Slf4j +public class ConversationOperation extends BaseAIOperation { + + @Override + public String buildPrompt(ConversationRequest input) { + StringBuilder promptBuilder = new StringBuilder(); + + promptBuilder.append(""" + 你是一名智能AI助手,专门负责与用户进行结构化对话。 + 你需要根据用户的输入和对话历史,提供有价值的回复。 + + ## 对话上下文 + 会话ID: %s + 当前节点ID: %s + """.formatted(input.getSessionId(), input.getCurrentNodeId())); + + // 添加对话历史 + if (input.getConversationHistory() != null && !input.getConversationHistory().isEmpty()) { + promptBuilder.append("\n## 对话历史\n"); + for (int i = 0; i < input.getConversationHistory().size(); i++) { + ConversationHistory history = input.getConversationHistory().get(i); + promptBuilder.append(String.format("%d. %s: %s\n", + i + 1, history.getRole(), history.getContent())); + } + } + + promptBuilder.append(""" + + ## 当前用户输入 + %s + + ## 回复要求 + 1. 根据用户输入和对话历史,提供有针对性的回复 + 2. 如果用户提出需求(如生成系统、解决问题等),可以提供选择题让用户进一步明确需求 + 3. 保持回复简洁、专业、有帮助 + 4. 必须返回以下JSON格式: + + ```json + { + "answer": "你的回复内容", + "responseType": "TEXT", + "options": [], + "nextQuestionSuggestion": "建议的下一个问题(可选)", + "metadata": { + "confidence": 0.95, + "category": "general" + } + } + ``` + + ## 响应类型说明 + - TEXT: 普通文本回复 + - SELECTION: 选择题回复(需要填充options数组) + - FORM: 表单回复(需要用户填写信息) + + ## 注意事项 + - 确保JSON格式正确且有效 + - 如果是选择题,options数组不能为空 + - confidence值范围0-1,表示回答的置信度 + - category可以是:general, technical, creative, problem_solving等 + """.formatted(input.getUserMessage())); + + return promptBuilder.toString(); + } + + @Override + protected ConversationResponse parseResult(String jsonContent, ConversationRequest input) { + try { + ConversationResponse response = objectMapper.readValue(jsonContent, ConversationResponse.class); + + // 验证响应完整性 + if (response.getAnswer() == null || response.getAnswer().trim().isEmpty()) { + log.warn("AI回复内容为空,使用默认回复"); + response.setAnswer("抱歉,我需要更多信息来帮助您。"); + } + + if (response.getResponseType() == null) { + response.setResponseType(ResponseType.TEXT); + } + + // 如果是选择题但没有选项,转为普通文本 + if (response.getResponseType() == ResponseType.SELECTION && + (response.getOptions() == null || response.getOptions().length == 0)) { + log.warn("选择题类型但无选项,转为文本类型"); + response.setResponseType(ResponseType.TEXT); + } + + return response; + + } catch (Exception e) { + log.warn("解析AI会话结果失败,使用默认响应: {}", e.getMessage()); + + ConversationResponse fallbackResponse = new ConversationResponse(); + fallbackResponse.setAnswer("我理解了您的需求,让我为您提供更详细的帮助。"); + fallbackResponse.setResponseType(ResponseType.TEXT); + fallbackResponse.setOptions(new String[0]); + + ConversationMetadata metadata = new ConversationMetadata(); + metadata.setConfidence(0.5); + metadata.setCategory("general"); + fallbackResponse.setMetadata(metadata); + + return fallbackResponse; + } + } + + @Data + public static class ConversationRequest { + private String sessionId; + private String currentNodeId; + private String userMessage; + private java.util.List conversationHistory; + private java.util.Map context; + + public ConversationRequest() { + this.conversationHistory = new java.util.ArrayList<>(); + this.context = new java.util.HashMap<>(); + } + + public ConversationRequest(String sessionId, String currentNodeId, String userMessage) { + this(); + this.sessionId = sessionId; + this.currentNodeId = currentNodeId; + this.userMessage = userMessage; + } + } + + @Data + public static class ConversationResponse { + private String answer; + private ResponseType responseType; + private String[] options; + private String nextQuestionSuggestion; + private ConversationMetadata metadata; + + public ConversationResponse() { + this.options = new String[0]; + this.metadata = new ConversationMetadata(); + } + } + + @Data + public static class ConversationHistory { + private String role; // "user" or "assistant" + private String content; + private String nodeId; + private Long timestamp; + + public ConversationHistory() {} + + public ConversationHistory(String role, String content, String nodeId) { + this.role = role; + this.content = content; + this.nodeId = nodeId; + this.timestamp = System.currentTimeMillis(); + } + } + + @Data + public static class ConversationMetadata { + private Double confidence; + private String category; + private java.util.Map additionalInfo; + + public ConversationMetadata() { + this.additionalInfo = new java.util.HashMap<>(); + } + } + + public enum ResponseType { + TEXT, + SELECTION, + FORM + } +} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/web/ConversationController.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/web/ConversationController.java new file mode 100644 index 0000000..a010ff3 --- /dev/null +++ b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/web/ConversationController.java @@ -0,0 +1,78 @@ +package io.github.timemachinelab.core.session.infrastructure.web; + +import io.github.timemachinelab.core.session.application.ConversationService; +import io.github.timemachinelab.core.session.domain.entity.ConversationSession; +import io.github.timemachinelab.core.session.infrastructure.web.dto.MessageRequest; + +import io.github.timemachinelab.core.session.infrastructure.web.dto.MessageResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.Map; + +@RestController +@RequestMapping("/api/conversation") +@RequiredArgsConstructor +public class ConversationController { + + private final ConversationService conversationService; + private final Map sseEmitters = new ConcurrentHashMap<>(); + + @PostMapping("/start") + public ConversationSession startConversation(@RequestParam String userId) { + return conversationService.createSession(userId); + } + + @GetMapping(value = "/sse/{sessionId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter streamConversation(@PathVariable String sessionId) { + SseEmitter emitter = new SseEmitter(Long.MAX_VALUE); + sseEmitters.put(sessionId, emitter); + + emitter.onCompletion(() -> sseEmitters.remove(sessionId)); + emitter.onTimeout(() -> sseEmitters.remove(sessionId)); + emitter.onError((ex) -> sseEmitters.remove(sessionId)); + + return emitter; + } + + @PostMapping("/message") + public void addMessage(@RequestBody MessageRequest request) { + // 核心业务流程:用户消息 -> AI服务 -> SSE流式返回 + conversationService.processUserMessage( + request.getSessionId(), + request.getContent(), + response -> sendSseMessage(request.getSessionId(), response) + ); +// switch (request.getType()) { +// case USER_TEXT: +// +// +// case USER_SELECTION: +// conversationService.handleUserSelection(request.getSessionId(), request.getQuestionId(), request.getSelectedOption()); +// sendSseMessage(request.getSessionId(), MessageResponse.userAnswer(request.getQuestionId(), "选择了选项: " + request.getSelectedOption())); +// break; +// } + } + + @GetMapping("/session/{sessionId}") + public ConversationSession getSession(@PathVariable String sessionId) { + return conversationService.getSession(sessionId); + } + + private void sendSseMessage(String sessionId, MessageResponse response) { + SseEmitter emitter = sseEmitters.get(sessionId); + if (emitter != null) { + try { + emitter.send(SseEmitter.event() + .name("message") + .data(response)); + } catch (IOException e) { + sseEmitters.remove(sessionId); + } + } + } +} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/web/dto/MessageRequest.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/web/dto/MessageRequest.java new file mode 100644 index 0000000..064d7c5 --- /dev/null +++ b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/web/dto/MessageRequest.java @@ -0,0 +1,16 @@ +package io.github.timemachinelab.core.session.infrastructure.web.dto; + +import lombok.Data; + +@Data +public class MessageRequest { + private String sessionId; + private String content; + + public enum MessageType { + USER_TEXT, + USER_SELECTION, + AI_QUESTION, + AI_SELECTION_QUESTION + } +} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/web/dto/MessageResponse.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/web/dto/MessageResponse.java new file mode 100644 index 0000000..b336773 --- /dev/null +++ b/prompto-lab-app/src/main/java/io/github/timemachinelab/core/session/infrastructure/web/dto/MessageResponse.java @@ -0,0 +1,33 @@ +package io.github.timemachinelab.core.session.infrastructure.web.dto; + +import lombok.Data; +import lombok.AllArgsConstructor; + +@Data +@AllArgsConstructor +public class MessageResponse { + private String nodeId; + private String content; + private MessageType type; + private String[] options; + private Long timestamp; + + public enum MessageType { + USER_ANSWER, + AI_QUESTION, + AI_ANSWER, + SYSTEM_INFO + } + + public static MessageResponse userAnswer(String nodeId, String content) { + return new MessageResponse(nodeId, content, MessageType.USER_ANSWER, null, System.currentTimeMillis()); + } + + public static MessageResponse aiQuestion(String nodeId, String content) { + return new MessageResponse(nodeId, content, MessageType.AI_QUESTION, null, System.currentTimeMillis()); + } + + public static MessageResponse aiAnswer(String nodeId, String content) { + return new MessageResponse(nodeId, content, MessageType.AI_ANSWER, null, System.currentTimeMillis()); + } +} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/demo/SSEDemoController.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/demo/SSEDemoController.java new file mode 100644 index 0000000..df237fd --- /dev/null +++ b/prompto-lab-app/src/main/java/io/github/timemachinelab/demo/SSEDemoController.java @@ -0,0 +1,170 @@ +package io.github.timemachinelab.demo; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.io.IOException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.Map; + +@RestController +@RequestMapping("/api/demo") +@Slf4j +public class SSEDemoController { + + private final Map emitters = new ConcurrentHashMap<>(); + private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); + + /** + * 建立SSE连接 + */ + @GetMapping(value = "/sse/{clientId}", produces = MediaType.TEXT_EVENT_STREAM_VALUE) + public SseEmitter connect(@PathVariable String clientId) { + log.info("客户端连接SSE: {}", clientId); + + SseEmitter emitter = new SseEmitter(Long.MAX_VALUE); + emitters.put(clientId, emitter); + + // 连接建立时发送欢迎消息 + try { + emitter.send(SseEmitter.event() + .name("connected") + .data("SSE连接已建立,客户端ID: " + clientId)); + } catch (IOException e) { + log.error("发送欢迎消息失败: {}", e.getMessage()); + } + + // 设置连接事件处理 + emitter.onCompletion(() -> { + log.info("SSE连接完成: {}", clientId); + emitters.remove(clientId); + }); + + emitter.onTimeout(() -> { + log.info("SSE连接超时: {}", clientId); + emitters.remove(clientId); + }); + + emitter.onError((ex) -> { + log.error("SSE连接错误: {} - {}", clientId, ex.getMessage()); + emitters.remove(clientId); + }); + + return emitter; + } + + /** + * 发送消息到指定客户端 + */ + @PostMapping("/send/{clientId}") + public String sendMessage(@PathVariable String clientId, @RequestParam String message) { + SseEmitter emitter = emitters.get(clientId); + + if (emitter == null) { + return "客户端未连接: " + clientId; + } + + try { + emitter.send(SseEmitter.event() + .name("message") + .data(message)); + + log.info("消息已发送到客户端 {}: {}", clientId, message); + return "消息发送成功"; + + } catch (IOException e) { + log.error("发送消息失败: {}", e.getMessage()); + emitters.remove(clientId); + return "发送失败: " + e.getMessage(); + } + } + + /** + * 广播消息到所有连接的客户端 + */ + @PostMapping("/broadcast") + public String broadcast(@RequestParam String message) { + int successCount = 0; + int failCount = 0; + + for (Map.Entry entry : emitters.entrySet()) { + try { + entry.getValue().send(SseEmitter.event() + .name("broadcast") + .data(message)); + successCount++; + } catch (IOException e) { + log.error("广播消息失败,客户端: {} - {}", entry.getKey(), e.getMessage()); + emitters.remove(entry.getKey()); + failCount++; + } + } + + return String.format("广播完成 - 成功: %d, 失败: %d", successCount, failCount); + } + + /** + * 真实流式数据传输 - SSE本身就是流式的 + * 这里演示连续推送数据流的场景 + */ + @PostMapping("/stream/{clientId}") + public String startStream(@PathVariable String clientId) { + SseEmitter emitter = emitters.get(clientId); + + if (emitter == null) { + return "客户端未连接: " + clientId; + } + + // 真实流式传输:连续推送数据,每秒一条,共10条 + // 这就是SSE的本质 - 服务器主动推送数据流 + scheduler.schedule(() -> { + for (int i = 1; i <= 10; i++) { + final int count = i; + scheduler.schedule(() -> { + try { + // 直接通过SSE流式推送数据 + emitter.send(SseEmitter.event() + .name("stream") + .data("实时数据流 #" + count + " - 时间戳: " + System.currentTimeMillis())); + + // 最后一条数据后发送完成通知 + if (count == 10) { + scheduler.schedule(() -> { + try { + emitter.send(SseEmitter.event() + .name("stream_complete") + .data("数据流传输完成")); + } catch (IOException e) { + log.error("发送完成通知失败: {}", e.getMessage()); + } + }, 1, TimeUnit.SECONDS); + } + + } catch (IOException e) { + log.error("流式数据推送失败: {}", e.getMessage()); + emitters.remove(clientId); + } + }, count, TimeUnit.SECONDS); + } + }, 0, TimeUnit.SECONDS); + + return "SSE数据流已开始推送"; + } + + /** + * 获取当前连接状态 + */ + @GetMapping("/status") + public Map getStatus() { + Map status = new ConcurrentHashMap<>(); + status.put("connectedClients", emitters.keySet()); + status.put("totalConnections", emitters.size()); + status.put("timestamp", System.currentTimeMillis()); + return status; + } +} \ No newline at end of file diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AIModelController.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AIModelController.java index d1adf06..47b7610 100644 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AIModelController.java +++ b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AIModelController.java @@ -31,7 +31,6 @@ @RestController @RequestMapping("/sf/api/models") @RequiredArgsConstructor -@CrossOrigin(origins = "*") public class AIModelController { private final PersistenceManager persistenceManager; diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AIOperationController.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AIOperationController.java index b5ac64e..5aa7aa1 100644 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AIOperationController.java +++ b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AIOperationController.java @@ -25,7 +25,6 @@ @RestController @RequestMapping("/sf/api/operations") @RequiredArgsConstructor -@CrossOrigin(origins = "*") public class AIOperationController { private final PersistenceManager persistenceManager; diff --git a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AISystemController.java b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AISystemController.java index 5144be6..e443989 100644 --- a/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AISystemController.java +++ b/prompto-lab-app/src/main/java/io/github/timemachinelab/sfchain/controller/AISystemController.java @@ -23,7 +23,6 @@ @RestController @RequestMapping("/sf/api/system") @RequiredArgsConstructor -@CrossOrigin(origins = "*") public class AISystemController { private final PersistenceManager persistenceManager; diff --git a/prompto-lab-app/src/main/resources/static/sse-demo.html b/prompto-lab-app/src/main/resources/static/sse-demo.html new file mode 100644 index 0000000..0ee13ba --- /dev/null +++ b/prompto-lab-app/src/main/resources/static/sse-demo.html @@ -0,0 +1,340 @@ + + + + + + SSE流传输Demo + + + +
+

🚀 SSE流传输Demo

+ + +
+

📡 连接状态

+
未连接
+
+ + + +
+
+ + +
+

💬 消息操作

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+

📨 消息记录

+ +
+
+
+ + + + \ No newline at end of file diff --git a/prompto-lab-ui/src/components/AIChatPage.vue b/prompto-lab-ui/src/components/AIChatPage.vue index f036f3d..6275380 100644 --- a/prompto-lab-ui/src/components/AIChatPage.vue +++ b/prompto-lab-ui/src/components/AIChatPage.vue @@ -74,7 +74,10 @@