fix(web): refactor pico chat flow and fix proxied websocket URLs (#1639)

- move chat controller, state, protocol, history, and websocket logic into a dedicated chat feature module
- improve chat reconnection, session hydration, and send gating based on actual websocket state
- preserve gateway status during transient SSE disconnects and update stop state immediately
- generate wss websocket URLs behind HTTPS proxies and add backend tests for forwarded proto handling
This commit is contained in:
wenjie
2026-03-16 16:25:16 +08:00
committed by GitHub
parent 0c94e6f7b3
commit c513ad22d7
16 changed files with 509 additions and 215 deletions
+8 -4
View File
@@ -67,10 +67,9 @@ export function useGateway() {
}
es.onerror = () => {
// EventSource will auto-reconnect
updateGatewayStore((prev) =>
prev.status === "restarting" ? {} : { status: "unknown" },
)
// EventSource will auto-reconnect. Preserve the last known gateway
// status so transient SSE disconnects do not suppress chat websocket
// reconnects while polling catches up.
}
return () => {
@@ -105,6 +104,11 @@ export function useGateway() {
setLoading(true)
try {
await stopGateway()
updateGatewayStore({
status: "stopped",
canStart: true,
restartRequired: false,
})
} catch (err) {
console.error("Failed to stop gateway:", err)
} finally {
+1 -3
View File
@@ -5,7 +5,7 @@ import {
newChatSession,
sendChatMessage,
switchChatSession,
} from "@/lib/pico-chat-controller"
} from "@/features/chat/controller"
import { chatAtom } from "@/store/chat"
const UNIX_MS_THRESHOLD = 1e12
@@ -33,7 +33,6 @@ function parseTimestamp(dateRaw: number | string | Date) {
return dayjs(dateRaw)
}
// Helper to format message timestamps
export function formatMessageTime(dateRaw: number | string | Date): string {
const date = parseTimestamp(dateRaw)
if (!date.isValid()) {
@@ -48,7 +47,6 @@ export function formatMessageTime(dateRaw: number | string | Date): string {
return date.format("LT")
}
// Cross-day formatting
if (isThisYear) {
return date.format("MMM D LT")
}
-47
View File
@@ -1,47 +0,0 @@
import { useCallback, useEffect, useRef, useState } from "react"
export function useWebSocket(path: string) {
const [message, setMessage] = useState<string>("No messages yet")
const [connected, setConnected] = useState(false)
const wsRef = useRef<WebSocket | null>(null)
const connect = useCallback(() => {
if (wsRef.current) {
wsRef.current.close()
}
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:"
const url = `${protocol}//${window.location.host}${path}`
const socket = new WebSocket(url)
socket.onopen = () => {
setConnected(true)
setMessage("Connected to WebSocket server.")
}
socket.onmessage = (event) => {
setMessage(event.data)
}
socket.onclose = () => {
setConnected(false)
setMessage("WebSocket connection closed.")
}
socket.onerror = (error) => {
setConnected(false)
setMessage("WebSocket error occurred.")
console.error("WebSocket Error:", error)
}
wsRef.current = socket
}, [path])
useEffect(() => {
return () => {
wsRef.current?.close()
}
}, [])
return { message, connected, connect }
}