修复bug
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
/* eslint-disable */
|
||||
// @ts-ignore
|
||||
import type { CustomRequestOptions } from '@/http/types';
|
||||
|
||||
type AnyRecord = Record<string, any>;
|
||||
|
||||
type StreamCallbacks = {
|
||||
onToken?: (text: string) => void;
|
||||
onDone?: () => void;
|
||||
onError?: (err: unknown) => void;
|
||||
onStart?: (payload: Record<string, any>) => void;
|
||||
onTool?: (payload: Record<string, any>) => void;
|
||||
onChunk?: (payload: Record<string, any>) => void;
|
||||
onEnd?: (payload: Record<string, any>) => 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<string, any> | 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<string, any> | 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;
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/* eslint-disable */
|
||||
// @ts-ignore
|
||||
import request from '@/http/vue-query';
|
||||
import type { CustomRequestOptions } from '@/http/types';
|
||||
|
||||
type AnyRecord = Record<string, any>;
|
||||
export type DiningProfileFields = Record<string, unknown>;
|
||||
|
||||
export type DiningProfileConfigItem = {
|
||||
groupKey: string;
|
||||
groupName: string;
|
||||
fieldKey: string;
|
||||
fieldName: string;
|
||||
optionsJson: string;
|
||||
sortNum: number;
|
||||
isRequired: number | null;
|
||||
multiSelect: number;
|
||||
};
|
||||
|
||||
/** 查询当前可用画像配置 GET /app/diningProfile/config */
|
||||
export async function appDiningProfileConfigGet({
|
||||
options,
|
||||
}: {
|
||||
options?: CustomRequestOptions;
|
||||
}) {
|
||||
return request<{ code: number; data: DiningProfileConfigItem[]; msg?: string }>(
|
||||
'/app/diningProfile/config',
|
||||
{
|
||||
method: 'GET',
|
||||
...(options || {}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/** 查询当前登录用户画像 GET /app/diningProfile */
|
||||
export async function appDiningProfileGet({
|
||||
options,
|
||||
}: {
|
||||
options?: CustomRequestOptions;
|
||||
}) {
|
||||
return request<{ code: number; data: DiningProfileFields | null; msg?: string }>(
|
||||
'/app/diningProfile',
|
||||
{
|
||||
method: 'GET',
|
||||
...(options || {}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/** 保存画像配置 POST /app/diningProfile/save */
|
||||
export async function appDiningProfileSavePost({
|
||||
body,
|
||||
options,
|
||||
}: {
|
||||
body: { fields: DiningProfileFields };
|
||||
options?: CustomRequestOptions;
|
||||
}) {
|
||||
return request<{ code: number; data: AnyRecord | null; msg?: string }>(
|
||||
'/app/diningProfile/save',
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
data: body,
|
||||
...(options || {}),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ export * from './types';
|
||||
export * from './displayEnumLabel';
|
||||
|
||||
export * from './filtersConfig';
|
||||
export * from './aiChat';
|
||||
export * from './diningProfile';
|
||||
export * from './merchant';
|
||||
export * from './agreement';
|
||||
export * from './version';
|
||||
|
||||
Reference in New Issue
Block a user