575 lines
15 KiB
TypeScript
575 lines
15 KiB
TypeScript
import other from '/@/utils/other';
|
||
import request from '/@/utils/request';
|
||
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
||
import { Session } from '/@/utils/storage';
|
||
|
||
export interface IOrchestrationScope {
|
||
id?: string;
|
||
username: string;
|
||
}
|
||
|
||
export enum EOrchestrationType {
|
||
WORK_FLOW = 'WORK_FLOW',
|
||
SIMPLE = 'SIMPLE',
|
||
}
|
||
|
||
export interface IOrchestrationItem {
|
||
id?: string;
|
||
name?: string;
|
||
type?: EOrchestrationType;
|
||
description?: string;
|
||
isPublic?: boolean;
|
||
publicAddress?: string;
|
||
apiAddress?: string;
|
||
apiBaseUrl?: string;
|
||
|
||
fullScreenUrl?: string;
|
||
fixedUrl?: string;
|
||
|
||
questionLimit?: number;
|
||
whiteListEnable?: boolean;
|
||
whiteList?: string;
|
||
showSource?: boolean;
|
||
|
||
modelId?: string;
|
||
|
||
createBy?: string;
|
||
createTime?: string;
|
||
updateTime?: string;
|
||
|
||
modelSetting?: {
|
||
prompt?: string;
|
||
system?: string;
|
||
noReferencesPrompt?: string;
|
||
};
|
||
dialogueNumber?: number;
|
||
datasetIdList?: any[];
|
||
datasetSetting?: {
|
||
searchMode?: string;
|
||
similarity?: number;
|
||
topN?: number;
|
||
maxParagraphCharNumber?: number;
|
||
noReferencesSetting?: {
|
||
status?: string;
|
||
designatedAnswer?: string;
|
||
};
|
||
};
|
||
problemOptimizationPrompt?: string;
|
||
problemOptimization?: boolean;
|
||
prologue?: string;
|
||
sttModelEnable?: boolean;
|
||
sttModelId?: string;
|
||
ttsModelEnable?: boolean;
|
||
ttsType?: string;
|
||
ttsModelId?: string;
|
||
ttsModelParamsSetting?: {};
|
||
fileUploadSetting?: {
|
||
audio?: boolean;
|
||
image?: boolean;
|
||
video?: boolean;
|
||
document?: boolean;
|
||
maxFiles?: number;
|
||
fileLimit?: number;
|
||
};
|
||
disclaimerValue?: string;
|
||
}
|
||
|
||
export interface IOrchestrationAPIKey {
|
||
id?: string;
|
||
allowCrossDomain?: boolean;
|
||
application?: string;
|
||
createTime?: string;
|
||
crossDomainList?: string;
|
||
isActive?: boolean;
|
||
secretKey?: string;
|
||
updateTime?: string;
|
||
}
|
||
|
||
export function fetchList(query?: Object) {
|
||
return request({
|
||
url: '/knowledge/aiFlow/page',
|
||
method: 'get',
|
||
params: query,
|
||
});
|
||
}
|
||
|
||
export function addObj(obj?: Object) {
|
||
return request({
|
||
url: '/knowledge/aiFlow',
|
||
method: 'post',
|
||
data: obj,
|
||
});
|
||
}
|
||
|
||
export function getObj(id?: string) {
|
||
return request({
|
||
url: '/knowledge/aiFlow/' + id,
|
||
method: 'get',
|
||
});
|
||
}
|
||
|
||
export function delObjs(ids?: Object) {
|
||
return request({
|
||
url: '/knowledge/aiFlow',
|
||
method: 'delete',
|
||
data: ids,
|
||
});
|
||
}
|
||
|
||
export function putObj(obj?: Object) {
|
||
return request({
|
||
url: '/knowledge/aiFlow',
|
||
method: 'put',
|
||
data: obj,
|
||
});
|
||
}
|
||
|
||
export function exportFlow(id: string, name: string) {
|
||
return other.downBlobFile(`/knowledge/aiFlow/export/${id}`, {}, `${name}.dsl`);
|
||
}
|
||
|
||
export function copyFlow(id: string) {
|
||
return request.post(`/knowledge/aiFlow/copy/${id}`);
|
||
}
|
||
|
||
export function importFlow(data: FormData) {
|
||
return request.post(`/knowledge/aiFlow/import`, data);
|
||
}
|
||
|
||
export interface IOrchestrationAPIKey {
|
||
id?: string;
|
||
allowCrossDomain?: boolean;
|
||
application?: string;
|
||
createTime?: string;
|
||
crossDomainList?: string;
|
||
isActive?: boolean;
|
||
secretKey?: string;
|
||
updateTime?: string;
|
||
}
|
||
|
||
/**
|
||
* @description : 编排 发起导入
|
||
* @return {any}
|
||
*/
|
||
export async function orchestrationImportContent(data: FormData) {
|
||
return request.post(`/knowledge/orchestration/import/content`, data);
|
||
}
|
||
|
||
/**
|
||
* @description : 编排 发起导出
|
||
* @return {any}
|
||
*/
|
||
export async function orchestrationExportContent(id: string, name: string) {
|
||
return other.downBlobFile(`/knowledge/orchestration/export/content/${id}`, {}, name);
|
||
}
|
||
|
||
/**
|
||
* @description : 复制 编排
|
||
* @param {string} originId
|
||
* @param {IOrchestrationItem} data
|
||
* @return {any}
|
||
*/
|
||
export async function orchestrationCopy(originId: string, data: IOrchestrationItem) {
|
||
return request.post(`/knowledge/orchestration/copy/${originId}`, data);
|
||
}
|
||
|
||
/**
|
||
* @description : 获取 编排 详情
|
||
* @param {string} id
|
||
* @return {any}
|
||
*/
|
||
export async function fetchOrchestrationInfo(id: string) {
|
||
return request.get(`/knowledge/orchestration/info/${id}`);
|
||
}
|
||
|
||
export async function fetchChartRecord(id: string, data: { startTime: string; endTime: string }) {
|
||
return request.get(`/knowledge/orchestration/chart/record/${id}`, { params: data });
|
||
}
|
||
|
||
export async function fetchOrchestrationAPIKeyList(data: { id: string }) {
|
||
return request.get(`/knowledge/orchestration/apikey/${data.id}`);
|
||
}
|
||
|
||
export async function orchestrationAPIKeyUpdate(data: any) {
|
||
return request.put(`/knowledge/orchestration/apikey/update`, data);
|
||
}
|
||
|
||
export async function orchestrationAPIKeyAdd(id: string) {
|
||
return request.post(`/knowledge/orchestration/apikey/${id}/add`);
|
||
}
|
||
|
||
export async function orchestrationAPIKeyRemove(id: string) {
|
||
return request.delete(`/knowledge/orchestration/apikey/${id}/delete`);
|
||
}
|
||
|
||
/**
|
||
* @description : 获取模型的参数设置
|
||
* @param {string} orchestrationId
|
||
* @param {string} modelId
|
||
* @return {any}
|
||
*/
|
||
export async function fetchModelParamsForm(orchestrationId: string, modelId: string) {
|
||
return request.get(`/knowledge/orchestration/${orchestrationId}/modelParamsForm/${modelId}`);
|
||
}
|
||
|
||
/**
|
||
* @description : 播放测试文本
|
||
* @param {string} id
|
||
* @param {any} data
|
||
* @return {any}
|
||
*/
|
||
export function playDemoText(id: string, data: any) {
|
||
return request.post(`/knowledge/orchestration/playDemoText/${id}`, data, {
|
||
responseType: 'blob',
|
||
});
|
||
}
|
||
|
||
/**
|
||
* @description : 文件上传
|
||
* @param {String} application_id
|
||
* @param {String} chat_id
|
||
* @param {any} data
|
||
* @return {any}
|
||
*/
|
||
export function chatUploadFile(application_id: string, chat_id: String, data: any) {
|
||
return request.post(`/knowledge/orchestration/uploadFile/${application_id}/${chat_id}`, data);
|
||
}
|
||
|
||
/**
|
||
* @description : 上传录音文件
|
||
* @param {string} application_id
|
||
* @param {any} data
|
||
* @return {any}
|
||
*/
|
||
export function chatPostSpeechToText(application_id: string, data: any) {
|
||
return request.post(`/knowledge/orchestration/chatPostSpeechToText/${application_id}`, data);
|
||
}
|
||
|
||
export interface FlowExecutionResult {
|
||
nodes: any[];
|
||
executed: boolean;
|
||
result: any;
|
||
duration: number;
|
||
error?: string;
|
||
totalTokens?: number;
|
||
}
|
||
|
||
export interface FlowExecutionEvent {
|
||
type: 'start' | 'progress' | 'complete' | 'error' | 'chat_message';
|
||
nodeId?: string;
|
||
nodeName?: string;
|
||
data?: any;
|
||
progress?: number;
|
||
error?: string;
|
||
result?: FlowExecutionResult;
|
||
content?: string;
|
||
tokens?: number; // 添加 tokens 字段
|
||
duration?: number; // 添加 duration 字段
|
||
nodes?: Array<any>; // 添加 nodes 字段
|
||
isStreaming?: boolean;
|
||
}
|
||
|
||
export interface FlowExecutionCallbacks {
|
||
onStart?: () => void;
|
||
onProgress?: (event: FlowExecutionEvent) => void;
|
||
onComplete?: (result: FlowExecutionResult) => void;
|
||
onError?: (error: string) => void;
|
||
onChatMessage?: (content: string, isComplete: boolean, tokens?: number, duration?: number, nodes?: Array<any>) => void;
|
||
}
|
||
|
||
export function executeFlow(data: { id: string; params: any; envs: any; stream?: boolean }) {
|
||
return request.post(`/knowledge/aiFlow/execute`, data, {
|
||
timeout: 300000 // 设置超时时间为300秒 (300000毫秒)
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 使用SSE执行工作流,提供实时执行状态更新
|
||
* @param data 执行参数
|
||
* @param callbacks 回调函数
|
||
* @returns Promise<FlowExecutionResult>
|
||
*/
|
||
export async function executeFlowSSE(
|
||
data: { id: string; params: any; envs: any; stream?: boolean },
|
||
callbacks?: FlowExecutionCallbacks
|
||
): Promise<FlowExecutionResult> {
|
||
const token = Session.getToken();
|
||
const tenant = Session.getTenant();
|
||
|
||
return new Promise<FlowExecutionResult>((resolve, reject) => {
|
||
let controller: AbortController | null = new AbortController();
|
||
let result: FlowExecutionResult | null = null;
|
||
|
||
const cleanup = () => {
|
||
if (controller) {
|
||
controller.abort();
|
||
controller = null;
|
||
}
|
||
};
|
||
|
||
// 解析SSE返回的数据
|
||
const parseSSEResponse = (eventData: string) => {
|
||
try {
|
||
const parsed = JSON.parse(eventData);
|
||
|
||
// 处理聊天格式的数据: {"data":{"content":"...", "tokens": 123, "duration": 22986, "nodes": [...]}}
|
||
if (parsed.data && parsed.data.content !== undefined) {
|
||
return {
|
||
type: 'chat_message',
|
||
content: parsed.data.content,
|
||
tokens: parsed.data.tokens, // 添加 tokens 字段
|
||
duration: parsed.data.duration, // 添加 duration 字段
|
||
nodes: parsed.data.nodes, // 添加 nodes 字段
|
||
isStreaming: true
|
||
} as FlowExecutionEvent;
|
||
}
|
||
|
||
// 处理标准的FlowExecutionEvent格式
|
||
return parsed as FlowExecutionEvent;
|
||
} catch (e) {
|
||
return null;
|
||
}
|
||
};
|
||
|
||
const fetchOptions = {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
Authorization: `Bearer ${token}`,
|
||
'TENANT-ID': tenant,
|
||
},
|
||
body: JSON.stringify(data),
|
||
signal: controller.signal,
|
||
async onopen(response: Response) {
|
||
if (response.ok && response.headers.get('content-type')?.includes('text/event-stream')) {
|
||
callbacks?.onStart?.();
|
||
return;
|
||
}
|
||
throw new Error(`Failed to connect: ${response.status} ${response.statusText}`);
|
||
},
|
||
onmessage(event: { data: string }) {
|
||
const parsed = parseSSEResponse(event.data);
|
||
if (!parsed) return;
|
||
|
||
switch (parsed.type) {
|
||
case 'start':
|
||
callbacks?.onStart?.();
|
||
break;
|
||
case 'progress':
|
||
callbacks?.onProgress?.(parsed);
|
||
break;
|
||
case 'chat_message':
|
||
// 处理聊天消息流
|
||
if (parsed.content !== undefined) {
|
||
// 检查是否收到完成标记
|
||
if (parsed.content === '[DONE]') {
|
||
// 标记聊天消息完成并关闭连接,传递 tokens、duration 和 nodes 信息
|
||
callbacks?.onChatMessage?.('', true, parsed.tokens, parsed.duration, parsed.nodes);
|
||
cleanup();
|
||
// 如果没有正式的结果,创建一个默认结果
|
||
if (!result) {
|
||
result = {
|
||
nodes: parsed.nodes || [],
|
||
executed: true,
|
||
result: {},
|
||
duration: parsed.duration || 0,
|
||
totalTokens: parsed.tokens || 0
|
||
};
|
||
callbacks?.onComplete?.(result);
|
||
resolve(result);
|
||
}
|
||
return;
|
||
}
|
||
callbacks?.onChatMessage?.(parsed.content, false, parsed.tokens, parsed.duration, parsed.nodes);
|
||
}
|
||
break;
|
||
case 'complete':
|
||
if (parsed.result) {
|
||
result = parsed.result;
|
||
callbacks?.onComplete?.(parsed.result);
|
||
resolve(parsed.result);
|
||
}
|
||
break;
|
||
case 'error':
|
||
const errorMsg = parsed.error || 'Unknown error occurred';
|
||
callbacks?.onError?.(errorMsg);
|
||
reject(new Error(errorMsg));
|
||
break;
|
||
}
|
||
},
|
||
onclose() {
|
||
// 连接关闭时,如果有未完成的聊天消息,标记为完成
|
||
if (callbacks?.onChatMessage) {
|
||
callbacks.onChatMessage('', true);
|
||
}
|
||
cleanup();
|
||
},
|
||
onerror(error: Error) {
|
||
cleanup();
|
||
const errorMsg = error.message || 'Connection error';
|
||
callbacks?.onError?.(errorMsg);
|
||
// 抛出错误以停止自动重试
|
||
throw error;
|
||
},
|
||
};
|
||
|
||
// 启动SSE连接
|
||
fetchEventSource(`${request.defaults.baseURL}${other.adaptationUrl('/knowledge/aiFlow/execute')}`, fetchOptions)
|
||
.catch((error) => {
|
||
cleanup();
|
||
if (error.name === 'AbortError') {
|
||
reject(new Error('Request was cancelled'));
|
||
} else {
|
||
reject(error);
|
||
}
|
||
});
|
||
|
||
// 返回清理函数以便外部可以取消请求
|
||
return cleanup;
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 取消SSE工作流执行
|
||
* @param executionId 执行ID
|
||
*/
|
||
export async function cancelFlowExecution(executionId: string) {
|
||
return request.post(`/knowledge/aiFlow/cancel/${executionId}`);
|
||
}
|
||
|
||
/**
|
||
* 专为聊天格式设计的SSE工作流执行函数
|
||
* 处理 {"data":{"content":"..."}} 格式的流式响应
|
||
* @param data 执行参数
|
||
* @param callbacks 回调函数
|
||
* @returns Promise<{chatMessage: string, result: any}>
|
||
*/
|
||
export async function executeFlowSSEWithChat(
|
||
data: { id: string; conversationId: string; params: any; envs: any; stream?: boolean },
|
||
callbacks?: FlowExecutionCallbacks
|
||
): Promise<{chatMessage: string, result: any}> {
|
||
const token = Session.getToken();
|
||
const tenant = Session.getTenant();
|
||
|
||
return new Promise<{chatMessage: string, result: any}>((resolve, reject) => {
|
||
let controller: AbortController | null = new AbortController();
|
||
let chatMessageContent = '';
|
||
let hasReceivedData = false;
|
||
|
||
const cleanup = () => {
|
||
if (controller) {
|
||
controller.abort();
|
||
controller = null;
|
||
}
|
||
};
|
||
|
||
const fetchOptions = {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
Authorization: `Bearer ${token}`,
|
||
'TENANT-ID': tenant,
|
||
},
|
||
body: JSON.stringify(data),
|
||
signal: controller.signal,
|
||
async onopen(response: Response) {
|
||
if (response.ok && response.headers.get('content-type')?.includes('text/event-stream')) {
|
||
callbacks?.onStart?.();
|
||
return;
|
||
}
|
||
throw new Error(`Failed to connect: ${response.status} ${response.statusText}`);
|
||
},
|
||
onmessage(event: { data: string }) {
|
||
try {
|
||
const parsed = JSON.parse(event.data);
|
||
|
||
// 处理聊天格式的数据: {"data":{"content":"...", "tokens": 123, "duration": 22986}}
|
||
if (parsed.data && parsed.data.content !== undefined) {
|
||
hasReceivedData = true;
|
||
|
||
// 检查是否收到完成标记
|
||
if (parsed.data.content === '[DONE]') {
|
||
const result = {
|
||
chatMessage: chatMessageContent,
|
||
result: {
|
||
nodes: parsed.data.nodes || [],
|
||
executed: true,
|
||
result: {},
|
||
duration: parsed.data.duration || 0,
|
||
totalTokens: parsed.data.tokens || 0
|
||
}
|
||
};
|
||
callbacks?.onChatMessage?.('', true, parsed.data.tokens, parsed.data.duration, parsed.data.nodes); // 标记聊天消息完成,传递 tokens、duration 和 nodes
|
||
callbacks?.onComplete?.(result.result);
|
||
cleanup();
|
||
resolve(result);
|
||
return;
|
||
}
|
||
|
||
chatMessageContent += parsed.data.content;
|
||
callbacks?.onChatMessage?.(parsed.data.content, false, parsed.data.tokens, parsed.data.duration, parsed.data.nodes);
|
||
return;
|
||
}
|
||
|
||
// 处理其他类型的事件
|
||
if (parsed.type) {
|
||
switch (parsed.type) {
|
||
case 'start':
|
||
callbacks?.onStart?.();
|
||
break;
|
||
case 'progress':
|
||
callbacks?.onProgress?.(parsed);
|
||
break;
|
||
case 'complete':
|
||
const result = {
|
||
chatMessage: chatMessageContent,
|
||
result: parsed.result || {}
|
||
};
|
||
callbacks?.onChatMessage?.('', true); // 标记聊天消息完成
|
||
callbacks?.onComplete?.(parsed.result);
|
||
resolve(result);
|
||
break;
|
||
case 'error':
|
||
const errorMsg = parsed.error || 'Unknown error occurred';
|
||
callbacks?.onError?.(errorMsg);
|
||
reject(new Error(errorMsg));
|
||
break;
|
||
}
|
||
}
|
||
} catch (e) {
|
||
// 忽略无法解析的数据
|
||
}
|
||
},
|
||
onclose() {
|
||
// 连接关闭时,如果有数据但没有收到完成事件,自动完成
|
||
if (hasReceivedData) {
|
||
const result = {
|
||
chatMessage: chatMessageContent,
|
||
result: {}
|
||
};
|
||
callbacks?.onChatMessage?.('', true);
|
||
resolve(result);
|
||
}
|
||
cleanup();
|
||
},
|
||
onerror(error: Error) {
|
||
cleanup();
|
||
const errorMsg = error.message || 'Connection error';
|
||
callbacks?.onError?.(errorMsg);
|
||
// 抛出错误以停止自动重试
|
||
throw error;
|
||
},
|
||
};
|
||
|
||
// 启动SSE连接
|
||
fetchEventSource(`${request.defaults.baseURL}${other.adaptationUrl('/knowledge/aiFlow/execute')}`, fetchOptions)
|
||
.catch((error) => {
|
||
cleanup();
|
||
if (error.name === 'AbortError') {
|
||
reject(new Error('Request was cancelled'));
|
||
} else {
|
||
reject(error);
|
||
}
|
||
});
|
||
});
|
||
} |