Build lightweight AI agent admin
This commit is contained in:
@@ -0,0 +1,268 @@
|
||||
/**
|
||||
* Chat API 适配器
|
||||
*
|
||||
* 根据不同模式调用不同的 API
|
||||
*/
|
||||
|
||||
import type {
|
||||
AiChatPanelProps,
|
||||
Attachment,
|
||||
ChatApiAdapter,
|
||||
ChatMessage,
|
||||
StreamEvent,
|
||||
} from '../types';
|
||||
|
||||
import { ref, toRef, watch } from 'vue';
|
||||
|
||||
import {
|
||||
feedbackAgentMessageApi,
|
||||
getAgentMessagesApi,
|
||||
resumeWorkflowStreamApi,
|
||||
runWorkflowStreamApi,
|
||||
sendAgentMessageStream,
|
||||
} from '#/api/ai-platform/ai-platform';
|
||||
import { getFileUrl } from '#/composables/useFileUrl';
|
||||
|
||||
/**
|
||||
* 创建 Chat API 适配器
|
||||
*/
|
||||
export function useChatApi(props: AiChatPanelProps): {
|
||||
adapter: ChatApiAdapter;
|
||||
conversationId: ReturnType<typeof ref<null | string>>;
|
||||
} {
|
||||
// 对话 ID(Agent 模式使用)
|
||||
const conversationId = ref<null | string>(props.conversationId || null);
|
||||
|
||||
// 使用 toRef 确保响应式追踪
|
||||
const propsConversationId = toRef(props, 'conversationId');
|
||||
|
||||
// 监听 props.conversationId 变化,同步到内部状态
|
||||
watch(propsConversationId, (newId) => {
|
||||
// 同步 props 的 conversationId 到内部状态
|
||||
conversationId.value = newId || null;
|
||||
});
|
||||
|
||||
// ========== 工作流模式适配器 ==========
|
||||
if (props.mode === 'workflow') {
|
||||
const adapter: ChatApiAdapter = {
|
||||
sendMessage: (
|
||||
_message: string,
|
||||
inputs: Record<string, any> | undefined,
|
||||
onEvent: (event: StreamEvent) => void,
|
||||
onError: (error: Error) => void,
|
||||
onComplete: () => void,
|
||||
) => {
|
||||
if (!props.workflowId) {
|
||||
onError(new Error('工作流 ID 未配置'));
|
||||
return null;
|
||||
}
|
||||
|
||||
return runWorkflowStreamApi(
|
||||
props.workflowId,
|
||||
inputs || {},
|
||||
onEvent,
|
||||
onError,
|
||||
onComplete,
|
||||
props.useDraft ?? true, // 工作流模式默认使用草稿版本
|
||||
);
|
||||
},
|
||||
|
||||
resumeExecution: (
|
||||
runId: string,
|
||||
userInput: any,
|
||||
onEvent: (event: StreamEvent) => void,
|
||||
onError: (error: Error) => void,
|
||||
onComplete: () => void,
|
||||
) => {
|
||||
return resumeWorkflowStreamApi(
|
||||
runId,
|
||||
userInput,
|
||||
onEvent,
|
||||
onError,
|
||||
onComplete,
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
return { adapter, conversationId };
|
||||
}
|
||||
|
||||
// ========== Agent 模式适配器 ==========
|
||||
const adapter: ChatApiAdapter = {
|
||||
sendMessage: (
|
||||
message: string,
|
||||
inputs: Record<string, any> | undefined,
|
||||
onEvent: (event: StreamEvent) => void,
|
||||
onError: (error: Error) => void,
|
||||
onComplete: () => void,
|
||||
attachments?: Attachment[],
|
||||
) => {
|
||||
if (!props.agentId) {
|
||||
onError(new Error('Agent ID 未配置'));
|
||||
return null;
|
||||
}
|
||||
|
||||
// 转换附件格式,使用 file_id(优先)或 id
|
||||
const attachmentInputs = attachments
|
||||
?.filter((att) => att.file_id || att.id)
|
||||
.map((att) => ({
|
||||
file_id: att.file_id || att.id!,
|
||||
type: att.type,
|
||||
}));
|
||||
|
||||
// 从 inputs 中提取 form_code(如果存在)
|
||||
const formCode = inputs?.form_code;
|
||||
|
||||
return sendAgentMessageStream(
|
||||
props.agentId,
|
||||
{
|
||||
message,
|
||||
conversation_id: conversationId.value || undefined,
|
||||
attachments: attachmentInputs,
|
||||
form_code: formCode,
|
||||
},
|
||||
(event) => {
|
||||
// 捕获 conversation_id
|
||||
if (event.type === 'start' && event.conversation_id) {
|
||||
conversationId.value = event.conversation_id;
|
||||
}
|
||||
onEvent(event);
|
||||
},
|
||||
onError,
|
||||
onComplete,
|
||||
);
|
||||
},
|
||||
|
||||
resumeExecution: (
|
||||
_runId: string,
|
||||
userInput: any,
|
||||
onEvent: (event: StreamEvent) => void,
|
||||
onError: (error: Error) => void,
|
||||
onComplete: () => void,
|
||||
) => {
|
||||
if (!props.agentId) {
|
||||
onError(new Error('Agent ID 未配置'));
|
||||
return null;
|
||||
}
|
||||
|
||||
// Agent 模式的恢复逻辑与发送相同
|
||||
return sendAgentMessageStream(
|
||||
props.agentId,
|
||||
{
|
||||
message:
|
||||
typeof userInput === 'string'
|
||||
? userInput
|
||||
: JSON.stringify(userInput),
|
||||
conversation_id: conversationId.value || undefined,
|
||||
},
|
||||
onEvent,
|
||||
onError,
|
||||
onComplete,
|
||||
);
|
||||
},
|
||||
|
||||
// Agent-chat 模式支持历史加载
|
||||
loadHistory:
|
||||
props.mode === 'agent-chat' && props.enableHistory !== false
|
||||
? async (convId: string): Promise<ChatMessage[]> => {
|
||||
const res = await getAgentMessagesApi(convId, { pageSize: 100 });
|
||||
conversationId.value = convId;
|
||||
|
||||
// 后端返回的是数组或分页对象,需要兼容处理
|
||||
const items = Array.isArray(res) ? res : res.items || [];
|
||||
|
||||
// 转换为 ChatMessage 格式
|
||||
const messages = await Promise.all(
|
||||
items.map(async (msg: any) => {
|
||||
// 处理附件,为图片加载完整 URL
|
||||
let attachments: Attachment[] | undefined;
|
||||
if (msg.attachments?.length) {
|
||||
attachments = await Promise.all(
|
||||
msg.attachments.map(async (att: any) => {
|
||||
const fileId = att.file_id || att.id;
|
||||
const attType = att.type || 'file';
|
||||
const mimeType = att.mime_type || '';
|
||||
|
||||
// 为图片附件加载完整 URL
|
||||
let url = att.url;
|
||||
let thumbnail = att.thumbnail;
|
||||
if (
|
||||
(attType === 'image' ||
|
||||
mimeType.startsWith('image/')) &&
|
||||
fileId
|
||||
) {
|
||||
try {
|
||||
const imageUrl = await getFileUrl(fileId);
|
||||
url = imageUrl || url;
|
||||
thumbnail = imageUrl || thumbnail;
|
||||
} catch (error) {
|
||||
console.warn(
|
||||
'Failed to load image URL for attachment:',
|
||||
fileId,
|
||||
error,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: fileId,
|
||||
file_id: fileId,
|
||||
type: attType,
|
||||
name: att.name,
|
||||
url,
|
||||
size: att.size,
|
||||
thumbnail,
|
||||
mime_type: mimeType,
|
||||
} as Attachment;
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
id: msg.id,
|
||||
role: msg.role as 'assistant' | 'user',
|
||||
content: msg.content,
|
||||
status: msg.status as any,
|
||||
timestamp: msg.sys_create_datetime || msg.created_at,
|
||||
elapsed_time: msg.elapsed_time,
|
||||
tokens_used: msg.total_tokens,
|
||||
error_message: msg.error_message,
|
||||
feedback: msg.feedback || null,
|
||||
reasoning_steps: msg.reasoning_steps?.map((step: any) => ({
|
||||
type: step.type,
|
||||
content: step.content || '',
|
||||
tool: step.tool,
|
||||
params: step.params,
|
||||
node_id: step.node_id,
|
||||
node_type: step.node_type,
|
||||
output: step.output,
|
||||
status: step.status,
|
||||
timestamp: step.timestamp,
|
||||
})),
|
||||
interaction: msg.interaction,
|
||||
voice: msg.voice,
|
||||
attachments,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
// 按时间升序排序
|
||||
return messages.sort(
|
||||
(a, b) =>
|
||||
new Date(a.timestamp).getTime() -
|
||||
new Date(b.timestamp).getTime(),
|
||||
);
|
||||
}
|
||||
: undefined,
|
||||
|
||||
// Agent-chat 模式支持反馈
|
||||
sendFeedback:
|
||||
props.mode === 'agent-chat' && props.enableFeedback !== false
|
||||
? async (messageId: string, feedback: 'dislike' | 'like') => {
|
||||
await feedbackAgentMessageApi(messageId, { feedback });
|
||||
}
|
||||
: undefined,
|
||||
};
|
||||
|
||||
return { adapter, conversationId };
|
||||
}
|
||||
Reference in New Issue
Block a user