feat(ai): 小程序 AI 链路改走 HTTP 流式,WS 细节收敛到 utils/chat-socket
与 web 端同因:生成期间普通 POST 零字节,会被 60s 空闲超时掐成 504。
- 新增 utils/chat-socket.js:地址统一拼 /api/chat/{userId}(网关只放行 /api),
attachHeartbeat 每 25s 发 ping;三个页面不再各抄一份
- chat-service 新增 streamAiRequest:H5 走 fetch + ReadableStream,小程序走
enableChunked + onChunkReceived,并手写 UTF-8 分块解码,避免中文被切半
- 写作 / 图片 / 大纲 / 稿件改走 /stream,结构化结果从末帧 {type:'final'} 取;
流断但有正文时保留已生成部分,不整块覆盖成错误文案
- 对话仍先试 WebSocket,连不上回退 /ai/chat/message/stream,帧处理复用
handleSocketMessage
This commit is contained in:
@@ -158,8 +158,10 @@
|
||||
<script>
|
||||
import {
|
||||
activityOutlineGen,
|
||||
attachHeartbeat,
|
||||
buildWebSocketUrl,
|
||||
getCurrentUser
|
||||
getCurrentUser,
|
||||
streamActivityOutlineGen
|
||||
} from './service'
|
||||
import {
|
||||
isFailedPayload,
|
||||
@@ -195,6 +197,7 @@
|
||||
activeMessageId: '',
|
||||
socketTask: null,
|
||||
socketConnectPromise: null,
|
||||
socketHeartbeatStop: null,
|
||||
streamStarted: false,
|
||||
streamFailed: false,
|
||||
streamResolver: null,
|
||||
@@ -364,6 +367,10 @@
|
||||
})
|
||||
},
|
||||
disconnectWebSocket() {
|
||||
if (this.socketHeartbeatStop) {
|
||||
this.socketHeartbeatStop()
|
||||
this.socketHeartbeatStop = null
|
||||
}
|
||||
this.socketConnectPromise = null
|
||||
const socketTask = this.socketTask
|
||||
this.socketTask = null
|
||||
@@ -471,6 +478,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
socketTask.onOpen(() => {
|
||||
// 握手成功后再挂心跳:连接没建起来时发出去只会失败
|
||||
this.socketHeartbeatStop = attachHeartbeat(socketTask)
|
||||
})
|
||||
socketTask.onMessage((event) => {
|
||||
const raw = String((event && event.data) || '')
|
||||
if (raw === SOCKET_CONNECTED_TEXT) {
|
||||
@@ -539,23 +550,21 @@
|
||||
}
|
||||
this.loading = true
|
||||
this.clearResult()
|
||||
this.resultSummary = '正在建立 WebSocket 连接...'
|
||||
this.resultSummary = '正在提交生成任务...'
|
||||
try {
|
||||
await this.connectWebSocket()
|
||||
this.resultSummary = '连接成功,已提交生成任务,等待流式返回...'
|
||||
// 一律走 HTTP 流式(/stream),不再先连 WebSocket:
|
||||
// 触发生成的那个 POST 在整段生成期间是零字节的,会被链路上的
|
||||
// 60s 空闲超时掐成 504 Gateway Time-out。/stream 每 15s 有后端
|
||||
// 心跳续命,帧内容与 WS 完全一致。
|
||||
const payload = this.buildPayload()
|
||||
this.resultSummary = '已提交生成任务,等待流式返回...'
|
||||
const waitStream = this.createStreamWaiter()
|
||||
const data = await activityOutlineGen(this.buildPayload())
|
||||
this.result = data || null
|
||||
if (!this.streamStarted && data && this.parseOutline(data)) {
|
||||
this.outlineMarkdown = this.parseOutline(data)
|
||||
this.syncEditableOutline()
|
||||
this.resultSummary = this.buildSummary() || '接口直接返回生成结果'
|
||||
this.loading = false
|
||||
this.resolveStream()
|
||||
this.disconnectWebSocket()
|
||||
} else {
|
||||
await waitStream
|
||||
}
|
||||
await streamActivityOutlineGen(payload, {
|
||||
onMessage: (raw) => this.handleSocketMessage(raw)
|
||||
}).promise
|
||||
// 兜底:流结束但没收到 __END__ 时也别把页面卡在加载中
|
||||
this.resolveStream()
|
||||
await waitStream
|
||||
if (this.streamFailed) {
|
||||
// 后端判定工作流失败,正文区已显示失败文案,这里同步提示
|
||||
this.showToast('生成失败,请稍后重试')
|
||||
|
||||
Reference in New Issue
Block a user