Build lightweight AI agent admin

This commit is contained in:
Codex
2026-06-08 18:14:59 +08:00
commit e164840f43
2530 changed files with 435693 additions and 0 deletions
@@ -0,0 +1,926 @@
<script setup lang="ts">
import type {
Agent,
AgentChatEvent,
AgentMessage,
ReasoningStep,
} from '#/api/ai-platform/ai-platform';
import type { ChatMessage } from '#/components/ChatBox/index';
/**
* Agent 对话面板组件
* 可复用于 Agent 编辑页面和 Agent 对话页面
*/
import { computed, nextTick, onUnmounted, ref, watch } from 'vue';
import { $t } from '@vben/locales';
import { ElMessage } from 'element-plus';
import {
feedbackAgentMessageApi,
getAgentMessagesApi,
sendAgentMessageStream,
} from '#/api/ai-platform/ai-platform';
import { ChatBox } from '#/components/ChatBox/index';
import { AppDesignPanel, DesignEditorPanel } from '#/components/form-editor';
import AiWorkingAnimation from '../../../../components/ai-loading/AiWorkingAnimation.vue';
const props = defineProps<{
/** Agent 详情(可选,用于显示名称和欢迎配置) */
agent?: Agent | null;
/** Agent ID */
agentId: string;
/** 当前对话 ID(用于加载历史消息) */
currentConversationId?: null | string;
/** 是否禁用输入 */
disabled?: boolean;
/** 是否启用附件 */
enableAttachment?: boolean;
/** 是否启用图片 */
enableImage?: boolean;
/** 是否启用语音 */
enableVoice?: boolean;
/** 占位符文本 */
placeholder?: string;
/** 是否显示清空按钮 */
showClearButton?: boolean;
}>();
const emit = defineEmits<{
(e: 'conversation-created', conversationId: string): void;
}>();
// 设计面板显示状态
const showDesignPanel = ref(false);
const showAppDesignPanel = ref(false);
// 对话状态
const chatMessages = ref<AgentMessage[]>([]);
const sending = ref(false);
const streamingContent = ref('');
const streamingSteps = ref<ReasoningStep[]>([]);
const currentStep = ref('');
const conversationId = ref<null | string>(null);
const chatBoxRef = ref<InstanceType<typeof ChatBox> | null>(null);
// 对话流交互状态
const waitingForInput = ref(false);
const waitingConfig = ref<any>(null);
// 设计面板状态
const currentDesign = ref<null | {
data: any;
form_fields?: any[];
nodeId: string;
table_configs?: any[];
title: string;
type: string;
}>(null);
// 应用设计面板状态
const currentAppDesign = ref<null | {
data: any;
nodeId: string;
title: string;
type: string;
}>(null);
// 取消流式请求
let cancelStream: (() => void) | null = null;
// 生成消息 ID
const generateId = () =>
`msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
// 转换消息格式为 ChatMessage
const formattedMessages = computed<ChatMessage[]>(() => {
return chatMessages.value.map((msg) => ({
id: msg.id,
role: msg.role as 'assistant' | 'user',
content: msg.content,
status: msg.status as any,
timestamp: msg.created_at,
elapsed_time: msg.elapsed_time,
tokens_used: msg.total_tokens,
error_message: msg.error_message,
feedback: (msg.feedback as any) || null,
reasoning_steps: msg.reasoning_steps?.map((step) => ({
type: step.type as any,
content: step.content || '',
tool: step.tool,
params: step.params,
})),
interaction: msg.interaction,
voice: msg.voice as any,
}));
});
// 欢迎配置
const welcomeConfig = computed(() => {
if (!props.agent) return undefined;
return {
title: props.agent.name,
description:
props.agent.status === 'published'
? props.agent.welcome_message ||
$t('ai-platform.agent.chatPanel.sendToStart')
: $t('ai-platform.agent.chatPanel.publishFirst'),
suggestions:
props.agent.status === 'published' ? props.agent.suggested_questions : [],
};
});
// 监听 agentId 变化,清空对话
watch(
() => props.agentId,
() => {
clearChat();
},
);
// 监听 currentConversationId 变化,加载历史消息
watch(
() => props.currentConversationId,
async (newId) => {
if (newId) {
await loadMessages(newId);
} else {
clearChat();
}
},
{ immediate: true },
);
// 加载历史消息
async function loadMessages(convId: string) {
// 如果正在发送消息或等待用户输入,跳过加载以避免覆盖当前状态
if (sending.value || waitingForInput.value) {
conversationId.value = convId;
return;
}
try {
const res = await getAgentMessagesApi(convId, { pageSize: 100 });
chatMessages.value = res.items;
conversationId.value = convId;
await nextTick();
scrollToBottom();
} catch (error) {
console.error('加载消息失败:', error);
}
}
// 发送消息 (由 ChatBox 触发)
async function handleSendMessage(message: string) {
if (!message.trim() || sending.value) return;
// 如果正在等待用户输入(对话流模式),处理恢复逻辑
if (waitingForInput.value) {
// 移除最后一条助手消息的交互配置
const lastAssistantMsg = chatMessages.value.findLast(
(m) => m.role === 'assistant',
);
if (lastAssistantMsg && lastAssistantMsg.interaction) {
const msgIndex = chatMessages.value.findIndex(
(m) => m.id === lastAssistantMsg.id,
);
if (msgIndex !== -1) {
chatMessages.value[msgIndex] = {
...lastAssistantMsg,
interaction: undefined,
};
}
}
// 添加用户消息
const userMsg: AgentMessage = {
id: generateId(),
role: 'user',
content: message,
status: 'completed',
reasoning_steps: [],
tool_calls: [],
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
elapsed_time: 0,
error_message: '',
feedback: '',
created_at: new Date().toISOString(),
};
chatMessages.value.push(userMsg);
// 重置等待状态并继续工作流
waitingForInput.value = false;
waitingConfig.value = null;
doSendMessage(message, false);
return;
}
doSendMessage(message, true);
}
// 实际发送消息的函数
async function doSendMessage(message: string, addUserMessage: boolean = true) {
sending.value = true;
streamingContent.value = '';
streamingSteps.value = [];
currentStep.value = '';
// 如果工作流类型是 "application",立即显示右侧面板(加载状态)
if (props.agent?.workflow_type === 'application') {
showDesignPanel.value = true;
currentDesign.value = {
type: 'loading',
title: $t('ai-platform.agent.chatPanel.aiDeveloping'),
data: null,
nodeId: '',
};
}
// 添加用户消息
if (addUserMessage) {
const userMessage: AgentMessage = {
id: generateId(),
role: 'user',
content: message,
status: 'completed',
reasoning_steps: [],
tool_calls: [],
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
elapsed_time: 0,
error_message: '',
feedback: '',
created_at: new Date().toISOString(),
};
chatMessages.value.push(userMessage);
}
// 添加助手消息占位
const assistantMessage: AgentMessage = {
id: generateId(),
role: 'assistant',
content: '',
status: 'pending',
reasoning_steps: [],
tool_calls: [],
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
elapsed_time: 0,
error_message: '',
feedback: '',
created_at: new Date().toISOString(),
};
chatMessages.value.push(assistantMessage);
await nextTick();
scrollToBottom();
// 发送流式请求
// 系统变量(application_id 等)由 API 层自动注入
cancelStream = sendAgentMessageStream(
props.agentId,
{
message,
conversation_id: conversationId.value || undefined,
},
(event: AgentChatEvent) => handleStreamEvent(event, assistantMessage),
(error) => {
console.error('Stream error:', error);
assistantMessage.status = 'failed';
assistantMessage.error_message = error.message;
sending.value = false;
},
() => {
sending.value = false;
},
);
}
// 处理流式事件
function handleStreamEvent(
event: AgentChatEvent,
assistantMessage: AgentMessage,
) {
const msgIndex = chatMessages.value.findIndex(
(m) => m.id === assistantMessage.id,
);
switch (event.type) {
case 'action': {
currentStep.value = 'action';
streamingSteps.value.push({
type: 'action',
content: '',
tool: event.tool,
params: event.params,
timestamp: new Date().toISOString(),
});
assistantMessage.reasoning_steps = [...streamingSteps.value];
break;
}
case 'annotation_reply':
case 'knowledge_retrieval': {
currentStep.value = event.type;
streamingSteps.value.push({
type: event.type as 'annotation_reply' | 'knowledge_retrieval',
content: event.content || '',
status: 'completed',
timestamp: new Date().toISOString(),
});
assistantMessage.reasoning_steps = [...streamingSteps.value];
break;
}
case 'answer': {
currentStep.value = 'answering';
streamingContent.value = event.content || '';
assistantMessage.content = streamingContent.value;
break;
}
case 'complete': {
assistantMessage.status = 'completed';
assistantMessage.total_tokens = event.tokens_used || 0;
assistantMessage.elapsed_time = event.elapsed_time || 0;
currentStep.value = '';
break;
}
case 'error': {
assistantMessage.status = 'failed';
assistantMessage.error_message =
event.content || $t('ai-platform.agent.chatPanel.executionFailed');
currentStep.value = '';
break;
}
case 'node_complete': {
// 查找已存在的 node_start 步骤并更新为 node_complete
const existingIndex = streamingSteps.value.findIndex(
(s) => s.node_id === event.node_id && s.type === 'node_start',
);
if (existingIndex === -1) {
// 如果没有找到对应的 start,添加新的 complete 步骤
streamingSteps.value.push({
type: 'node_complete',
content: event.node_label || event.node_type || '',
node_id: event.node_id,
node_type: event.node_type,
output: event.outputs,
status: 'completed',
timestamp: new Date().toISOString(),
});
} else {
const existingStep = streamingSteps.value[existingIndex]!;
// 更新已存在的步骤
streamingSteps.value[existingIndex] = {
...existingStep,
type: 'node_complete',
content: event.node_label || event.node_type || '',
output: event.outputs,
status: 'completed',
timestamp: existingStep.timestamp || new Date().toISOString(),
};
}
assistantMessage.reasoning_steps = [...streamingSteps.value];
break;
}
case 'node_event': {
// 节点事件(如消息节点发送消息)
const nodeEvent = event.event;
if (nodeEvent?.type === 'message') {
// 从消息列表中查找初始消息,检查其内容是否为空
const initialMsg = chatMessages.value.find(
(m) => m.id === assistantMessage.id,
);
if (initialMsg && !initialMsg.content?.trim()) {
// 更新初始消息(typing indicator -> 实际内容)
initialMsg.content = nodeEvent.content || '';
initialMsg.status = 'completed';
} else {
// 已有内容,创建新消息
const newMsg: AgentMessage = {
id: generateId(),
role: 'assistant',
content: nodeEvent.content || '',
status: 'completed',
reasoning_steps: [],
tool_calls: [],
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
elapsed_time: 0,
error_message: '',
feedback: '',
created_at: new Date().toISOString(),
};
chatMessages.value.push(newMsg);
}
}
break;
}
case 'node_start': {
// 检查是否已存在相同 node_id 的步骤(避免重复)
const existingIndex = streamingSteps.value.findIndex(
(s) => s.node_id === event.node_id,
);
if (existingIndex === -1) {
// 添加新的执行步骤
streamingSteps.value.push({
type: 'node_start',
content: `${event.node_label || event.node_type}`,
node_id: event.node_id,
node_type: event.node_type,
status: 'running',
timestamp: new Date().toISOString(),
});
}
assistantMessage.reasoning_steps = [...streamingSteps.value];
break;
}
case 'observation': {
currentStep.value = 'observation';
streamingSteps.value.push({
type: 'observation',
content: event.content || '',
timestamp: new Date().toISOString(),
});
assistantMessage.reasoning_steps = [...streamingSteps.value];
break;
}
case 'start': {
if (event.conversation_id && !conversationId.value) {
conversationId.value = event.conversation_id;
emit('conversation-created', event.conversation_id);
}
if (event.message_id) {
assistantMessage.id = event.message_id;
}
break;
}
case 'thought': {
currentStep.value = 'thinking';
streamingSteps.value.push({
type: 'thought',
content: event.content || '',
timestamp: new Date().toISOString(),
});
assistantMessage.reasoning_steps = [...streamingSteps.value];
break;
}
case 'waiting_input': {
assistantMessage.status = 'completed';
currentStep.value = '';
waitingForInput.value = true;
waitingConfig.value = event.config;
const configData = event.config as any;
// 检查是否是设计预览类型
if (configData?.type === 'design_preview') {
const previewType = configData.preview_type;
// 应用设计方案使用 AppDesignPanel(富文本编辑器)
if (previewType === 'app_design') {
currentAppDesign.value = {
type: previewType,
title:
configData.title ||
$t('ai-platform.agent.chatPanel.appDesignTitle'),
data: configData.data || {},
nodeId: event.node_id || '',
};
showAppDesignPanel.value = true;
} else {
// 其他设计类型使用 DesignEditorPanel(表单编辑器)
currentDesign.value = {
type: previewType,
title:
configData.title ||
$t('ai-platform.agent.chatPanel.designPreview'),
data: configData.data || {},
nodeId: event.node_id || '',
form_fields: configData.form_fields || [],
table_configs: configData.table_configs || [],
};
showDesignPanel.value = true;
}
// 显示提示消息
const waitingContent = `**${configData.title}**\n\n${configData.message || $t('ai-platform.agent.chatPanel.designPanelHint')}`;
const newMsg: AgentMessage = {
id: generateId(),
role: 'assistant',
content: waitingContent,
status: 'completed',
reasoning_steps: [],
tool_calls: [],
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
elapsed_time: 0,
error_message: '',
feedback: '',
created_at: new Date().toISOString(),
};
chatMessages.value.push(newMsg);
} else {
// 普通对话流交互
const newMsg: AgentMessage = {
id: generateId(),
role: 'assistant',
content: getWaitingPrompt(event.config),
status: 'completed',
reasoning_steps: [],
tool_calls: [],
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
elapsed_time: 0,
error_message: '',
feedback: '',
created_at: new Date().toISOString(),
interaction: event.config,
};
chatMessages.value.push(newMsg);
}
sending.value = false;
break;
}
}
if (msgIndex !== -1) {
chatMessages.value[msgIndex] = { ...assistantMessage };
}
scrollToBottom();
}
// 获取等待提示文本
function getWaitingPrompt(config: any) {
if (!config) return $t('ai-platform.agent.chatPanel.waitingInput');
switch (config.type) {
case 'choice': {
return config.question || $t('ai-platform.agent.chatPanel.selectPrompt');
}
case 'confirm': {
return `**${config.title || $t('ai-platform.agent.chatPanel.confirm')}**\n\n${config.content || $t('ai-platform.agent.chatPanel.confirmContinue')}`;
}
case 'question': {
return config.question || $t('ai-platform.agent.chatPanel.inputPrompt');
}
default: {
return $t('ai-platform.agent.chatPanel.waitingInput');
}
}
}
// 处理交互提交
function handleInteractionSubmit(messageId: string, value: any) {
const msgIndex = chatMessages.value.findIndex((m) => m.id === messageId);
if (msgIndex !== -1) {
const msg = chatMessages.value[msgIndex];
if (msg) {
chatMessages.value[msgIndex] = { ...msg, interaction: undefined };
}
}
const userMsg: AgentMessage = {
id: generateId(),
role: 'user',
content: formatInteractionValue(value),
status: 'completed',
reasoning_steps: [],
tool_calls: [],
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
elapsed_time: 0,
error_message: '',
feedback: '',
created_at: new Date().toISOString(),
};
chatMessages.value.push(userMsg);
waitingForInput.value = false;
waitingConfig.value = null;
doSendMessage(String(value), false);
}
// 处理交互取消
function handleInteractionCancel(messageId: string) {
const msgIndex = chatMessages.value.findIndex((m) => m.id === messageId);
if (msgIndex !== -1) {
const msg = chatMessages.value[msgIndex];
if (msg) {
chatMessages.value[msgIndex] = { ...msg, interaction: undefined };
}
}
const userMsg: AgentMessage = {
id: generateId(),
role: 'user',
content:
waitingConfig.value?.cancel_text ||
$t('ai-platform.agent.chatPanel.cancelText'),
status: 'completed',
reasoning_steps: [],
tool_calls: [],
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
elapsed_time: 0,
error_message: '',
feedback: '',
created_at: new Date().toISOString(),
};
chatMessages.value.push(userMsg);
waitingForInput.value = false;
waitingConfig.value = null;
doSendMessage('false', false);
}
// 格式化交互值显示
function formatInteractionValue(value: any) {
if (value === true) {
return (
waitingConfig.value?.confirm_text ||
$t('ai-platform.agent.chatPanel.confirm')
);
}
if (value === false) {
return (
waitingConfig.value?.cancel_text ||
$t('ai-platform.agent.chatPanel.cancelText')
);
}
if (Array.isArray(value)) {
const options = waitingConfig.value?.options || [];
const labels = value.map((v: string) => {
const opt = options.find((o: any) => o.value === v);
return opt?.label || v;
});
return labels.join(', ');
}
if (waitingConfig.value?.type === 'choice') {
const options = waitingConfig.value?.options || [];
const opt = options.find((o: any) => o.value === value);
return opt?.label || value;
}
return String(value);
}
// 滚动到底部
function scrollToBottom() {
chatBoxRef.value?.scrollToBottom();
}
// 清空对话
function clearChat() {
chatMessages.value = [];
conversationId.value = null;
streamingContent.value = '';
streamingSteps.value = [];
waitingForInput.value = false;
waitingConfig.value = null;
}
// 添加消息(语音消息用)
function handleAddMessage(message: any) {
const agentMsg: AgentMessage = {
id: message.id || generateId(),
role: message.role || 'user',
content: message.content || '',
status: 'completed',
reasoning_steps: [],
tool_calls: [],
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
elapsed_time: 0,
error_message: '',
feedback: '',
created_at: new Date().toISOString(),
voice: message.voice,
};
chatMessages.value.push(agentMsg);
scrollToBottom();
}
// 更新消息(语音识别完成后用)
function handleUpdateMessage(messageId: string, updates: any) {
const msgIndex = chatMessages.value.findIndex((m) => m.id === messageId);
if (msgIndex !== -1) {
const msg = chatMessages.value[msgIndex]!;
chatMessages.value[msgIndex] = { ...msg, ...updates };
// 如果语音识别成功,触发发送
if (updates.content && !updates.content.startsWith('[')) {
doSendMessage(updates.content, false);
}
}
}
// 消息反馈
async function handleChatFeedback(
messageId: string,
feedback: 'dislike' | 'like',
) {
if (messageId.startsWith('temp-') || messageId.startsWith('msg-')) return;
try {
await feedbackAgentMessageApi(messageId, { feedback });
const msg = chatMessages.value.find((m) => m.id === messageId);
if (msg) {
msg.feedback = feedback;
}
ElMessage.success($t('ai-platform.agent.chatPanel.feedbackThanks'));
} catch (error) {
console.error('反馈失败:', error);
}
}
// 设计面板确认
function handleDesignConfirm(data: Record<string, any>) {
showDesignPanel.value = false;
// 添加用户确认消息
const userMsg: AgentMessage = {
id: generateId(),
role: 'user',
content: $t('ai-platform.agent.chatPanel.confirmDesign'),
status: 'completed',
reasoning_steps: [],
tool_calls: [],
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
elapsed_time: 0,
error_message: '',
feedback: '',
created_at: new Date().toISOString(),
};
chatMessages.value.push(userMsg);
// 重置等待状态
waitingForInput.value = false;
waitingConfig.value = null;
currentDesign.value = null;
// 继续工作流,传递编辑后的数据
doSendMessage(JSON.stringify(data), false);
}
// 设计面板取消
function handleDesignCancel() {
showDesignPanel.value = false;
waitingForInput.value = false;
waitingConfig.value = null;
currentDesign.value = null;
}
// 应用设计面板确认
function handleAppDesignConfirm(data: Record<string, any>) {
showAppDesignPanel.value = false;
// 添加用户确认消息
const userMsg: AgentMessage = {
id: generateId(),
role: 'user',
content: $t('ai-platform.agent.chatPanel.confirmAppDesign'),
status: 'completed',
reasoning_steps: [],
tool_calls: [],
prompt_tokens: 0,
completion_tokens: 0,
total_tokens: 0,
elapsed_time: 0,
error_message: '',
feedback: '',
created_at: new Date().toISOString(),
};
chatMessages.value.push(userMsg);
// 重置等待状态
waitingForInput.value = false;
waitingConfig.value = null;
currentAppDesign.value = null;
// 继续工作流,传递编辑后的数据
doSendMessage(JSON.stringify(data), false);
}
// 应用设计面板取消
function handleAppDesignCancel() {
showAppDesignPanel.value = false;
waitingForInput.value = false;
waitingConfig.value = null;
currentAppDesign.value = null;
}
// 暴露方法给父组件
defineExpose({
clearChat,
scrollToBottom,
});
onUnmounted(() => {
cancelStream?.();
});
</script>
<template>
<div class="flex h-full overflow-hidden">
<!-- 聊天区域 -->
<div
class="h-full overflow-hidden"
:class="showDesignPanel ? 'w-[400px] flex-shrink-0' : 'flex-1'"
>
<ChatBox
ref="chatBoxRef"
:messages="formattedMessages"
:loading="sending"
:streaming-content="streamingContent"
:assistant-name="agent?.name"
:welcome-config="welcomeConfig"
:disabled="disabled"
:placeholder="
placeholder || $t('ai-platform.agent.chatPanel.inputPlaceholder')
"
:enable-feedback="true"
:enable-regenerate="false"
:enable-attachment="enableAttachment ?? false"
:enable-image="enableImage ?? false"
:enable-voice="enableVoice ?? true"
:show-clear-button="showClearButton ?? true"
class="h-full"
@send="handleSendMessage"
@feedback="handleChatFeedback"
@clear="clearChat"
@interaction-submit="handleInteractionSubmit"
@interaction-cancel="handleInteractionCancel"
@add-message="handleAddMessage"
@update-message="handleUpdateMessage"
/>
</div>
<!-- 设计面板 -->
<div
v-if="showDesignPanel && currentDesign"
class="flex flex-1 flex-col overflow-hidden"
>
<!-- 加载状态 - AI 工作中动画 -->
<AiWorkingAnimation
v-if="currentDesign.type === 'loading'"
:title="currentDesign.title"
:subtitle="$t('ai-platform.agent.chatPanel.analyzingAndGenerating')"
class="ml-3 h-full min-h-[400px] flex-1 rounded-lg"
/>
<!-- 设计编辑器 -->
<DesignEditorPanel
v-else
v-model:visible="showDesignPanel"
:design="{
type: currentDesign.type as any,
title: currentDesign.title,
data: currentDesign.data,
nodeId: currentDesign.nodeId,
form_fields: currentDesign.form_fields,
table_configs: currentDesign.table_configs,
}"
@confirm="handleDesignConfirm"
@close="handleDesignCancel"
/>
</div>
<!-- 应用设计面板 -->
<div
v-if="showAppDesignPanel && currentAppDesign"
class="flex flex-1 flex-col overflow-hidden"
>
<AppDesignPanel
:visible="showAppDesignPanel"
:design="currentAppDesign as any"
@update:visible="showAppDesignPanel = $event"
@confirm="handleAppDesignConfirm"
@close="handleAppDesignCancel"
/>
</div>
</div>
</template>
@@ -0,0 +1,193 @@
<script lang="ts" setup>
import type { MenuItem } from '#/components/zq-form/zq-menu-selector/types';
import { computed, ref, watch } from 'vue';
import { $t } from '@vben/locales';
import {
ElButton,
ElDialog,
ElForm,
ElFormItem,
ElInput,
ElInputNumber,
ElMessage,
ElScrollbar,
} from 'element-plus';
import { publishAgentToMenuApi } from '#/api/ai-platform/ai-platform';
import { ZqIconPicker } from '#/components/zq-form/zq-icon-picker';
import { ZqMenuSelector } from '#/components/zq-form/zq-menu-selector';
import { useAppContextStore } from '#/store/app-context';
interface Props {
modelValue: boolean;
agentId: string;
agentName: string;
agentCode?: string;
}
const props = defineProps<Props>();
const emit = defineEmits<{
published: [];
'update:modelValue': [value: boolean];
}>();
const appContextStore = useAppContextStore();
const dialogVisible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
});
const loading = ref(false);
// 发布表单
const publishForm = ref({
menu_name: '',
menu_parent_id: undefined as string | undefined,
menu_icon: 'lucide:bot',
menu_order: 0,
});
// 监听弹窗打开
watch(
() => props.modelValue,
(visible) => {
if (visible) {
// 重置表单,菜单名称默认为智能体名称
publishForm.value = {
menu_name: props.agentName,
menu_parent_id: undefined,
menu_icon: 'lucide:bot',
menu_order: 0,
};
}
},
);
// 菜单选择回调
function handleMenuChange(menu: MenuItem | MenuItem[] | null) {
publishForm.value.menu_parent_id =
menu && !Array.isArray(menu) ? menu.id : undefined;
}
async function handlePublish() {
if (!publishForm.value.menu_name) {
ElMessage.warning($t('ui.placeholder.input'));
return;
}
loading.value = true;
try {
await publishAgentToMenuApi(props.agentId, {
menu_name: publishForm.value.menu_name,
menu_parent_id: publishForm.value.menu_parent_id,
menu_icon: publishForm.value.menu_icon,
menu_order: publishForm.value.menu_order,
});
ElMessage.success($t('common.operationSuccess'));
emit('published');
dialogVisible.value = false;
} catch (error: any) {
ElMessage.error(error?.message || $t('common.operationFailed'));
} finally {
loading.value = false;
}
}
</script>
<template>
<ElDialog
v-model="dialogVisible"
:title="$t('ai-platform.agent.publishToMenu')"
width="500px"
destroy-on-close
align-center
append-to-body
>
<ElScrollbar max-height="60vh">
<div class="pr-4">
<ElForm :model="publishForm" label-width="100px">
<!-- 菜单配置 -->
<div class="mb-4 font-medium">
{{ $t('form-manager.publishDialog.menuConfig') }}
</div>
<ElFormItem
:label="$t('form-manager.publishDialog.menuName')"
required
>
<ElInput
v-model="publishForm.menu_name"
:placeholder="$t('form-manager.placeholder.name')"
/>
</ElFormItem>
<ElFormItem :label="$t('form-manager.publishDialog.parentMenu')">
<ZqMenuSelector
:model-value="publishForm.menu_parent_id || null"
mode="dialog"
:placeholder="
$t('form-manager.publishDialog.parentMenuPlaceholder')
"
:application-id="appContextStore.currentApp?.id"
@change="handleMenuChange"
/>
</ElFormItem>
<ElFormItem :label="$t('form-manager.publishDialog.menuIcon')">
<ZqIconPicker
v-model="publishForm.menu_icon"
prefix="lucide"
:auto-fetch-api="false"
class="w-full"
/>
</ElFormItem>
<ElFormItem :label="$t('common.sort')">
<ElInputNumber
v-model="publishForm.menu_order"
:min="0"
:max="999"
class="w-full"
/>
</ElFormItem>
<!-- 路由信息 -->
<template v-if="agentCode">
<div class="mb-4 mt-6 font-medium">
{{ $t('form-manager.publishDialog.routeInfo') }}
</div>
<ElFormItem :label="$t('form-manager.publishDialog.accessPath')">
<ElInput
:model-value="`/agent-chat/${agentCode}`"
disabled
class="text-muted-foreground"
/>
</ElFormItem>
</template>
</ElForm>
</div>
</ElScrollbar>
<template #footer>
<ElButton @click="dialogVisible = false">
{{ $t('common.cancel') }}
</ElButton>
<ElButton type="primary" :loading="loading" @click="handlePublish">
{{ $t('common.confirm') }}
</ElButton>
</template>
</ElDialog>
</template>
<style>
/* 确保 IconPicker 的弹出层在 Dialog 之上 */
.z-popup {
z-index: 2100 !important;
}
</style>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,642 @@
<script setup lang="ts">
import type {
AgentCreateInput,
AgentListItem,
} from '#/api/ai-platform/ai-platform';
import { onMounted, reactive, ref } from 'vue';
import { useRouter } from 'vue-router';
import { Page } from '@vben/common-ui';
import {
Bot,
CirclePlus,
Download,
Edit,
Menu,
MessageSquare,
MoreVertical,
Play,
Square,
Trash2,
Upload,
} from '@vben/icons';
import { $t } from '@vben/locales';
import {
ElButton,
ElCard,
ElDialog,
ElDropdown,
ElDropdownItem,
ElDropdownMenu,
ElEmpty,
ElForm,
ElFormItem,
ElInput,
ElMessage,
ElMessageBox,
ElOption,
ElPagination,
ElSelect,
ElSwitch,
ElTag,
ElTooltip,
} from 'element-plus';
import {
createAgentApi,
deleteAgentApi,
disableAgentApi,
exportAgentConfigApi,
getAgentListApi,
publishAgentApi,
unpublishAgentMenuApi,
} from '#/api/ai-platform/ai-platform';
import { useAppContextStore } from '#/store/app-context';
import PublishToMenuDialog from './components/PublishToMenuDialog.vue';
import ImportDialog from './modules/import-dialog.vue';
defineOptions({ name: 'AgentList' });
const router = useRouter();
const appContextStore = useAppContextStore();
// 是否在主应用下(只有主应用才显示“子应用可见”开关)
const isMainApp = !appContextStore.currentApp?.id;
// 搜索关键词
const searchKeyword = ref('');
// 列表数据
const loading = ref(false);
const agentList = ref<AgentListItem[]>([]);
// 分页
const pagination = reactive({
current: 1,
pageSize: 12,
total: 0,
});
// 发布到菜单弹窗
const showPublishToMenuDialog = ref(false);
const publishingAgent = ref<AgentListItem | null>(null);
// 导入弹窗
const showImportDialog = ref(false);
// 创建对话框
const createDialogVisible = ref(false);
const createForm = reactive<AgentCreateInput>({
name: '',
code: '',
description: '',
mode: 'autonomous',
is_global: false,
});
const createLoading = ref(false);
// 获取列表
async function fetchList() {
loading.value = true;
try {
const res = await getAgentListApi({
page: pagination.current,
pageSize: pagination.pageSize,
name: searchKeyword.value || undefined,
applicationId: appContextStore.currentApp?.id,
});
agentList.value = res.items;
pagination.total = res.total;
} catch (error) {
console.error('获取智能体列表失败:', error);
} finally {
loading.value = false;
}
}
// 搜索
function handleSearch() {
pagination.current = 1;
fetchList();
}
// 分页变化
function handlePageChange(page: number) {
pagination.current = page;
fetchList();
}
// 每页条数变化
function handleSizeChange(size: number) {
pagination.pageSize = size;
pagination.current = 1;
fetchList();
}
// 打开创建对话框
function openCreateDialog() {
createForm.name = '';
createForm.code = '';
createForm.description = '';
createForm.mode = 'autonomous';
createForm.is_global = false;
createDialogVisible.value = true;
}
// 创建智能体
async function handleCreate() {
if (!createForm.name || !createForm.code) {
ElMessage.warning($t('ai-platform.agent.fillNameAndCode'));
return;
}
// 验证 code 格式
if (!/^[a-z]\w*$/i.test(createForm.code)) {
ElMessage.warning($t('ai-platform.agent.codeFormatError'));
return;
}
createLoading.value = true;
try {
const agent = await createAgentApi({
...createForm,
application_id: appContextStore.currentApp?.id,
});
ElMessage.success($t('ai-platform.agent.createSuccess'));
createDialogVisible.value = false;
// 跳转到编辑页(支持子应用模式)
router.push(
appContextStore.getContextPath(`/ai-platform/agent/editor/${agent.id}`),
);
} catch (error: any) {
ElMessage.error(error.message || $t('ai-platform.agent.createFailed'));
} finally {
createLoading.value = false;
}
}
// 编辑智能体
function handleEdit(agent: AgentListItem) {
router.push(
appContextStore.getContextPath(`/ai-platform/agent/editor/${agent.id}`),
);
}
// 对话
function handleChat(agent: AgentListItem) {
router.push(
appContextStore.getContextPath(`/ai-platform/agent/chat/${agent.id}`),
);
}
// 发布
async function handlePublish(agent: AgentListItem) {
try {
await publishAgentApi(agent.id);
ElMessage.success($t('ai-platform.agent.publishSuccess'));
fetchList();
} catch (error: any) {
ElMessage.error(error.message || $t('ai-platform.agent.publishFailed'));
}
}
// 停用
async function handleDisable(agent: AgentListItem) {
try {
await disableAgentApi(agent.id);
ElMessage.success($t('ai-platform.agent.disableSuccess'));
fetchList();
} catch (error: any) {
ElMessage.error(error.message || $t('ai-platform.agent.operationFailed'));
}
}
// 删除
async function handleDelete(agent: AgentListItem) {
try {
await ElMessageBox.confirm(
$t('ai-platform.agent.deleteConfirm', { name: agent.name }),
$t('ai-platform.agent.deleteTitle'),
{ type: 'warning' },
);
await deleteAgentApi(agent.id);
ElMessage.success($t('ai-platform.agent.deleteSuccess'));
fetchList();
} catch (error: any) {
if (error !== 'cancel') {
ElMessage.error(error.message || $t('ai-platform.agent.deleteFailed'));
}
}
}
// 获取状态标签类型
function getStatusType(status: string) {
switch (status) {
case 'disabled': {
return 'danger';
}
case 'published': {
return 'success';
}
default: {
return 'info';
}
}
}
// 获取状态文本
function getStatusText(status: string) {
switch (status) {
case 'disabled': {
return $t('ai-platform.agent.status.disabled');
}
case 'published': {
return $t('ai-platform.agent.status.published');
}
default: {
return $t('ai-platform.agent.status.draft');
}
}
}
// 获取模式文本
function getModeText(mode: string) {
return mode === 'autonomous'
? $t('ai-platform.agent.mode.autonomous')
: $t('ai-platform.agent.mode.dialogFlow');
}
// 发布到菜单
function handlePublishToMenu(agent: AgentListItem) {
publishingAgent.value = agent;
showPublishToMenuDialog.value = true;
}
// 发布到菜单成功回调
function handlePublishedToMenu() {
fetchList();
}
// 取消发布菜单
async function handleUnpublishMenu(agent: AgentListItem) {
try {
await unpublishAgentMenuApi(agent.id);
ElMessage.success($t('ai-platform.agent.unpublishMenuSuccess'));
fetchList();
} catch (error: any) {
ElMessage.error(error.message || $t('ai-platform.agent.operationFailed'));
}
}
function downloadBlob(blob: Blob, filename: string) {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
document.body.append(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
}
async function handleExport(agent: AgentListItem) {
try {
const blob = await exportAgentConfigApi(agent.id);
downloadBlob(blob, `${agent.code}.json`);
ElMessage.success($t('ai-platform.agent.importExport.exportSuccess'));
} catch {
ElMessage.error($t('ai-platform.agent.importExport.exportFailed'));
}
}
function handleImported() {
fetchList();
}
// 处理更多菜单命令
function handleCommand(command: string, agent: AgentListItem) {
switch (command) {
case 'export': {
handleExport(agent);
break;
}
case 'chat': {
handleChat(agent);
break;
}
case 'disable': {
handleDisable(agent);
break;
}
case 'publish': {
handlePublish(agent);
break;
}
case 'publishToMenu': {
handlePublishToMenu(agent);
break;
}
case 'unpublishMenu': {
handleUnpublishMenu(agent);
break;
}
}
}
onMounted(() => {
fetchList();
});
</script>
<template>
<div class="agent-list-page">
<Page auto-content-height v-loading="loading">
<template #title>
<div class="flex items-center justify-between">
<div class="flex items-center gap-4">
<ElInput
v-model="searchKeyword"
:placeholder="$t('ai-platform.agent.searchPlaceholder')"
clearable
class="w-64"
@keyup.enter="handleSearch"
/>
<ElButton @click="handleSearch">
{{ $t('ai-platform.agent.search') }}
</ElButton>
</div>
<div class="flex items-center gap-2">
<ElButton @click="showImportDialog = true">
<Upload class="mr-1 h-4 w-4" />
{{ $t('ai-platform.agent.importExport.import') }}
</ElButton>
<ElButton type="primary" @click="openCreateDialog">
<CirclePlus class="mr-1 h-4 w-4" />
{{ $t('ai-platform.agent.create') }}
</ElButton>
</div>
</div>
</template>
<!-- 智能体卡片列表 -->
<div
v-if="agentList.length > 0"
class="grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5"
>
<ElCard
v-for="agent in agentList"
:key="agent.id"
class="agent-card cursor-pointer transition-shadow"
shadow="hover"
:body-style="{ padding: '0' }"
style="border: none"
@click="handleEdit(agent)"
>
<div class="p-4">
<!-- 头部图标 + 右侧信息区 -->
<div class="mb-4 flex gap-3">
<div
class="bg-primary/10 flex h-10 w-10 flex-shrink-0 items-center justify-center rounded-lg"
>
<Bot class="text-primary h-5 w-5" />
</div>
<div class="min-w-0 flex-1">
<!-- name + 操作 -->
<div class="flex items-center justify-between">
<div class="min-w-0 flex-1 truncate text-sm font-medium">
{{ agent.name }}
</div>
<div
class="flex flex-shrink-0 items-center -space-x-1"
@click.stop
>
<ElTooltip
:content="$t('ai-platform.agent.edit')"
placement="top"
>
<ElButton text size="small" @click="handleEdit(agent)">
<Edit class="h-3.5 w-3.5" />
</ElButton>
</ElTooltip>
<ElTooltip
:content="$t('ai-platform.agent.delete')"
placement="top"
>
<ElButton text size="small" @click="handleDelete(agent)">
<Trash2 class="h-3.5 w-3.5" />
</ElButton>
</ElTooltip>
<ElDropdown
trigger="click"
@command="(cmd: string) => handleCommand(cmd, agent)"
>
<ElButton text size="small">
<MoreVertical class="h-3.5 w-3.5" />
</ElButton>
<template #dropdown>
<ElDropdownMenu>
<ElDropdownItem
v-if="agent.status === 'published'"
command="chat"
>
<MessageSquare class="mr-2 h-4 w-4" />
{{ $t('ai-platform.agent.chat') }}
</ElDropdownItem>
<ElDropdownItem
v-if="agent.status === 'draft'"
command="publish"
>
<Play class="mr-2 h-4 w-4" />
{{ $t('ai-platform.agent.publish') }}
</ElDropdownItem>
<ElDropdownItem
v-if="
agent.status === 'published' && !agent.has_menu
"
command="publishToMenu"
>
<Menu class="mr-2 h-4 w-4" />
{{ $t('ai-platform.agent.publishToMenu') }}
</ElDropdownItem>
<ElDropdownItem
v-if="
agent.status === 'published' && agent.has_menu
"
command="unpublishMenu"
>
<Square class="mr-2 h-4 w-4" />
{{ $t('ai-platform.agent.unpublishMenu') }}
</ElDropdownItem>
<ElDropdownItem command="export">
<Download class="mr-2 h-4 w-4" />
{{ $t('ai-platform.agent.importExport.export') }}
</ElDropdownItem>
<ElDropdownItem
v-if="agent.status === 'published'"
command="disable"
>
<Square class="mr-2 h-4 w-4" />
{{ $t('ai-platform.agent.disable') }}
</ElDropdownItem>
</ElDropdownMenu>
</template>
</ElDropdown>
</div>
</div>
<!-- code -->
<div class="text-muted-foreground font-mono text-xs">
{{ agent.code }}
</div>
</div>
</div>
<!-- 描述 -->
<div
class="text-muted-foreground mb-4 line-clamp-1 min-h-[18px] text-xs"
>
{{ agent.description || $t('ai-platform.agent.noDescription') }}
</div>
<!-- 标签 -->
<div class="mb-4 flex items-center justify-between">
<div class="flex gap-1">
<ElTag size="small" :type="getStatusType(agent.status) as any">
{{ getStatusText(agent.status) }}
</ElTag>
<ElTag size="small" type="info">
{{ getModeText(agent.mode) }}
</ElTag>
</div>
</div>
<!-- 应用名称 + 创建时间 -->
<div class="flex items-center justify-between">
<div class="text-muted-foreground text-xs">
<span v-if="agent.application_name">{{
agent.application_name
}}</span>
<span v-else>{{ $t('ai-platform.agent.mainApp') }}</span>
</div>
<span class="text-muted-foreground text-xs">
{{ agent.sys_create_datetime }}
</span>
</div>
</div>
</ElCard>
</div>
<!-- 空状态 -->
<ElEmpty v-else :description="$t('ai-platform.agent.empty')" />
<!-- 分页 -->
<template #footer>
<div class="flex w-full items-center justify-end">
<ElPagination
v-model:current-page="pagination.current"
v-model:page-size="pagination.pageSize"
:total="pagination.total"
:page-sizes="[12, 24, 36, 48]"
:pager-count="7"
layout="total, sizes, prev, pager, next, jumper"
background
size="small"
@current-change="handlePageChange"
@size-change="handleSizeChange"
/>
</div>
</template>
</Page>
<ImportDialog v-model="showImportDialog" @imported="handleImported" />
<!-- 发布到菜单弹窗 -->
<PublishToMenuDialog
v-if="publishingAgent"
v-model="showPublishToMenuDialog"
:agent-id="publishingAgent.id"
:agent-name="publishingAgent.name"
:agent-code="publishingAgent.code"
@published="handlePublishedToMenu"
/>
<!-- 创建对话框 -->
<ElDialog
v-model="createDialogVisible"
:title="$t('ai-platform.agent.create')"
width="500px"
>
<ElForm label-width="80px">
<ElFormItem :label="$t('ai-platform.agent.form.name')" required>
<ElInput
v-model="createForm.name"
:placeholder="$t('ai-platform.agent.form.namePlaceholder')"
/>
</ElFormItem>
<ElFormItem :label="$t('ai-platform.agent.form.code')" required>
<ElInput
v-model="createForm.code"
:placeholder="$t('ai-platform.agent.form.codePlaceholder')"
/>
</ElFormItem>
<ElFormItem :label="$t('ai-platform.agent.form.mode')">
<ElSelect v-model="createForm.mode" style="width: 100%">
<ElOption
:label="$t('ai-platform.agent.mode.autonomousLabel')"
value="autonomous"
>
<div class="flex items-center gap-2">
<div>
<div>{{ $t('ai-platform.agent.mode.autonomousLabel') }}</div>
<div class="text-muted-foreground text-xs">
{{ $t('ai-platform.agent.mode.autonomousDesc') }}
</div>
</div>
</div>
</ElOption>
<ElOption
:label="$t('ai-platform.agent.mode.dialogFlowLabel')"
value="dialog_flow"
>
<div class="flex items-center gap-2">
<div>
<div>{{ $t('ai-platform.agent.mode.dialogFlowLabel') }}</div>
<div class="text-muted-foreground text-xs">
{{ $t('ai-platform.agent.mode.dialogFlowDesc') }}
</div>
</div>
</div>
</ElOption>
</ElSelect>
</ElFormItem>
<ElFormItem :label="$t('ai-platform.agent.form.description')">
<ElInput
v-model="createForm.description"
:rows="3"
:placeholder="$t('ai-platform.agent.form.descriptionPlaceholder')"
type="textarea"
/>
</ElFormItem>
<ElFormItem
v-if="isMainApp"
:label="$t('ai-platform.agent.form.globalVisible')"
>
<ElSwitch v-model="createForm.is_global" />
</ElFormItem>
</ElForm>
<template #footer>
<ElButton @click="createDialogVisible = false">
{{ $t('ai-platform.agent.cancel') }}
</ElButton>
<ElButton :loading="createLoading" type="primary" @click="handleCreate">
{{ $t('ai-platform.agent.create') }}
</ElButton>
</template>
</ElDialog>
</div>
</template>
<style scoped>
.agent-card :deep(.el-card__body) {
padding: 0;
}
</style>
@@ -0,0 +1,278 @@
<script lang="ts" setup>
import type {
AgentImportCheckResult,
AgentImportInput,
} from '#/api/ai-platform/ai-platform';
import { computed, ref, watch } from 'vue';
import { Upload } from '@vben/icons';
import { $t } from '@vben/locales';
import {
ElAlert,
ElButton,
ElDescriptions,
ElDescriptionsItem,
ElInput,
ElMessage,
ElUpload,
} from 'element-plus';
import {
checkImportAgentApi,
importAgentConfigApi,
} from '#/api/ai-platform/ai-platform';
import { ZqDialog } from '#/components/zq-dialog';
import { useAppContextStore } from '#/store/app-context';
interface Props {
modelValue: boolean;
}
const props = defineProps<Props>();
const emit = defineEmits<{
imported: [];
'update:modelValue': [value: boolean];
}>();
const appContextStore = useAppContextStore();
const dialogVisible = computed({
get: () => props.modelValue,
set: (val) => emit('update:modelValue', val),
});
type Step = 'check' | 'result' | 'upload';
const step = ref<Step>('upload');
const loading = ref(false);
const importData = ref<AgentImportInput | null>(null);
const checkResult = ref<AgentImportCheckResult | null>(null);
const newCode = ref('');
function resetState() {
step.value = 'upload';
loading.value = false;
importData.value = null;
checkResult.value = null;
newCode.value = '';
}
watch(dialogVisible, (val) => {
if (!val) resetState();
});
const canConfirmImport = computed(() => {
if (!checkResult.value || !importData.value) return false;
if (checkResult.value.code_exists && !newCode.value.trim()) return false;
return true;
});
const showFooter = computed(() => step.value === 'result');
function readFileAsJson(file: File): Promise<AgentImportInput> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => {
try {
resolve(JSON.parse(reader.result as string));
} catch {
reject(new Error('parse'));
}
};
reader.onerror = () => reject(new Error('read'));
reader.readAsText(file);
});
}
async function handleFileChange(file: any) {
const rawFile = (file.raw || file) as File;
if (!rawFile) return;
loading.value = true;
try {
const data = await readFileAsJson(rawFile);
if (!data.name || !data.code) {
ElMessage.error($t('ai-platform.agent.importExport.fileParseError'));
return;
}
importData.value = data;
newCode.value = data.code;
step.value = 'check';
await runCheck();
} catch {
ElMessage.error($t('ai-platform.agent.importExport.fileParseError'));
} finally {
loading.value = false;
}
}
async function runCheck() {
if (!importData.value) return;
loading.value = true;
try {
checkResult.value = await checkImportAgentApi({
code: importData.value.code,
});
step.value = 'result';
} catch (error: any) {
ElMessage.error(
error?.message || $t('ai-platform.agent.importExport.importFailed'),
);
} finally {
loading.value = false;
}
}
async function handleConfirmImport() {
if (!importData.value || !canConfirmImport.value) return;
loading.value = true;
try {
const payload: AgentImportInput = {
...importData.value,
application_id: appContextStore.currentApp?.id,
code: checkResult.value?.code_exists
? newCode.value.trim()
: importData.value.code,
};
await importAgentConfigApi(payload);
ElMessage.success($t('ai-platform.agent.importExport.importSuccess'));
dialogVisible.value = false;
emit('imported');
} catch (error: any) {
ElMessage.error(
error?.message || $t('ai-platform.agent.importExport.importFailed'),
);
} finally {
loading.value = false;
}
}
</script>
<template>
<ZqDialog
v-model="dialogVisible"
:title="$t('ai-platform.agent.importExport.importTitle')"
:confirm-loading="loading"
width="520px"
:show-footer="showFooter"
@confirm="handleConfirmImport"
>
<template #footer>
<ElButton @click="resetState">
{{ $t('ai-platform.agent.importExport.reselect') }}
</ElButton>
<ElButton
type="primary"
:loading="loading"
:disabled="!canConfirmImport"
@click="handleConfirmImport"
>
{{ $t('ai-platform.agent.importExport.confirmImport') }}
</ElButton>
</template>
<div v-if="step === 'upload' || step === 'check'" class="py-4">
<ElUpload
drag
:auto-upload="false"
:show-file-list="false"
accept=".json"
:disabled="loading"
@change="handleFileChange"
>
<div class="flex flex-col items-center py-6">
<Upload class="text-muted-foreground mb-3 h-10 w-10" />
<div class="text-sm">
{{ $t('ai-platform.agent.importExport.dragOrClick') }}
</div>
<div class="text-muted-foreground mt-1 text-xs">
{{ $t('ai-platform.agent.importExport.onlyJson') }}
</div>
</div>
</ElUpload>
<div
v-if="loading && step === 'check'"
class="text-muted-foreground mt-4 text-center text-sm"
>
{{ $t('ai-platform.agent.importExport.checking') }}
</div>
</div>
<div v-if="step === 'result' && importData && checkResult" class="space-y-4">
<ElAlert
:title="$t('ai-platform.agent.importExport.appTip')"
type="info"
show-icon
:closable="false"
/>
<ElAlert
:title="$t('ai-platform.agent.importExport.refTip')"
type="warning"
show-icon
:closable="false"
/>
<div>
<div class="mb-2 text-sm font-medium">
{{ $t('ai-platform.agent.importExport.agentInfo') }}
</div>
<ElDescriptions :column="1" border size="small">
<ElDescriptionsItem :label="$t('ai-platform.agent.form.name')">
{{ importData.name }}
</ElDescriptionsItem>
<ElDescriptionsItem :label="$t('ai-platform.agent.form.code')">
{{ importData.code }}
</ElDescriptionsItem>
<ElDescriptionsItem
v-if="importData.mode"
:label="$t('ai-platform.agent.form.mode')"
>
{{ importData.mode }}
</ElDescriptionsItem>
<ElDescriptionsItem
v-if="importData.description"
:label="$t('ai-platform.agent.form.description')"
>
{{ importData.description }}
</ElDescriptionsItem>
<ElDescriptionsItem
v-if="importData.model_name"
:label="$t('ai-platform.agent.importExport.modelName')"
>
{{ importData.model_name }}
</ElDescriptionsItem>
<ElDescriptionsItem
v-if="importData.workflow_code"
:label="$t('ai-platform.agent.importExport.workflowCode')"
>
{{ importData.workflow_code }}
</ElDescriptionsItem>
</ElDescriptions>
</div>
<div v-if="checkResult.code_exists">
<ElAlert
:title="$t('ai-platform.agent.importExport.codeConflictTip')"
type="warning"
show-icon
:closable="false"
class="mb-3"
/>
<ElInput
v-model="newCode"
:placeholder="$t('ai-platform.agent.importExport.newCodePlaceholder')"
/>
</div>
<ElAlert
v-else
:title="$t('ai-platform.agent.importExport.codeAvailable')"
type="success"
show-icon
:closable="false"
/>
</div>
</ZqDialog>
</template>