fix: wire ai platform run and codex chat
This commit is contained in:
@@ -62,6 +62,17 @@ function createLightAiMenuRoute(
|
||||
}
|
||||
|
||||
const LIGHT_AI_PLATFORM_MENU_ROUTES = [
|
||||
createLightAiMenuRoute({
|
||||
component: '/_core/agent-chat/index',
|
||||
meta: {
|
||||
fullPathKey: true,
|
||||
icon: 'lucide:code',
|
||||
order: 25,
|
||||
title: 'menu-title.codex',
|
||||
},
|
||||
name: 'CodexAgentChat',
|
||||
path: '/agent-chat/codex',
|
||||
}),
|
||||
createLightAiMenuRoute({
|
||||
component: '/ai-platform/model/index',
|
||||
meta: {
|
||||
|
||||
@@ -7,6 +7,7 @@ export const LIGHT_MENU_NAMES = new Set([
|
||||
'AIWorkflowRuns',
|
||||
'AnnouncementList',
|
||||
'AnnouncementManage',
|
||||
'CodexAgentChat',
|
||||
'KnowledgeBase',
|
||||
'PageRenderMainHome',
|
||||
'SystemManagement',
|
||||
@@ -23,6 +24,7 @@ const LIGHT_ROUTE_PATH_PREFIXES = [
|
||||
'/ai-platform/model',
|
||||
'/ai-platform/workflow',
|
||||
'/ai-platform/workflow-runs',
|
||||
'/agent-chat',
|
||||
'/message/announcement',
|
||||
'/page-render/main_home',
|
||||
'/system/menu',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import type {
|
||||
DefaultModel,
|
||||
ModelListItem,
|
||||
ProviderCreateInput,
|
||||
ProviderListItem,
|
||||
ProviderType,
|
||||
} from '#/api/ai-platform/ai-platform';
|
||||
@@ -57,6 +58,7 @@ import {
|
||||
deleteProviderApi,
|
||||
fetchProviderModelsApi,
|
||||
getModelListApi,
|
||||
getProviderDetailApi,
|
||||
getProviderDefaultModelsApi,
|
||||
getProviderListApi,
|
||||
getProviderTypesApi,
|
||||
@@ -95,6 +97,7 @@ const providerForm = ref({
|
||||
name: '',
|
||||
provider_type: '',
|
||||
api_key: '',
|
||||
api_key_masked: '',
|
||||
api_base: '',
|
||||
ollama_host: 'http://localhost:11434',
|
||||
description: '',
|
||||
@@ -247,6 +250,7 @@ function handleAddProvider() {
|
||||
name: '',
|
||||
provider_type: '',
|
||||
api_key: '',
|
||||
api_key_masked: '',
|
||||
api_base: '',
|
||||
ollama_host: 'http://localhost:11434',
|
||||
description: '',
|
||||
@@ -255,28 +259,51 @@ function handleAddProvider() {
|
||||
showProviderDialog.value = true;
|
||||
}
|
||||
|
||||
function handleEditProvider(row: ProviderListItem) {
|
||||
async function handleEditProvider(row: ProviderListItem) {
|
||||
providerDialogMode.value = 'edit';
|
||||
providerForm.value = {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
provider_type: row.provider_type,
|
||||
api_key: '',
|
||||
api_base: '',
|
||||
ollama_host: 'http://localhost:11434',
|
||||
description: row.description,
|
||||
is_active: row.is_active,
|
||||
try {
|
||||
const detail = await getProviderDetailApi(row.id);
|
||||
providerForm.value = {
|
||||
id: detail.id,
|
||||
name: detail.name,
|
||||
provider_type: detail.provider_type,
|
||||
api_key: '',
|
||||
api_key_masked: detail.api_key_masked || '',
|
||||
api_base: detail.api_base || '',
|
||||
ollama_host: detail.ollama_host || 'http://localhost:11434',
|
||||
description: detail.description || '',
|
||||
is_active: detail.is_active,
|
||||
};
|
||||
showProviderDialog.value = true;
|
||||
} catch (error: any) {
|
||||
ElMessage.error(error.message || $t('ui.actionMessage.operationFailed'));
|
||||
}
|
||||
}
|
||||
|
||||
function buildProviderPayload() {
|
||||
const payload: ProviderCreateInput = {
|
||||
name: providerForm.value.name,
|
||||
provider_type: providerForm.value.provider_type,
|
||||
api_base: providerForm.value.api_base,
|
||||
ollama_host: providerForm.value.ollama_host,
|
||||
description: providerForm.value.description,
|
||||
is_active: providerForm.value.is_active,
|
||||
};
|
||||
showProviderDialog.value = true;
|
||||
const apiKey = providerForm.value.api_key.trim();
|
||||
if (apiKey) {
|
||||
payload.api_key = apiKey;
|
||||
}
|
||||
return payload;
|
||||
}
|
||||
|
||||
async function handleSaveProvider() {
|
||||
try {
|
||||
const payload = buildProviderPayload();
|
||||
if (providerDialogMode.value === 'add') {
|
||||
await createProviderApi(providerForm.value);
|
||||
await createProviderApi(payload);
|
||||
ElMessage.success($t('ui.actionMessage.operationSuccess'));
|
||||
} else {
|
||||
await updateProviderApi(providerForm.value.id, providerForm.value);
|
||||
await updateProviderApi(providerForm.value.id, payload);
|
||||
ElMessage.success($t('ui.actionMessage.operationSuccess'));
|
||||
}
|
||||
showProviderDialog.value = false;
|
||||
@@ -889,9 +916,19 @@ defineExpose({ reset });
|
||||
<ElInput
|
||||
v-model="providerForm.api_key"
|
||||
type="password"
|
||||
:placeholder="$t('ui.placeholder.input')"
|
||||
:placeholder="
|
||||
providerDialogMode === 'edit' && providerForm.api_key_masked
|
||||
? '已配置,留空不修改'
|
||||
: $t('ui.placeholder.input')
|
||||
"
|
||||
show-password
|
||||
/>
|
||||
<div
|
||||
v-if="providerDialogMode === 'edit' && providerForm.api_key_masked"
|
||||
class="text-muted-foreground mt-1 text-xs"
|
||||
>
|
||||
当前 Key:{{ providerForm.api_key_masked }},留空不会修改。
|
||||
</div>
|
||||
</ElFormItem>
|
||||
<ElFormItem
|
||||
v-if="providerForm.provider_type !== 'ollama'"
|
||||
|
||||
@@ -9,7 +9,14 @@ import { useRouter } from 'vue-router';
|
||||
import { ExternalLink } from '@vben/icons';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { ElAlert, ElButton, ElTag } from 'element-plus';
|
||||
import {
|
||||
ElAlert,
|
||||
ElButton,
|
||||
ElCollapse,
|
||||
ElCollapseItem,
|
||||
ElScrollbar,
|
||||
ElTag,
|
||||
} from 'element-plus';
|
||||
|
||||
import { getWorkflowDetailApi, getWorkflowRunDetailApi } from '#/api/ai-platform/ai-platform';
|
||||
import { ZqDesc, ZqDescItem } from '#/components/zq-desc';
|
||||
@@ -43,8 +50,10 @@ const nodes = ref<Node[]>([]);
|
||||
const edges = ref<Edge[]>([]);
|
||||
const activeNodeId = ref('');
|
||||
const definitionFallback = ref(false);
|
||||
const activeLogNames = ref<string[]>([]);
|
||||
|
||||
const dialogTitle = computed(() => run.value?.workflow_name || $t('ai-platform.workflowRuns.detail.runId'));
|
||||
const executionLogs = computed(() => run.value?.execution_log || []);
|
||||
|
||||
async function loadDetail() {
|
||||
if (!runId.value) return;
|
||||
@@ -78,6 +87,7 @@ async function loadDetail() {
|
||||
const replayed = applyRunReplay(base.nodes, base.edges, detail);
|
||||
nodes.value = replayed.nodes;
|
||||
edges.value = replayed.edges;
|
||||
activeLogNames.value = executionLogs.value.slice(0, 1).map((_, index) => String(index));
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
@@ -109,6 +119,40 @@ function triggerLabel(value?: string) {
|
||||
return getTagLabel(value, triggerOptions);
|
||||
}
|
||||
|
||||
function getLogStatusType(status?: string) {
|
||||
if (status === 'completed' || status === 'success') return 'success';
|
||||
if (status === 'failed') return 'danger';
|
||||
if (status === 'waiting') return 'warning';
|
||||
if (status === 'running') return 'primary';
|
||||
return 'info';
|
||||
}
|
||||
|
||||
function getLogStatusText(status?: string) {
|
||||
const map: Record<string, string> = {
|
||||
completed: $t('ai-platform.workflowRuns.status.completed'),
|
||||
failed: $t('ai-platform.workflowRuns.status.failed'),
|
||||
running: $t('ai-platform.workflowRuns.status.running'),
|
||||
success: $t('ai-platform.workflowRuns.status.completed'),
|
||||
waiting: $t('ai-platform.workflowRuns.status.waiting'),
|
||||
};
|
||||
return status ? map[status] || status : '-';
|
||||
}
|
||||
|
||||
function previewValue(value: any) {
|
||||
if (value === undefined || value === null || value === '') return '-';
|
||||
if (typeof value === 'string') return value;
|
||||
try {
|
||||
return JSON.stringify(value, null, 2);
|
||||
} catch {
|
||||
return String(value);
|
||||
}
|
||||
}
|
||||
|
||||
function selectLog(nodeId?: string) {
|
||||
if (!nodeId) return;
|
||||
activeNodeId.value = nodeId;
|
||||
}
|
||||
|
||||
defineExpose({ open });
|
||||
</script>
|
||||
|
||||
@@ -192,12 +236,79 @@ defineExpose({ open });
|
||||
</ZqDescItem>
|
||||
</ZqDesc>
|
||||
|
||||
<div class="run-detail-canvas border-border overflow-hidden rounded-lg border">
|
||||
<WorkflowReadonlyCanvas
|
||||
:nodes="nodes"
|
||||
:edges="edges"
|
||||
:fit-node-id="activeNodeId"
|
||||
/>
|
||||
<div class="run-detail-main grid min-h-0 flex-1 grid-cols-[minmax(0,1fr)_420px] gap-4">
|
||||
<div class="run-detail-canvas border-border min-h-0 overflow-hidden rounded-lg border">
|
||||
<WorkflowReadonlyCanvas
|
||||
:nodes="nodes"
|
||||
:edges="edges"
|
||||
:fit-node-id="activeNodeId"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="border-border flex min-h-0 flex-col rounded-lg border">
|
||||
<div class="border-border flex items-center justify-between border-b px-3 py-2">
|
||||
<div class="text-sm font-medium">
|
||||
{{ $t('ai-platform.workflowRuns.detail.timeline') }}
|
||||
</div>
|
||||
<ElTag size="small">{{ executionLogs.length }}</ElTag>
|
||||
</div>
|
||||
<ElScrollbar class="flex-1">
|
||||
<div class="p-3">
|
||||
<ElAlert
|
||||
v-if="executionLogs.length === 0"
|
||||
type="info"
|
||||
:closable="false"
|
||||
:title="$t('ai-platform.workflowRuns.detail.noLogs')"
|
||||
/>
|
||||
<ElCollapse v-else v-model="activeLogNames">
|
||||
<ElCollapseItem
|
||||
v-for="(log, index) in executionLogs"
|
||||
:key="`${log.node_id}-${index}`"
|
||||
:name="String(index)"
|
||||
>
|
||||
<template #title>
|
||||
<div
|
||||
class="flex min-w-0 flex-1 items-center gap-2"
|
||||
@click="selectLog(log.node_id)"
|
||||
>
|
||||
<ElTag :type="getLogStatusType(log.status)" size="small">
|
||||
{{ getLogStatusText(log.status) }}
|
||||
</ElTag>
|
||||
<span class="truncate text-sm">
|
||||
{{ log.node_label || log.node_id }}
|
||||
</span>
|
||||
<span class="text-muted-foreground text-xs">
|
||||
{{ formatDuration(log.elapsed_time) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="space-y-3 text-xs">
|
||||
<div v-if="log.error" class="text-destructive break-words">
|
||||
{{ log.error }}
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-muted-foreground mb-1 font-medium">
|
||||
{{ $t('ai-platform.workflowRuns.detail.inputsOutputs') }} - 输入
|
||||
</div>
|
||||
<pre class="run-json">{{ previewValue(log.inputs) }}</pre>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-muted-foreground mb-1 font-medium">
|
||||
{{ $t('ai-platform.workflowRuns.detail.inputsOutputs') }} - 输出
|
||||
</div>
|
||||
<pre class="run-json">{{ previewValue(log.output) }}</pre>
|
||||
</div>
|
||||
<div class="text-muted-foreground flex gap-3">
|
||||
<span>Token: {{ log.tokens_used || 0 }}</span>
|
||||
<span>{{ log.node_type }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</ElCollapseItem>
|
||||
</ElCollapse>
|
||||
</div>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ZqDialog>
|
||||
@@ -236,4 +347,20 @@ defineExpose({ open });
|
||||
min-height: 320px;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.workflow-run-detail-dialog .run-json {
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 6px;
|
||||
max-height: 180px;
|
||||
overflow: auto;
|
||||
padding: 8px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
@media (max-width: 1024px) {
|
||||
.workflow-run-detail-dialog .run-detail-main {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type {
|
||||
WorkflowListItem,
|
||||
WorkflowStreamEvent,
|
||||
WorkflowType,
|
||||
} from '#/api/ai-platform/ai-platform';
|
||||
|
||||
import { computed, onMounted, reactive, ref } from 'vue';
|
||||
import { computed, defineAsyncComponent, onMounted, onUnmounted, reactive, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import { Page } from '@vben/common-ui/es/page';
|
||||
@@ -24,6 +25,7 @@ import { $t } from '@vben/locales';
|
||||
import {
|
||||
ElButton,
|
||||
ElCard,
|
||||
ElDialog,
|
||||
ElDropdown,
|
||||
ElDropdownItem,
|
||||
ElDropdownMenu,
|
||||
@@ -35,6 +37,7 @@ import {
|
||||
ElMessageBox,
|
||||
ElOption,
|
||||
ElPagination,
|
||||
ElScrollbar,
|
||||
ElSelect,
|
||||
ElSwitch,
|
||||
ElTag,
|
||||
@@ -49,6 +52,7 @@ import {
|
||||
getWorkflowDetailApi,
|
||||
getWorkflowListApi,
|
||||
publishWorkflowApi,
|
||||
runWorkflowStreamApi,
|
||||
updateWorkflowApi,
|
||||
} from '#/api/ai-platform/ai-platform';
|
||||
import { ZqDialog } from '#/components/zq-dialog';
|
||||
@@ -57,6 +61,10 @@ import { useAppContextStore } from '#/store/app-context';
|
||||
import RunDialog from '../workflow/editor/components/RunDialog.vue';
|
||||
import ImportDialog from './modules/import-dialog.vue';
|
||||
|
||||
const DetailDialog = defineAsyncComponent(
|
||||
() => import('../workflow-runs/modules/detail-dialog.vue'),
|
||||
);
|
||||
|
||||
const router = useRouter();
|
||||
const appContextStore = useAppContextStore();
|
||||
const isMainApp = computed(() => appContextStore.isMainApp);
|
||||
@@ -96,6 +104,22 @@ const runDialogVisible = ref(false);
|
||||
const runWorkflowTarget = ref<any>(null);
|
||||
const runWorkflowNodes = ref<any[]>([]);
|
||||
const showImportDialog = ref(false);
|
||||
const runDetailRef = ref<any>();
|
||||
const runStatusDialogVisible = ref(false);
|
||||
const currentRunId = ref('');
|
||||
const runStatus = ref('pending');
|
||||
const runError = ref('');
|
||||
const runElapsedTime = ref(0);
|
||||
const runEvents = ref<
|
||||
Array<{
|
||||
detail?: string;
|
||||
id: string;
|
||||
status: 'failed' | 'info' | 'running' | 'success' | 'warning';
|
||||
time: string;
|
||||
title: string;
|
||||
}>
|
||||
>([]);
|
||||
let cancelWorkflowRunStream: (() => void) | null = null;
|
||||
|
||||
async function handleConfirm() {
|
||||
if (!formRef.value) return;
|
||||
@@ -232,6 +256,157 @@ const handleQuickRun = async (row: WorkflowListItem) => {
|
||||
}
|
||||
};
|
||||
|
||||
const runStatusType = computed(() => {
|
||||
const map: Record<string, string> = {
|
||||
completed: 'success',
|
||||
failed: 'danger',
|
||||
pending: 'info',
|
||||
running: 'primary',
|
||||
stopped: 'info',
|
||||
waiting: 'warning',
|
||||
};
|
||||
return map[runStatus.value] || 'info';
|
||||
});
|
||||
|
||||
function getRunStatusText(status: string) {
|
||||
const map: Record<string, string> = {
|
||||
completed: '已完成',
|
||||
failed: '失败',
|
||||
pending: '等待中',
|
||||
running: '运行中',
|
||||
stopped: '已停止',
|
||||
waiting: '等待输入',
|
||||
};
|
||||
return map[status] || status;
|
||||
}
|
||||
|
||||
function formatRunDuration(ms?: number) {
|
||||
if (!ms && ms !== 0) return '-';
|
||||
if (ms < 1000) return `${ms}ms`;
|
||||
return `${(ms / 1000).toFixed(2)}s`;
|
||||
}
|
||||
|
||||
function pushRunEvent(
|
||||
title: string,
|
||||
status: 'failed' | 'info' | 'running' | 'success' | 'warning' = 'info',
|
||||
detail?: string,
|
||||
) {
|
||||
runEvents.value.push({
|
||||
detail,
|
||||
id: `${Date.now()}-${runEvents.value.length}`,
|
||||
status,
|
||||
time: new Date().toLocaleTimeString(),
|
||||
title,
|
||||
});
|
||||
}
|
||||
|
||||
function resetRunState() {
|
||||
currentRunId.value = '';
|
||||
runStatus.value = 'running';
|
||||
runError.value = '';
|
||||
runElapsedTime.value = 0;
|
||||
runEvents.value = [];
|
||||
}
|
||||
|
||||
function handleWorkflowRunEvent(event: WorkflowStreamEvent) {
|
||||
switch (event.type) {
|
||||
case 'start': {
|
||||
currentRunId.value = event.run_id || '';
|
||||
runStatus.value = 'running';
|
||||
pushRunEvent(
|
||||
'已创建运行记录',
|
||||
'success',
|
||||
currentRunId.value ? `Run ID: ${currentRunId.value}` : undefined,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'node_start': {
|
||||
pushRunEvent(
|
||||
`开始执行:${event.node_label || event.node_id || '节点'}`,
|
||||
'running',
|
||||
event.node_type,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'node_complete': {
|
||||
const ok = event.status === 'success' || event.status === 'completed';
|
||||
pushRunEvent(
|
||||
`${ok ? '执行完成' : '执行失败'}:${event.node_label || event.node_id || '节点'}`,
|
||||
ok ? 'success' : 'failed',
|
||||
event.error_message ||
|
||||
`耗时 ${formatRunDuration(event.elapsed_time)},Token ${event.tokens_used || 0}`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'waiting_input': {
|
||||
runStatus.value = 'waiting';
|
||||
pushRunEvent(
|
||||
`等待输入:${event.node_label || event.node_id || '节点'}`,
|
||||
'warning',
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 'complete': {
|
||||
runStatus.value = 'completed';
|
||||
runElapsedTime.value = event.elapsed_time || 0;
|
||||
pushRunEvent(
|
||||
'流程运行完成',
|
||||
'success',
|
||||
`耗时 ${formatRunDuration(event.elapsed_time)},Token ${event.total_tokens || 0}`,
|
||||
);
|
||||
ElMessage.success('流程运行完成');
|
||||
cancelWorkflowRunStream = null;
|
||||
fetchList();
|
||||
break;
|
||||
}
|
||||
case 'error': {
|
||||
runStatus.value = 'failed';
|
||||
runError.value =
|
||||
event.message || event.error_message || event.error || '流程运行失败';
|
||||
pushRunEvent('流程运行失败', 'failed', runError.value);
|
||||
ElMessage.error(runError.value);
|
||||
cancelWorkflowRunStream = null;
|
||||
fetchList();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function handleRunWorkflow(inputs: Record<string, any>) {
|
||||
if (!runWorkflowTarget.value?.id) return;
|
||||
|
||||
cancelWorkflowRunStream?.();
|
||||
resetRunState();
|
||||
runStatusDialogVisible.value = true;
|
||||
pushRunEvent('已提交运行请求', 'info', runWorkflowTarget.value.name);
|
||||
|
||||
cancelWorkflowRunStream = runWorkflowStreamApi(
|
||||
runWorkflowTarget.value.id,
|
||||
inputs,
|
||||
handleWorkflowRunEvent,
|
||||
(error) => {
|
||||
runStatus.value = 'failed';
|
||||
runError.value = error.message || '流程运行失败';
|
||||
pushRunEvent('流程运行失败', 'failed', runError.value);
|
||||
ElMessage.error(runError.value);
|
||||
cancelWorkflowRunStream = null;
|
||||
fetchList();
|
||||
},
|
||||
() => {
|
||||
cancelWorkflowRunStream = null;
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
function openRunHistory() {
|
||||
router.push(appContextStore.getContextPath('/ai-platform/workflow-runs'));
|
||||
}
|
||||
|
||||
function openCurrentRunDetail() {
|
||||
if (!currentRunId.value) return;
|
||||
runDetailRef.value?.open(currentRunId.value);
|
||||
}
|
||||
|
||||
const handleEdit = (row: WorkflowListItem) => {
|
||||
router.push(
|
||||
appContextStore.getContextPath(`/ai-platform/workflow/editor/${row.id}`),
|
||||
@@ -371,10 +546,16 @@ const getStatusText = (status: string) => {
|
||||
onMounted(() => {
|
||||
fetchList();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
cancelWorkflowRunStream?.();
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ai-workflow-list-page">
|
||||
<DetailDialog ref="runDetailRef" />
|
||||
|
||||
<Page auto-content-height v-loading="loading">
|
||||
<template #title>
|
||||
<div class="flex items-center justify-between">
|
||||
@@ -640,12 +821,83 @@ onMounted(() => {
|
||||
<!-- 快速运行弹窗(数据处理/自动化工作流) -->
|
||||
<RunDialog
|
||||
v-model:visible="runDialogVisible"
|
||||
:workflow-id="runWorkflowTarget?.id || ''"
|
||||
:nodes="runWorkflowNodes"
|
||||
:workflow-name="runWorkflowTarget?.name"
|
||||
:auto-run="true"
|
||||
@submit="handleRunWorkflow"
|
||||
/>
|
||||
|
||||
<ElDialog
|
||||
v-model="runStatusDialogVisible"
|
||||
title="流程运行"
|
||||
width="680px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<div class="space-y-4">
|
||||
<div class="flex flex-wrap items-center gap-3">
|
||||
<ElTag :type="runStatusType as any">
|
||||
{{ getRunStatusText(runStatus) }}
|
||||
</ElTag>
|
||||
<span class="text-sm font-medium">
|
||||
{{ runWorkflowTarget?.name || '-' }}
|
||||
</span>
|
||||
<span class="text-muted-foreground text-xs">
|
||||
{{ currentRunId || '正在创建运行记录...' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="runError"
|
||||
class="border-destructive/30 bg-destructive/10 text-destructive rounded-md border px-3 py-2 text-sm"
|
||||
>
|
||||
{{ runError }}
|
||||
</div>
|
||||
|
||||
<ElScrollbar height="320px">
|
||||
<div class="space-y-2 pr-2">
|
||||
<div
|
||||
v-for="event in runEvents"
|
||||
:key="event.id"
|
||||
class="border-border flex gap-3 rounded-md border px-3 py-2"
|
||||
>
|
||||
<ElTag :type="event.status === 'failed' ? 'danger' : event.status as any" size="small">
|
||||
{{ event.time }}
|
||||
</ElTag>
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="text-sm font-medium">{{ event.title }}</div>
|
||||
<div
|
||||
v-if="event.detail"
|
||||
class="text-muted-foreground mt-1 break-words text-xs"
|
||||
>
|
||||
{{ event.detail }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ElScrollbar>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-between">
|
||||
<ElButton @click="openRunHistory">
|
||||
打开执行历史
|
||||
</ElButton>
|
||||
<div class="flex gap-2">
|
||||
<ElButton
|
||||
:disabled="!currentRunId"
|
||||
type="primary"
|
||||
plain
|
||||
@click="openCurrentRunDetail"
|
||||
>
|
||||
查看本次详情
|
||||
</ElButton>
|
||||
<ElButton @click="runStatusDialogVisible = false">
|
||||
关闭
|
||||
</ElButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ElDialog>
|
||||
|
||||
<ImportDialog v-model="showImportDialog" @imported="handleImported" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user