Pārlūkot izejas kodu

Release v0.2.2802

qdy 3 nedēļas atpakaļ
revīzija
c536bc0923

+ 23
- 4
internal/routes/log_stream_routes.go Parādīt failu

@@ -1,6 +1,7 @@
1 1
 package routes
2 2
 
3 3
 import (
4
+	"encoding/json"
4 5
 	"fmt"
5 6
 	"net/http"
6 7
 	"strings"
@@ -68,8 +69,17 @@ func LogStreamHandler(process *opencode.Process, opencodePort int) http.HandlerF
68 69
 					// 客户端断开连接
69 70
 					return
70 71
 				case <-time.After(30 * time.Second):
71
-					// 发送心跳保持连接
72
-					fmt.Fprintf(w, ": heartbeat\n\n")
72
+					// 发送JSON格式的心跳事件保持连接
73
+					heartbeatEvent := map[string]interface{}{
74
+						"payload": map[string]interface{}{
75
+							"type": "server.heartbeat",
76
+							"properties": map[string]interface{}{
77
+								"timestamp": time.Now().Format(time.RFC3339),
78
+							},
79
+						},
80
+					}
81
+					jsonData, _ := json.Marshal(heartbeatEvent)
82
+					fmt.Fprintf(w, "data: %s\n\n", string(jsonData))
73 83
 					flusher.Flush()
74 84
 				}
75 85
 			}
@@ -114,8 +124,17 @@ func LogStreamHandler(process *opencode.Process, opencodePort int) http.HandlerF
114 124
 				// 客户端断开连接
115 125
 				return
116 126
 			case <-time.After(30 * time.Second):
117
-				// 发送心跳保持连接
118
-				fmt.Fprintf(w, ": heartbeat\n\n")
127
+				// 发送JSON格式的心跳事件保持连接
128
+				heartbeatEvent := map[string]interface{}{
129
+					"payload": map[string]interface{}{
130
+						"type": "server.heartbeat",
131
+						"properties": map[string]interface{}{
132
+							"timestamp": time.Now().Format(time.RFC3339),
133
+						},
134
+					},
135
+				}
136
+				jsonData, _ := json.Marshal(heartbeatEvent)
137
+				fmt.Fprintf(w, "data: %s\n\n", string(jsonData))
119 138
 				flusher.Flush()
120 139
 			}
121 140
 		}

+ 28
- 20
internal/routes/prompt_stream_routes.go Parādīt failu

@@ -62,8 +62,8 @@ func StreamPromptHandler(client opencode.OpenCodeClient) http.HandlerFunc {
62 62
 		w.Header().Set("Connection", "keep-alive")
63 63
 		w.Header().Set("Access-Control-Allow-Origin", "*")
64 64
 
65
-		// 创建带超时的上下文
66
-		ctx, cancel := context.WithTimeout(r.Context(), 5*time.Minute)
65
+		// 创建带超时的上下文 - 增加超时时间确保AI有足够时间生成完整响应
66
+		ctx, cancel := context.WithTimeout(r.Context(), 15*time.Minute)
67 67
 		defer cancel()
68 68
 
69 69
 		fmt.Printf("🔍 [StreamPromptHandler] 调用 SendPromptStream, sessionID=%s\n", req.SessionID)
@@ -86,6 +86,14 @@ func StreamPromptHandler(client opencode.OpenCodeClient) http.HandlerFunc {
86 86
 		fmt.Printf("🔍 [StreamPromptHandler] 开始发送流式响应\n")
87 87
 		eventCount := 0
88 88
 
89
+		// 创建心跳定时器,每30秒发送一次心跳保活(SSE注释格式)
90
+		heartbeatTicker := time.NewTicker(30 * time.Second)
91
+		defer heartbeatTicker.Stop()
92
+
93
+		// 发送初始心跳,确保连接立即活跃
94
+		fmt.Fprintf(w, ": heartbeat\n\n")
95
+		flusher.Flush()
96
+
89 97
 		for {
90 98
 			select {
91 99
 			case data, ok := <-ch:
@@ -139,6 +147,11 @@ func StreamPromptHandler(client opencode.OpenCodeClient) http.HandlerFunc {
139 147
 			case <-ctx.Done():
140 148
 				fmt.Printf("🔍 [StreamPromptHandler] 上下文超时\n")
141 149
 				return
150
+			case <-heartbeatTicker.C:
151
+				// 发送心跳保活(SSE注释格式)
152
+				fmt.Printf("🔍 [StreamPromptHandler] 发送心跳保活\n")
153
+				fmt.Fprintf(w, ": heartbeat\n\n")
154
+				flusher.Flush()
142 155
 			case <-r.Context().Done():
143 156
 				fmt.Printf("🔍 [StreamPromptHandler] 客户端断开连接\n")
144 157
 				return
@@ -194,37 +207,32 @@ func removeDuplicateContent(data interface{}) interface{} {
194 207
 		return obj
195 208
 	}
196 209
 
197
-	// 事件过滤策略:减少发送给前端的事件数量
210
+	// 事件过滤策略:保守过滤,保留大多数事件以确保连接稳定
198 211
 	switch typeStr {
199 212
 	case "message.updated":
200
-		// 检查是否有properties字段
213
+		// 对于message.updated事件,移除content字段避免重复,但不过滤事件本身
201 214
 		if properties, ok := obj["properties"].(map[string]interface{}); ok {
202 215
 			if info, ok := properties["info"].(map[string]interface{}); ok {
203 216
 				// 移除content字段,避免重复
204 217
 				delete(info, "content")
205
-
206
-				// 检查是否有completed时间,如果没有则过滤掉(只发送最终状态)
207
-				if timeInfo, ok := info["time"].(map[string]interface{}); ok {
208
-					if timeInfo["completed"] == nil {
209
-						// 没有completed时间,这是中间状态,过滤掉
210
-						return nil
211
-					}
212
-				}
213 218
 			}
214 219
 		}
220
+		// 保留事件,不过滤
221
+		return obj
215 222
 	case "session.status":
216
-		// session.status事件很频繁但前端可能不需要,过滤掉
217
-		return nil
223
+		// 保留状态事件,可能包含重要状态信息
224
+		return obj
218 225
 	case "session.diff":
219
-		// session.diff事件通常为空,过滤掉
220
-		return nil
226
+		// 保留差异事件,避免客户端逻辑中断
227
+		return obj
221 228
 	case "server.heartbeat":
222
-		// 心跳事件,过滤掉
223
-		return nil
229
+		// 保留心跳事件,保持连接活跃 - 非常重要!
230
+		return obj
224 231
 	case "session.idle":
225
-		// 空闲事件,过滤掉
226
-		return nil
232
+		// 保留事件,不过滤
233
+		return obj
227 234
 	}
228 235
 
236
+	// 其他所有事件类型都保留,包括message.part.updated(关键事件)
229 237
 	return obj
230 238
 }

Notiek ielāde…
Atcelt
Saglabāt