/* eslint-disable */ // @ts-ignore import type { CustomRequestOptions } from '@/http/types'; type AnyRecord = Record; type StreamCallbacks = { onToken?: (text: string) => void; onDone?: () => void; onError?: (err: unknown) => void; onStart?: (payload: Record) => void; onTool?: (payload: Record) => void; onChunk?: (payload: Record) => void; onEnd?: (payload: Record) => void; }; export type AiChatStreamQuery = { content: string; conversationId?: string; }; function decodeChunk(data: ArrayBuffer | string): string { if (typeof data === 'string') return data; try { return new TextDecoder('utf-8').decode(data); } catch (e) { const arr = new Uint8Array(data); let str = ''; for (let i = 0; i < arr.length; i += 1) str += String.fromCharCode(arr[i]); return decodeURIComponent(escape(str)); } } function parseSsePayload(raw: string): string { const line = raw.trim(); if (!line) return ''; if (!line.startsWith('data:')) return line; const payload = line.slice(5).trim(); if (!payload || payload === '[DONE]') return ''; try { const json = JSON.parse(payload); return ( json?.content ?? json?.delta?.content ?? json?.message ?? json?.data?.content ?? '' ); } catch (e) { return payload; } } function parseSseEventBlock(block: string): { event: string; payload: Record | null } | null { const lines = block .split('\n') .map((line) => line.trim()) .filter(Boolean); if (!lines.length) return null; let eventName = 'message'; const dataLines: string[] = []; lines.forEach((line) => { if (line.startsWith('event:')) { eventName = line.slice(6).trim() || 'message'; } else if (line.startsWith('data:')) { dataLines.push(line.slice(5).trim()); } }); if (!dataLines.length) return { event: eventName, payload: null }; const raw = dataLines.join('\n'); try { return { event: eventName, payload: JSON.parse(raw) }; } catch (e) { return { event: eventName, payload: { text: raw } }; } } function dispatchSseEvent( parsed: { event: string; payload: Record | null } | null, callbacks?: StreamCallbacks, ) { if (!parsed) return; const payload = parsed.payload || {}; switch (parsed.event) { case 'start': callbacks?.onStart?.(payload); break; case 'tool': callbacks?.onTool?.(payload); break; case 'chunk': callbacks?.onChunk?.(payload); if (!callbacks?.onChunk && typeof payload.text === 'string' && payload.text) { callbacks?.onToken?.(payload.text); } break; case 'end': callbacks?.onEnd?.(payload); callbacks?.onDone?.(); break; default: // 兼容未知 event 或普通 message if (typeof payload.text === 'string' && payload.text) { callbacks?.onToken?.(payload.text); } break; } } /** * AI 流式对话 GET /app/ai/chat/stream * 注意:不同端对 chunk 支持程度不同,不支持时会在 success 中返回完整文本。 */ export function appAiChatStreamGet({ query, callbacks, options, }: { query: AiChatStreamQuery; callbacks?: StreamCallbacks; options?: CustomRequestOptions; }) { let buffer = ''; let gotChunk = false; const task = uni.request({ url: '/app/ai/chat/stream', method: 'GET', data: query, enableChunked: true as any, responseType: 'text', timeout: options?.timeout ?? 60000, header: { ...(options?.header || {}), }, success(res) { // 部分端不支持 chunk 回调,这里兜底按整段文本处理 if (gotChunk) { return; } const raw = typeof res.data === 'string' ? res.data : JSON.stringify(res.data || ''); const normalized = raw.replace(/\r\n/g, '\n'); const blocks = normalized.split('\n\n').filter(Boolean); if (!blocks.length) { if (raw) callbacks?.onToken?.(raw); callbacks?.onDone?.(); return; } blocks.forEach((block) => { dispatchSseEvent(parseSseEventBlock(block), callbacks); }); }, fail(err) { callbacks?.onError?.(err); }, }); const taskAny = task as any; if (taskAny && typeof taskAny.onChunkReceived === 'function') { taskAny.onChunkReceived((chunk: { data: ArrayBuffer | string }) => { gotChunk = true; buffer += decodeChunk(chunk.data).replace(/\r\n/g, '\n'); const blocks = buffer.split('\n\n'); buffer = blocks.pop() || ''; blocks.forEach((block) => { dispatchSseEvent(parseSseEventBlock(block), callbacks); }); }); } return task; } // 兼容旧调用名,避免其他页面引用报错 export const appAiChatStreamPost = appAiChatStreamGet;