Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
737432cc49
@ -350,14 +350,17 @@ public class GenerationController {
|
||||
emitter.onCompletion(() -> generationFuture.cancel(true));
|
||||
emitter.onTimeout(() -> generationFuture.cancel(true));
|
||||
|
||||
// 等待生成结果,每 10s 发送一次心跳,防止 nginx/移动网络因 30s 空闲超时而断开
|
||||
// 等待生成结果,每 5s 发送一次心跳,防止 nginx/移动网络因空闲超时而断开
|
||||
while (!generationFuture.isDone()) {
|
||||
try {
|
||||
generationFuture.get(10, TimeUnit.SECONDS);
|
||||
generationFuture.get(5, TimeUnit.SECONDS);
|
||||
break;
|
||||
} catch (TimeoutException e) {
|
||||
try {
|
||||
emitter.send(SseEmitter.event().comment("keep-alive"));
|
||||
Map<String, Object> ping = new HashMap<>();
|
||||
ping.put("type", "ping");
|
||||
ping.put("time", System.currentTimeMillis());
|
||||
emitter.send(SseEmitter.event().name("ping").data(ping));
|
||||
} catch (IOException ioe) {
|
||||
log.warn("SSE 心跳发送失败,连接已断开");
|
||||
generationFuture.cancel(true);
|
||||
|
||||
@ -35,6 +35,7 @@ export default function ScriptRecommendStream({ userInfo, customerId: propCustom
|
||||
const [utterances, setUtterances] = useState<UtteranceItem[]>([])
|
||||
const [error, setError] = useState('')
|
||||
const sseRef = useRef<EventSource | null>(null)
|
||||
const retryCountRef = useRef(0)
|
||||
|
||||
const customerId = propCustomerId || localStorage.getItem('current_customer_id') || ''
|
||||
const staffId = userInfo?.userId || ''
|
||||
@ -103,12 +104,16 @@ export default function ScriptRecommendStream({ userInfo, customerId: propCustom
|
||||
|
||||
setGenerating(true)
|
||||
setLoading(false)
|
||||
setError('')
|
||||
retryCountRef.current = 0
|
||||
|
||||
const attempt = () => {
|
||||
try {
|
||||
// 单聊才获取画像标签
|
||||
if (!isGroupChat && customerId) {
|
||||
const profileRes = await fetch(`/api/v1/intent/profile/${encodeURIComponent(customerId)}?corpId=${corpId}`)
|
||||
const profileData = await profileRes.json()
|
||||
// 仅首次连接时获取画像标签,重试时不重复调用
|
||||
if (retryCountRef.current === 0 && !isGroupChat && customerId) {
|
||||
fetch(`/api/v1/intent/profile/${encodeURIComponent(customerId)}?corpId=${corpId}`)
|
||||
.then(res => res.json())
|
||||
.then(profileData => {
|
||||
if (profileData.code === 0 && profileData.data) {
|
||||
const p = profileData.data
|
||||
const tags: string[] = []
|
||||
@ -120,6 +125,8 @@ export default function ScriptRecommendStream({ userInfo, customerId: propCustom
|
||||
if (p.decisionStage) tags.push(p.decisionStage)
|
||||
setProfileTags(tags)
|
||||
}
|
||||
})
|
||||
.catch(e => console.error('获取学员画像失败:', e))
|
||||
}
|
||||
|
||||
// 构建 SSE URL:群聊传 roomid,单聊传 fromUser+toUser+staffName
|
||||
@ -135,7 +142,7 @@ export default function ScriptRecommendStream({ userInfo, customerId: propCustom
|
||||
}
|
||||
|
||||
const url = `/api/v1/generation/utterance-stream?${params.toString()}`
|
||||
console.log('SSE connect:', url)
|
||||
console.log('SSE connect (attempt', retryCountRef.current + 1, '):', url)
|
||||
const es = new EventSource(url)
|
||||
sseRef.current = es
|
||||
|
||||
@ -148,6 +155,10 @@ export default function ScriptRecommendStream({ userInfo, customerId: propCustom
|
||||
} catch (e) { console.error('SSE start error:', e) }
|
||||
})
|
||||
|
||||
es.addEventListener('ping', (_event: MessageEvent) => {
|
||||
// 服务端心跳保活,无需处理
|
||||
})
|
||||
|
||||
es.addEventListener('item-start', (event: MessageEvent) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
@ -187,19 +198,30 @@ export default function ScriptRecommendStream({ userInfo, customerId: propCustom
|
||||
if (data.type === 'done') {
|
||||
setGenerating(false)
|
||||
es.close()
|
||||
retryCountRef.current = 0
|
||||
}
|
||||
} catch (e) { console.error('SSE done error:', e) }
|
||||
})
|
||||
|
||||
es.addEventListener('error', (event: MessageEvent) => {
|
||||
es.close()
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
setError(data.message || '生成失败')
|
||||
} catch (e) {
|
||||
setError('连接中断')
|
||||
}
|
||||
setGenerating(false)
|
||||
es.close()
|
||||
retryCountRef.current = 0
|
||||
} catch (e) {
|
||||
// 连接类错误自动重试一次,避免网络抖动导致用户看到"连接中断"
|
||||
if (retryCountRef.current < 2) {
|
||||
retryCountRef.current += 1
|
||||
console.warn(`SSE 连接中断,第 ${retryCountRef.current} 次重试...`)
|
||||
setTimeout(attempt, 1000)
|
||||
} else {
|
||||
setError('连接中断')
|
||||
setGenerating(false)
|
||||
retryCountRef.current = 0
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
} catch (e) {
|
||||
@ -207,6 +229,9 @@ export default function ScriptRecommendStream({ userInfo, customerId: propCustom
|
||||
setError('网络错误,请重试')
|
||||
setGenerating(false)
|
||||
}
|
||||
}
|
||||
|
||||
attempt()
|
||||
}, [canStartGeneration, isGroupChat, customerId, staffId, chatId, corpId])
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user