|
|
@@ -1,5 +1,6 @@
|
|
|
import { defHttp } from '/@/utils/http/axios';
|
|
|
import { getToken } from '/@/utils/auth';
|
|
|
+import { consumeSse } from '../sse';
|
|
|
import type { SseEvent } from './components/chatModal/types';
|
|
|
|
|
|
enum Api {
|
|
|
@@ -190,12 +191,13 @@ export const uploadSkill = (file: File) => {
|
|
|
* @param params.session_id - 会话唯一标识ID,不传参时服务端自动生成全新会话ID
|
|
|
* @param params.file - 上传的PDF文件,可选
|
|
|
* @param onChunk - 流式数据回调函数
|
|
|
- * @returns Promise<{ session_id: string }>
|
|
|
+ * @returns Promise<{ session_id: string; parse_errors: number }>
|
|
|
+ * parse_errors 大于 0 表示这条流里有事件被丢弃(正文可能不完整)
|
|
|
*/
|
|
|
export const unifiedStream = async (
|
|
|
params: { message: string; session_id?: string; file?: File; mode?: string; signal?: AbortSignal },
|
|
|
onChunk: (data: SseEvent) => void
|
|
|
-): Promise<{ session_id: string }> => {
|
|
|
+): Promise<{ session_id: string; parse_errors: number }> => {
|
|
|
try {
|
|
|
const formData = new FormData();
|
|
|
formData.append('message', params.message);
|
|
|
@@ -220,49 +222,10 @@ export const unifiedStream = async (
|
|
|
await throwStreamHttpError(response);
|
|
|
}
|
|
|
|
|
|
- const reader = response.body!.getReader();
|
|
|
- const decoder = new TextDecoder();
|
|
|
- let buffer = '';
|
|
|
- let sessionId = '';
|
|
|
- let isDone = false;
|
|
|
-
|
|
|
- // eslint-disable-next-line no-constant-condition
|
|
|
- while (true) {
|
|
|
- const { done, value } = await reader.read();
|
|
|
- if (done || isDone) break;
|
|
|
-
|
|
|
- buffer += decoder.decode(value, { stream: true });
|
|
|
- const lines = buffer.split('\n');
|
|
|
- buffer = lines.pop() || '';
|
|
|
-
|
|
|
- for (const line of lines) {
|
|
|
- if (line.trim() === '') continue;
|
|
|
-
|
|
|
- if (line.startsWith('data: ')) {
|
|
|
- const dataStr = line.replace(/^data: /, '');
|
|
|
-
|
|
|
- try {
|
|
|
- const data = JSON.parse(dataStr) as SseEvent;
|
|
|
-
|
|
|
- onChunk(data);
|
|
|
-
|
|
|
- if (data.type === 'done' || data.type === 'error' || data.type === 'interrupt') {
|
|
|
- if (data.session_id) {
|
|
|
- sessionId = data.session_id;
|
|
|
- }
|
|
|
- isDone = true;
|
|
|
- break;
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- console.warn('SSE数据解析失败:', dataStr);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (isDone) break;
|
|
|
- }
|
|
|
+ const { sessionId, parseErrors } = await consumeSse<SseEvent>(response, onChunk);
|
|
|
|
|
|
- return { session_id: sessionId };
|
|
|
+ // parseErrors > 0 表示这条流里有事件被丢弃,正文可能不完整
|
|
|
+ return { session_id: sessionId, parse_errors: parseErrors };
|
|
|
} catch (error) {
|
|
|
if ((error as Error)?.name !== 'AbortError') {
|
|
|
console.error('统一对话流式请求失败:', error);
|
|
|
@@ -277,12 +240,13 @@ export const unifiedStream = async (
|
|
|
* @param params.session_id - 会话唯一标识ID,可选
|
|
|
* @param params.message - 附加消息/审查指令,可选
|
|
|
* @param onChunk - 流式数据回调函数
|
|
|
- * @returns Promise<{ session_id: string }>
|
|
|
+ * @returns Promise<{ session_id: string; parse_errors: number }>
|
|
|
+ * parse_errors 大于 0 表示这条流里有事件被丢弃(正文可能不完整)
|
|
|
*/
|
|
|
export const reviewPdfStream = async (
|
|
|
params: { file: File; session_id?: string; message?: string; mode?: string; signal?: AbortSignal },
|
|
|
onChunk: (data: SseEvent) => void
|
|
|
-): Promise<{ session_id: string }> => {
|
|
|
+): Promise<{ session_id: string; parse_errors: number }> => {
|
|
|
try {
|
|
|
const formData = new FormData();
|
|
|
formData.append('file', params.file);
|
|
|
@@ -307,49 +271,10 @@ export const reviewPdfStream = async (
|
|
|
await throwStreamHttpError(response);
|
|
|
}
|
|
|
|
|
|
- const reader = response.body!.getReader();
|
|
|
- const decoder = new TextDecoder();
|
|
|
- let buffer = '';
|
|
|
- let sessionId = '';
|
|
|
- let isDone = false;
|
|
|
-
|
|
|
- // eslint-disable-next-line no-constant-condition
|
|
|
- while (true) {
|
|
|
- const { done, value } = await reader.read();
|
|
|
- if (done || isDone) break;
|
|
|
-
|
|
|
- buffer += decoder.decode(value, { stream: true });
|
|
|
- const lines = buffer.split('\n');
|
|
|
- buffer = lines.pop() || '';
|
|
|
-
|
|
|
- for (const line of lines) {
|
|
|
- if (line.trim() === '') continue;
|
|
|
-
|
|
|
- if (line.startsWith('data: ')) {
|
|
|
- const dataStr = line.replace(/^data: /, '');
|
|
|
-
|
|
|
- try {
|
|
|
- const data = JSON.parse(dataStr) as SseEvent;
|
|
|
-
|
|
|
- onChunk(data);
|
|
|
-
|
|
|
- if (data.type === 'done' || data.type === 'error' || data.type === 'interrupt') {
|
|
|
- if (data.session_id) {
|
|
|
- sessionId = data.session_id;
|
|
|
- }
|
|
|
- isDone = true;
|
|
|
- break;
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- console.warn('SSE数据解析失败:', dataStr);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ const { sessionId, parseErrors } = await consumeSse<SseEvent>(response, onChunk);
|
|
|
|
|
|
- if (isDone) break;
|
|
|
- }
|
|
|
-
|
|
|
- return { session_id: sessionId };
|
|
|
+ // parseErrors > 0 表示这条流里有事件被丢弃,正文可能不完整
|
|
|
+ return { session_id: sessionId, parse_errors: parseErrors };
|
|
|
} catch (error) {
|
|
|
if ((error as Error)?.name !== 'AbortError') {
|
|
|
console.error('PDF审查流式请求失败:', error);
|
|
|
@@ -365,12 +290,13 @@ export const reviewPdfStream = async (
|
|
|
* @param params.status - ask_user响应状态:"answered"(默认,消费answers)/ "cancelled"(取消)/ "error"(出错)
|
|
|
* @param params.answers - ask_user答案列表,与中断事件ask_user.questions一一对应(status=answered时必填)
|
|
|
* @param onChunk - 流式数据回调函数
|
|
|
- * @returns Promise<{ session_id: string }>
|
|
|
+ * @returns Promise<{ session_id: string; parse_errors: number }>
|
|
|
+ * parse_errors 大于 0 表示这条流里有事件被丢弃(正文可能不完整)
|
|
|
*/
|
|
|
export const chatResumeStream = async (
|
|
|
params: { session_id: string; thread_id?: string; status?: string; answers?: string[]; signal?: AbortSignal },
|
|
|
onChunk: (data: SseEvent) => void
|
|
|
-): Promise<{ session_id: string }> => {
|
|
|
+): Promise<{ session_id: string; parse_errors: number }> => {
|
|
|
try {
|
|
|
const body: Record<string, any> = {
|
|
|
session_id: params.session_id,
|
|
|
@@ -397,49 +323,10 @@ export const chatResumeStream = async (
|
|
|
await throwStreamHttpError(response);
|
|
|
}
|
|
|
|
|
|
- const reader = response.body!.getReader();
|
|
|
- const decoder = new TextDecoder();
|
|
|
- let buffer = '';
|
|
|
- let sessionId = '';
|
|
|
- let isDone = false;
|
|
|
-
|
|
|
- // eslint-disable-next-line no-constant-condition
|
|
|
- while (true) {
|
|
|
- const { done, value } = await reader.read();
|
|
|
- if (done || isDone) break;
|
|
|
-
|
|
|
- buffer += decoder.decode(value, { stream: true });
|
|
|
- const lines = buffer.split('\n');
|
|
|
- buffer = lines.pop() || '';
|
|
|
-
|
|
|
- for (const line of lines) {
|
|
|
- if (line.trim() === '') continue;
|
|
|
-
|
|
|
- if (line.startsWith('data: ')) {
|
|
|
- const dataStr = line.replace(/^data: /, '');
|
|
|
-
|
|
|
- try {
|
|
|
- const data = JSON.parse(dataStr) as SseEvent;
|
|
|
-
|
|
|
- onChunk(data);
|
|
|
-
|
|
|
- if (data.type === 'done' || data.type === 'error' || data.type === 'interrupt') {
|
|
|
- if (data.session_id) {
|
|
|
- sessionId = data.session_id;
|
|
|
- }
|
|
|
- isDone = true;
|
|
|
- break;
|
|
|
- }
|
|
|
- } catch (e) {
|
|
|
- console.warn('SSE数据解析失败:', dataStr);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if (isDone) break;
|
|
|
- }
|
|
|
+ const { sessionId, parseErrors } = await consumeSse<SseEvent>(response, onChunk);
|
|
|
|
|
|
- return { session_id: sessionId };
|
|
|
+ // parseErrors > 0 表示这条流里有事件被丢弃,正文可能不完整
|
|
|
+ return { session_id: sessionId, parse_errors: parseErrors };
|
|
|
} catch (error) {
|
|
|
if ((error as Error)?.name !== 'AbortError') {
|
|
|
console.error('恢复会话流式请求失败:', error);
|