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.onCompletion(() -> generationFuture.cancel(true));
|
||||||
emitter.onTimeout(() -> generationFuture.cancel(true));
|
emitter.onTimeout(() -> generationFuture.cancel(true));
|
||||||
|
|
||||||
// 等待生成结果,每 10s 发送一次心跳,防止 nginx/移动网络因 30s 空闲超时而断开
|
// 等待生成结果,每 5s 发送一次心跳,防止 nginx/移动网络因空闲超时而断开
|
||||||
while (!generationFuture.isDone()) {
|
while (!generationFuture.isDone()) {
|
||||||
try {
|
try {
|
||||||
generationFuture.get(10, TimeUnit.SECONDS);
|
generationFuture.get(5, TimeUnit.SECONDS);
|
||||||
break;
|
break;
|
||||||
} catch (TimeoutException e) {
|
} catch (TimeoutException e) {
|
||||||
try {
|
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) {
|
} catch (IOException ioe) {
|
||||||
log.warn("SSE 心跳发送失败,连接已断开");
|
log.warn("SSE 心跳发送失败,连接已断开");
|
||||||
generationFuture.cancel(true);
|
generationFuture.cancel(true);
|
||||||
|
|||||||
@ -35,6 +35,7 @@ export default function ScriptRecommendStream({ userInfo, customerId: propCustom
|
|||||||
const [utterances, setUtterances] = useState<UtteranceItem[]>([])
|
const [utterances, setUtterances] = useState<UtteranceItem[]>([])
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
const sseRef = useRef<EventSource | null>(null)
|
const sseRef = useRef<EventSource | null>(null)
|
||||||
|
const retryCountRef = useRef(0)
|
||||||
|
|
||||||
const customerId = propCustomerId || localStorage.getItem('current_customer_id') || ''
|
const customerId = propCustomerId || localStorage.getItem('current_customer_id') || ''
|
||||||
const staffId = userInfo?.userId || ''
|
const staffId = userInfo?.userId || ''
|
||||||
@ -103,12 +104,16 @@ export default function ScriptRecommendStream({ userInfo, customerId: propCustom
|
|||||||
|
|
||||||
setGenerating(true)
|
setGenerating(true)
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
setError('')
|
||||||
|
retryCountRef.current = 0
|
||||||
|
|
||||||
|
const attempt = () => {
|
||||||
try {
|
try {
|
||||||
// 单聊才获取画像标签
|
// 仅首次连接时获取画像标签,重试时不重复调用
|
||||||
if (!isGroupChat && customerId) {
|
if (retryCountRef.current === 0 && !isGroupChat && customerId) {
|
||||||
const profileRes = await fetch(`/api/v1/intent/profile/${encodeURIComponent(customerId)}?corpId=${corpId}`)
|
fetch(`/api/v1/intent/profile/${encodeURIComponent(customerId)}?corpId=${corpId}`)
|
||||||
const profileData = await profileRes.json()
|
.then(res => res.json())
|
||||||
|
.then(profileData => {
|
||||||
if (profileData.code === 0 && profileData.data) {
|
if (profileData.code === 0 && profileData.data) {
|
||||||
const p = profileData.data
|
const p = profileData.data
|
||||||
const tags: string[] = []
|
const tags: string[] = []
|
||||||
@ -120,6 +125,8 @@ export default function ScriptRecommendStream({ userInfo, customerId: propCustom
|
|||||||
if (p.decisionStage) tags.push(p.decisionStage)
|
if (p.decisionStage) tags.push(p.decisionStage)
|
||||||
setProfileTags(tags)
|
setProfileTags(tags)
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
.catch(e => console.error('获取学员画像失败:', e))
|
||||||
}
|
}
|
||||||
|
|
||||||
// 构建 SSE URL:群聊传 roomid,单聊传 fromUser+toUser+staffName
|
// 构建 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()}`
|
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)
|
const es = new EventSource(url)
|
||||||
sseRef.current = es
|
sseRef.current = es
|
||||||
|
|
||||||
@ -148,6 +155,10 @@ export default function ScriptRecommendStream({ userInfo, customerId: propCustom
|
|||||||
} catch (e) { console.error('SSE start error:', e) }
|
} catch (e) { console.error('SSE start error:', e) }
|
||||||
})
|
})
|
||||||
|
|
||||||
|
es.addEventListener('ping', (_event: MessageEvent) => {
|
||||||
|
// 服务端心跳保活,无需处理
|
||||||
|
})
|
||||||
|
|
||||||
es.addEventListener('item-start', (event: MessageEvent) => {
|
es.addEventListener('item-start', (event: MessageEvent) => {
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data)
|
const data = JSON.parse(event.data)
|
||||||
@ -187,19 +198,30 @@ export default function ScriptRecommendStream({ userInfo, customerId: propCustom
|
|||||||
if (data.type === 'done') {
|
if (data.type === 'done') {
|
||||||
setGenerating(false)
|
setGenerating(false)
|
||||||
es.close()
|
es.close()
|
||||||
|
retryCountRef.current = 0
|
||||||
}
|
}
|
||||||
} catch (e) { console.error('SSE done error:', e) }
|
} catch (e) { console.error('SSE done error:', e) }
|
||||||
})
|
})
|
||||||
|
|
||||||
es.addEventListener('error', (event: MessageEvent) => {
|
es.addEventListener('error', (event: MessageEvent) => {
|
||||||
|
es.close()
|
||||||
try {
|
try {
|
||||||
const data = JSON.parse(event.data)
|
const data = JSON.parse(event.data)
|
||||||
setError(data.message || '生成失败')
|
setError(data.message || '生成失败')
|
||||||
} catch (e) {
|
|
||||||
setError('连接中断')
|
|
||||||
}
|
|
||||||
setGenerating(false)
|
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) {
|
} catch (e) {
|
||||||
@ -207,6 +229,9 @@ export default function ScriptRecommendStream({ userInfo, customerId: propCustom
|
|||||||
setError('网络错误,请重试')
|
setError('网络错误,请重试')
|
||||||
setGenerating(false)
|
setGenerating(false)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attempt()
|
||||||
}, [canStartGeneration, isGroupChat, customerId, staffId, chatId, corpId])
|
}, [canStartGeneration, isGroupChat, customerId, staffId, chatId, corpId])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user