|
|
|
@ -80,13 +80,85 @@ export const callDeepSeekAPI = async (messages, options = {}) => {
|
|
|
|
|
return content
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 本地服务调用(调试版:打印详细日志,方便逐步排查)
|
|
|
|
|
export const callLocalChatAPI = async (prompt) => {
|
|
|
|
|
const base = '/api/chat/stream'
|
|
|
|
|
const url = `${base}?prompt=${encodeURIComponent(prompt)}`
|
|
|
|
|
|
|
|
|
|
// DeepSeek 流式调用
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 逐步调试日志
|
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
console.debug('[callLocalChatAPI] url:', url)
|
|
|
|
|
try {
|
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
console.debug('[callLocalChatAPI] fetch start')
|
|
|
|
|
const resp = await fetch(url, { method: 'GET' })
|
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
console.debug('[callLocalChatAPI] fetch done', {
|
|
|
|
|
ok: resp.ok,
|
|
|
|
|
status: resp.status,
|
|
|
|
|
statusText: resp.statusText,
|
|
|
|
|
headers: Object.fromEntries(resp.headers.entries())
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (!resp.ok) {
|
|
|
|
|
const txt = await resp.text().catch(() => null)
|
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
console.error('[callLocalChatAPI] non-OK response body:', txt)
|
|
|
|
|
const msg = txt || resp.statusText || `HTTP_${resp.status}`
|
|
|
|
|
const err = new Error(msg)
|
|
|
|
|
err.status = resp.status
|
|
|
|
|
throw err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 最简单:一次性读取完整响应文本(非流式),然后从 SSE 格式中提取第一个 event:message 的 data
|
|
|
|
|
const raw = await resp.text().catch(e => {
|
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
console.error('[callLocalChatAPI] text read error:', e)
|
|
|
|
|
return ''
|
|
|
|
|
})
|
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
console.debug('[callLocalChatAPI] raw length:', raw?.length || 0, 'preview:', (raw || '').slice(0, 500))
|
|
|
|
|
if (!raw) throw new Error('EMPTY_RESPONSE')
|
|
|
|
|
|
|
|
|
|
// 按空行分块,优先找到 event:message 的 data
|
|
|
|
|
const blocks = raw.split(/\r?\n\r?\n/).map(b => b.trim()).filter(Boolean)
|
|
|
|
|
for (const block of blocks) {
|
|
|
|
|
const lines = block.split(/\r?\n/).map(l => l.trim())
|
|
|
|
|
let eventType = ''
|
|
|
|
|
const dataLines = []
|
|
|
|
|
for (const line of lines) {
|
|
|
|
|
if (/^event\s*:/i.test(line)) {
|
|
|
|
|
eventType = line.split(':').slice(1).join(':').trim()
|
|
|
|
|
} else if (/^data\s*:/i.test(line)) {
|
|
|
|
|
dataLines.push(line.split(':').slice(1).join(':'))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (eventType === 'message' && dataLines.length > 0) {
|
|
|
|
|
const message = dataLines.join('\n').trim()
|
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
console.debug('[callLocalChatAPI] parsed message data preview:', message.slice(0, 300))
|
|
|
|
|
if (!message) throw new Error('EMPTY_RESPONSE')
|
|
|
|
|
return message
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 回退:取第一个 data: 行的内容
|
|
|
|
|
const firstDataMatch = raw.match(/^[ \t]*data\s*:(.*)$/im)
|
|
|
|
|
if (firstDataMatch && firstDataMatch[1]) {
|
|
|
|
|
const fallback = firstDataMatch[1].trim()
|
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
console.debug('[callLocalChatAPI] fallback data:', fallback.slice(0, 300))
|
|
|
|
|
return fallback
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 最后回退:返回原始文本
|
|
|
|
|
return raw
|
|
|
|
|
} catch (err) {
|
|
|
|
|
// eslint-disable-next-line no-console
|
|
|
|
|
console.error('[callLocalChatAPI] ERROR:', err)
|
|
|
|
|
throw err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 错误处理函数
|
|
|
|
|
export const handleAPIError = (error) => {
|
|
|
|
|