Prechádzať zdrojové kódy

feat: 添加日志功能,修改优化问题

jiaxing.liao 2 týždňov pred
rodič
commit
c5b7f99d2c

+ 2 - 0
apps/web/components.d.ts

@@ -84,6 +84,7 @@ declare module 'vue' {
     RouterView: typeof import('vue-router')['RouterView']
     SearchDialog: typeof import('./src/components/SearchDialog/index.vue')['default']
     Sidebar: typeof import('./src/components/Sidebar/index.vue')['default']
+    SMarkdown: typeof import('./src/components/Chat/SMarkdown.vue')['default']
     SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
     TemplateModal: typeof import('./src/components/TemplateModal/index.vue')['default']
     VarLabel: typeof import('./src/components/VarLabel/index.vue')['default']
@@ -167,6 +168,7 @@ declare global {
   const RouterView: typeof import('vue-router')['RouterView']
   const SearchDialog: typeof import('./src/components/SearchDialog/index.vue')['default']
   const Sidebar: typeof import('./src/components/Sidebar/index.vue')['default']
+  const SMarkdown: typeof import('./src/components/Chat/SMarkdown.vue')['default']
   const SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
   const TemplateModal: typeof import('./src/components/TemplateModal/index.vue')['default']
   const VarLabel: typeof import('./src/components/VarLabel/index.vue')['default']

+ 41 - 46
apps/web/src/components/Chat/MessageList.vue

@@ -42,7 +42,7 @@
 								color="var(--text-primary)"
 							>
 								<template #content="{ content }">
-									<XMarkdown :markdown="content" enableLatex enableBreaks />
+									<SMarkdown :markdown="content" enableLatex enableBreaks />
 								</template>
 							</Thinking>
 
@@ -136,22 +136,16 @@
 							</div>
 						</div>
 
-						<XMarkdown
-							v-if="getDisplayContent(item)"
-							class="msg-content-text"
-							:markdown="getDisplayContent(item)"
+						<SMarkdown
+							v-if="getDisplayText(item)"
+							:class="['msg-content-text', { 'msg-content-text--error': isErrorMessage(item) }]"
+							:markdown="getDisplayText(item)"
 							:customAttrs="markdownCustomAttrs"
 							allow-html
 							enableLatex
 							enableBreaks
-						>
-							<template #kb="{ doc, chunk_id }">
-								<span class="kb-reference">
-									<el-icon class="kb-reference__icon"><DocumentIcon /></el-icon>
-									<span class="kb-reference__doc">{{ doc }}</span>
-								</span>
-							</template>
-						</XMarkdown>
+							need-view-code-btn
+						/>
 					</div>
 				</template>
 
@@ -222,8 +216,9 @@ import { nextTick, ref } from 'vue'
 import { Bubble, Thinking, XMarkdown } from 'vue-element-plus-x'
 import { useI18n } from '@/composables/useI18n'
 import { ElMessage } from 'element-plus'
-import { Document as DocumentIcon, DocumentCopy, DocumentAdd } from '@element-plus/icons-vue'
+import { DocumentCopy, DocumentAdd } from '@element-plus/icons-vue'
 import { Icon } from '@repo/ui'
+import SMarkdown from './SMarkdown.vue'
 import type { BubbleMessage, ChatToolResult } from '../../views/chat/types'
 
 const { t } = useI18n()
@@ -263,7 +258,7 @@ const getMessageImages = (message: BubbleMessage) => {
 const THINK_TAG_RE = /<think\b[^>]*>([\s\S]*?)(?:<\/think>|$)/gi
 
 const parseThinkContent = (content?: string) => {
-	const text = `${content || ''}`
+	const text = content || ''
 	const thinkingParts: string[] = []
 	const displayContent = text
 		.replace(THINK_TAG_RE, (_match, thinkingText) => {
@@ -279,11 +274,36 @@ const parseThinkContent = (content?: string) => {
 	}
 }
 
+const parseMessageError = (message: BubbleMessage) => {
+	if (message.error) {
+		return `${message.error}`.trim()
+	}
+
+	const text = `${message.content || ''}`.trim()
+	if (!text.startsWith('{') || !text.endsWith('}')) {
+		return ''
+	}
+
+	try {
+		const parsed = JSON.parse(text)
+		if (parsed?.isSuccess === false) {
+			return `${parsed.error || parsed.errors?.message || ''}`.trim()
+		}
+	} catch {
+		return ''
+	}
+
+	return ''
+}
+
 const getEmbeddedThinkingText = (message: BubbleMessage) =>
 	parseThinkContent(message.content).thinkingText
 
-const getDisplayContent = (message: BubbleMessage) =>
-	parseThinkContent(message.content).displayContent
+const getDisplayContent = (message: BubbleMessage) => parseThinkContent(message.content).displayContent
+
+const getDisplayText = (message: BubbleMessage) => parseMessageError(message) || getDisplayContent(message)
+
+const isErrorMessage = (message: BubbleMessage) => !!parseMessageError(message)
 
 const getThinkingText = (message: BubbleMessage) => {
 	const parts = [`${message.thinking || ''}`.trim(), getEmbeddedThinkingText(message)].filter(
@@ -425,6 +445,10 @@ const handleAddToKb = (message: BubbleMessage) => {
 		word-break: break-word;
 	}
 
+	.msg-content-text--error {
+		color: var(--el-color-danger);
+	}
+
 	.msg-content-text :deep(h3) {
 		margin: 0 0 10px;
 		font-size: 12px;
@@ -469,35 +493,6 @@ const handleAddToKb = (message: BubbleMessage) => {
 	.msg-content-text :deep(li) {
 		margin: 4px 0;
 	}
-
-	.kb-reference {
-		display: inline-flex;
-		align-items: center;
-		max-width: min(100%, 360px);
-		height: 24px;
-		margin: 0 3px;
-		padding: 0 8px;
-		border: 1px solid var(--el-color-primary-light-7);
-		border-radius: 6px;
-		background: var(--el-color-primary-light-9);
-		color: var(--el-color-primary);
-		font-size: 12px;
-		line-height: 1;
-		cursor: default;
-	}
-
-	.kb-reference__icon {
-		margin-right: 4px;
-		font-size: 14px;
-		flex-shrink: 0;
-	}
-
-	.kb-reference__doc {
-		min-width: 0;
-		overflow: hidden;
-		text-overflow: ellipsis;
-		white-space: nowrap;
-	}
 }
 
 .structured-blocks {

+ 64 - 0
apps/web/src/components/Chat/SMarkdown.vue

@@ -0,0 +1,64 @@
+<template>
+	<XMarkdown :markdown="getMarkdownContent" v-bind="$attrs">
+		<template #kb="{ doc }">
+			<span class="kb-reference">
+				<el-icon class="kb-reference__icon"><DocumentIcon /></el-icon>
+				<span class="kb-reference__doc">{{ doc }}</span>
+			</span>
+		</template>
+		<slot></slot>
+	</XMarkdown>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import { XMarkdown } from 'vue-element-plus-x'
+
+import type { MarkdownProps } from 'vue-element-plus-x/types/XMarkdownCore/shared'
+
+defineOptions({
+	name: 'SMarkdown'
+})
+
+const props = defineProps<MarkdownProps>()
+
+const KB_SELF_CLOSING_TAG_RE = /<kb\b([^>]*)\/>/gi
+
+const normalizeMarkdownContent = (content?: string) =>
+	`${content || ''}`.replace(KB_SELF_CLOSING_TAG_RE, '<kb$1></kb>')
+
+const getMarkdownContent = computed(() => {
+	return normalizeMarkdownContent(props.markdown)
+})
+</script>
+
+<style scoped lang="less">
+.kb-reference {
+	display: inline-flex;
+	align-items: center;
+	max-width: min(100%, 360px);
+	height: 24px;
+	margin: 0 3px;
+	padding: 0 8px;
+	border: 1px solid var(--el-color-primary-light-7);
+	border-radius: 6px;
+	background: var(--el-color-primary-light-9);
+	color: var(--el-color-primary);
+	font-size: 12px;
+	line-height: 1;
+	cursor: default;
+}
+
+.kb-reference__icon {
+	margin-right: 4px;
+	font-size: 14px;
+	flex-shrink: 0;
+}
+
+.kb-reference__doc {
+	min-width: 0;
+	overflow: hidden;
+	text-overflow: ellipsis;
+	white-space: nowrap;
+}
+</style>

+ 1 - 1
apps/web/src/features/createModal/index.vue

@@ -103,7 +103,7 @@ const createDefaultForm = (): CreateWorkflowForm => ({
 })
 
 const form = ref<CreateWorkflowForm>(createDefaultForm())
-const dialogTitle = computed(() => (props.mode === 'edit' ? '新建编排' : '编辑编排'))
+const dialogTitle = computed(() => (props.mode === 'edit' ? '编辑编排' : '新建编排'))
 
 const rules: FormRules<CreateWorkflowForm> = {
 	name: [

+ 26 - 10
apps/web/src/i18n/locales/en-us.ts

@@ -508,32 +508,48 @@ export default {
 			title: 'Execution',
 			subtitle: 'Track status, duration, and failure reasons in one place',
 			stats: {
-				todayExecutions: 'Today’s Runs',
+				recentExecutions: 'Runs in Last 7 Days',
 				successRate: 'Success Rate',
 				avgDuration: 'Avg Duration',
 				failedCount: 'Failures'
 			},
 			filters: {
-				keyword: 'Search by workflow or execution ID',
-				status: 'Status'
+				keyword: 'Search by agent or execution ID',
+				status: 'Status',
+				source: 'Source'
+			},
+			sources: {
+				manual: 'Manual',
+				schedule: 'Schedule',
+				webhook: 'Webhook',
+				api: 'API'
 			},
 			table: {
-				workflow: 'Workflow',
+				agentName: 'Agent',
 				executionId: 'Execution ID',
 				startedAt: 'Start Time',
+				endedAt: 'End Time',
 				duration: 'Duration',
-				trigger: 'Trigger',
-				status: 'Status',
-				actions: 'Actions'
+				status: 'Status'
 			},
 			panels: {
 				summary: 'Execution Summary',
 				tips: 'Alerts and Suggestions'
 			},
+			summaryLabels: {
+				processing: 'Processing',
+				success: 'Succeeded',
+				failed: 'Failed',
+				currentPage: 'Rows on Page'
+			},
+			messages: {
+				loadStatsFailed: 'Failed to load execution stats',
+				loadListFailed: 'Failed to load execution list'
+			},
 			tips: [
-				'Enable retry and timeout strategies for workflows with high failure rates',
-				'Split expensive nodes into asynchronous execution where possible',
-				'Add notification nodes to critical paths for better observability'
+				'When failures climb, check model, tool, and knowledge dependencies first',
+				'When duration rises, inspect external calls and retrieval paths',
+				'Long-running records usually point to timeouts or blocked nodes'
 			],
 			summary: [
 				{ label: 'Running', value: '3' },

+ 26 - 10
apps/web/src/i18n/locales/zh-cn.ts

@@ -459,32 +459,48 @@ export default {
 			title: '执行',
 			subtitle: '聚合监控执行状态、耗时与失败原因',
 			stats: {
-				todayExecutions: '今日执行',
+				recentExecutions: '近 7 天运行数',
 				successRate: '成功率',
 				avgDuration: '平均耗时',
 				failedCount: '失败数'
 			},
 			filters: {
-				keyword: '搜索工作流或执行 ID',
-				status: '状态'
+				keyword: '搜索智能体或执行 ID',
+				status: '状态',
+				source: '来源'
+			},
+			sources: {
+				manual: '手动',
+				schedule: '定时',
+				webhook: 'Webhook',
+				api: 'API'
 			},
 			table: {
-				workflow: '工作流',
+				agentName: '智能体',
 				executionId: '执行 ID',
 				startedAt: '开始时间',
+				endedAt: '结束时间',
 				duration: '耗时',
-				trigger: '触发方式',
-				status: '状态',
-				actions: '操作'
+				status: '状态'
 			},
 			panels: {
 				summary: '执行概览',
 				tips: '告警与建议'
 			},
+			summaryLabels: {
+				processing: '处理中',
+				success: '成功数',
+				failed: '失败数',
+				currentPage: '当前页条数'
+			},
+			messages: {
+				loadStatsFailed: '获取执行统计失败',
+				loadListFailed: '获取执行列表失败'
+			},
 			tips: [
-				'失败率高的工作流建议开启重试与超时策略',
-				'高耗时节点建议拆分为异步执行',
-				'关键流程可接入通知节点提高可观测性'
+				'失败较多时优先检查模型、工具和知识库依赖是否可用',
+				'平均耗时偏高时优先排查外部调用和检索链路',
+				'长时间运行中的记录建议重点排查超时配置和阻塞节点'
 			],
 			summary: [
 				{ label: '正在运行', value: '3' },

+ 232 - 65
apps/web/src/views/WorkflowExecution.vue

@@ -5,7 +5,7 @@
 				<h1>{{ t('pages.execution.title') }}</h1>
 				<p class="subtitle">{{ t('pages.execution.subtitle') }}</p>
 			</div>
-			<el-button type="primary" @click="refresh">
+			<el-button type="primary" :loading="statsLoading || tableLoading" @click="refresh">
 				<el-icon><RefreshRight /></el-icon>
 				{{ t('common.refresh') }}
 			</el-button>
@@ -17,8 +17,8 @@
 					<SvgIcon name="play" size="20" />
 				</div>
 				<div>
-					<div class="stat-value">186</div>
-					<div class="stat-label">{{ t('pages.execution.stats.todayExecutions') }}</div>
+					<div class="stat-value">{{ stats.runTotal }}</div>
+					<div class="stat-label">{{ t('pages.execution.stats.recentExecutions') }}</div>
 				</div>
 			</div>
 			<div class="stat-card">
@@ -26,7 +26,7 @@
 					<SvgIcon name="check-circle" size="20" />
 				</div>
 				<div>
-					<div class="stat-value">96.8%</div>
+					<div class="stat-value">{{ successRateText }}</div>
 					<div class="stat-label">{{ t('pages.execution.stats.successRate') }}</div>
 				</div>
 			</div>
@@ -35,7 +35,7 @@
 					<SvgIcon name="clock" size="20" />
 				</div>
 				<div>
-					<div class="stat-value">2.4s</div>
+					<div class="stat-value">{{ stats.avgElapsedSec }}s</div>
 					<div class="stat-label">{{ t('pages.execution.stats.avgDuration') }}</div>
 				</div>
 			</div>
@@ -44,7 +44,7 @@
 					<SvgIcon name="x-circle" size="20" />
 				</div>
 				<div>
-					<div class="stat-value">6</div>
+					<div class="stat-value">{{ stats.errorTotal }}</div>
 					<div class="stat-label">{{ t('pages.execution.stats.failedCount') }}</div>
 				</div>
 			</div>
@@ -53,9 +53,11 @@
 		<div class="filters">
 			<el-input
 				v-model="keyword"
-				:placeholder="t('pages.execution.filters.keyword')"
+				placeholder="关键词搜索"
 				clearable
 				class="filter-item"
+				@keyup.enter="handleSearch"
+				@clear="handleSearch"
 			>
 				<template #prefix>
 					<el-icon><Search /></el-icon>
@@ -66,30 +68,59 @@
 				:placeholder="t('pages.execution.filters.status')"
 				class="filter-item"
 				clearable
+				@change="handleSearch"
 			>
 				<el-option :label="t('common.all')" value="" />
-				<el-option :label="t('common.status.success')" value="success" />
-				<el-option :label="t('common.status.failed')" value="failed" />
 				<el-option :label="t('common.status.running')" value="running" />
+				<el-option :label="t('common.status.success')" value="finish" />
+				<el-option :label="t('common.status.failed')" value="error" />
 			</el-select>
-			<el-date-picker
-				v-model="dateRange"
-				type="daterange"
-				:range-separator="t('common.date.rangeSeparator')"
-				:start-placeholder="t('common.date.start')"
-				:end-placeholder="t('common.date.end')"
+			<el-select
+				v-model="source"
+				:placeholder="t('pages.execution.filters.source')"
 				class="filter-item"
-			/>
+				clearable
+				@change="handleSearch"
+			>
+				<el-option :label="t('common.all')" value="" />
+				<el-option :label="t('pages.execution.sources.manual')" value="manual" />
+				<el-option :label="t('pages.execution.sources.schedule')" value="schedule" />
+				<el-option :label="t('pages.execution.sources.webhook')" value="webhook" />
+				<el-option :label="t('pages.execution.sources.api')" value="api" />
+			</el-select>
 			<el-button @click="resetFilters">{{ t('common.reset') }}</el-button>
 		</div>
 
 		<div class="panel">
-			<el-table :data="filteredExecutions" stripe style="width: 100%">
-				<el-table-column prop="workflow" :label="t('pages.execution.table.workflow')" />
-				<el-table-column prop="executionId" :label="t('pages.execution.table.executionId')" />
-				<el-table-column prop="startedAt" :label="t('pages.execution.table.startedAt')" />
-				<el-table-column prop="duration" :label="t('pages.execution.table.duration')" />
-				<el-table-column prop="trigger" :label="t('pages.execution.table.trigger')" />
+			<el-table v-loading="tableLoading" :data="executions" stripe style="width: 100%">
+				<el-table-column
+					prop="agentName"
+					:label="t('pages.execution.table.agentName')"
+					min-width="220"
+					show-overflow-tooltip
+				/>
+				<el-table-column
+					prop="executionId"
+					:label="t('pages.execution.table.executionId')"
+					min-width="260"
+					show-overflow-tooltip
+				/>
+				<el-table-column
+					prop="startedAt"
+					:label="t('pages.execution.table.startedAt')"
+					min-width="180"
+				/>
+				<el-table-column
+					prop="endedAt"
+					:label="t('pages.execution.table.endedAt')"
+					min-width="180"
+				/>
+				<el-table-column
+					prop="duration"
+					:label="t('pages.execution.table.duration')"
+					min-width="120"
+				/>
+				<el-table-column prop="source" :label="t('pages.execution.filters.source')" />
 				<el-table-column :label="t('pages.execution.table.status')">
 					<template #default="scope">
 						<el-tag :type="statusType(scope.row.status)">
@@ -97,13 +128,19 @@
 						</el-tag>
 					</template>
 				</el-table-column>
-				<el-table-column :label="t('pages.execution.table.actions')">
-					<template #default>
-						<el-button text size="small">{{ t('common.details') }}</el-button>
-						<el-button text size="small">{{ t('common.retry') }}</el-button>
-					</template>
-				</el-table-column>
 			</el-table>
+			<div v-if="pagination.totalCount" class="pagination">
+				<el-pagination
+					v-model:current-page="pagination.pageIndex"
+					v-model:page-size="pagination.pageSize"
+					background
+					layout="total, sizes, prev, pager, next, jumper"
+					:page-sizes="[10, 20, 50, 100]"
+					:total="pagination.totalCount"
+					@current-change="handlePageChange"
+					@size-change="handleSizeChange"
+				/>
+			</div>
 		</div>
 
 		<div class="side-panels">
@@ -125,66 +162,190 @@
 </template>
 
 <script setup lang="ts">
-import { computed, ref } from 'vue'
+import { computed, onMounted, reactive, ref } from 'vue'
+import { ElMessage } from 'element-plus'
 import { RefreshRight, Search } from '@element-plus/icons-vue'
 import { useI18n } from '@/composables/useI18n'
+import { agentLog } from '@repo/api-service'
+
+type AgentRunnerPageResponse = Awaited<ReturnType<typeof agentLog.postAgentGetAgentRunnerPageList>>
+type AgentRunnerPageItem = NonNullable<AgentRunnerPageResponse['result']>['model'][number]
+
+interface ExecutionRow {
+	agentName: string
+	executionId: string
+	startedAt: string
+	endedAt: string
+	duration: string
+	status: string
+}
 
-const { t, tm } = useI18n()
+const { t } = useI18n()
 const keyword = ref('')
 const status = ref('')
-const dateRange = ref<[Date, Date] | null>(null)
-
-const executions = computed(
-	() =>
-		(tm('pages.execution.executions') as Array<{
-			workflow: string
-			executionId: string
-			startedAt: string
-			duration: string
-			trigger: string
-			status: string
-		}>) || []
-)
-
-const summary = computed(
-	() => (tm('pages.execution.summary') as Array<{ label: string; value: string }>) || []
-)
-
-const tips = computed(() => (tm('pages.execution.tips') as string[]) || [])
-
-const filteredExecutions = computed(() => {
-	const q = keyword.value.trim().toLowerCase()
-	return executions.value.filter((item) => {
-		const matchKeyword =
-			!q || item.workflow.toLowerCase().includes(q) || item.executionId.toLowerCase().includes(q)
-		const matchStatus = !status.value || item.status === status.value
-		return matchKeyword && matchStatus
-	})
+const source = ref('')
+const tableLoading = ref(false)
+const statsLoading = ref(false)
+const executions = ref<ExecutionRow[]>([])
+
+const pagination = reactive({
+	pageIndex: 1,
+	pageSize: 20,
+	totalCount: 0,
+	totalPages: 0
+})
+
+const stats = reactive({
+	runTotal: 0,
+	successTotal: 0,
+	errorTotal: 0,
+	processTotal: 0,
+	avgElapsedSec: 0
+})
+
+const sourceMap = {
+	manual: t('pages.execution.sources.manual'),
+	schedule: t('pages.execution.sources.schedule'),
+	webhook: t('pages.execution.sources.webhook'),
+	api: t('pages.execution.sources.api')
+}
+
+const summary = computed(() => [
+	{ label: t('pages.execution.summaryLabels.processing'), value: `${stats.processTotal}` },
+	{ label: t('pages.execution.summaryLabels.success'), value: `${stats.successTotal}` },
+	{ label: t('pages.execution.summaryLabels.failed'), value: `${stats.errorTotal}` },
+	{ label: t('pages.execution.summaryLabels.currentPage'), value: `${executions.value.length}` }
+])
+
+const tips = computed(() => [
+	t('pages.execution.tips.0'),
+	t('pages.execution.tips.1'),
+	t('pages.execution.tips.2')
+])
+
+const successRateText = computed(() => {
+	if (!stats.runTotal) return '0%'
+	return `${((stats.successTotal / stats.runTotal) * 100).toFixed(1)}%`
+})
+
+const allStatuses = 'running,finish,error'
+const allSources = 'manual,schedule,webhook,api'
+
+const formatDuration = (value?: number | string | null) => {
+	const duration = Number(value || 0)
+	if (!Number.isFinite(duration) || duration <= 0) return '-'
+	if (duration < 1000) return `${Math.round(duration)}ms`
+	if (duration < 60_000) {
+		const seconds = duration / 1000
+		return `${Number.isInteger(seconds) ? seconds.toFixed(0) : seconds.toFixed(1)}s`
+	}
+	const minutes = Math.floor(duration / 60_000)
+	const seconds = Math.round((duration % 60_000) / 1000)
+	return `${minutes}m ${seconds}s`
+}
+
+const normalizeStatus = (value?: string) => `${value || ''}`.trim().toLowerCase()
+const getStatusQuery = () => status.value || allStatuses
+const getSourceQuery = () => source.value || allSources
+
+const mapExecutionRow = (item: AgentRunnerPageItem): ExecutionRow => ({
+	agentName: item.agentName || '-',
+	executionId: item.runnerId || '-',
+	startedAt: item.beginDate || '-',
+	endedAt: item.endDate || '-',
+	duration: formatDuration(item.useTime),
+	status: normalizeStatus(item.status),
+	source: sourceMap[item.source]
 })
 
 const statusType = (value: string) => {
-	if (value === 'success') return 'success'
-	if (value === 'failed') return 'danger'
+	if (value === 'finish') return 'success'
+	if (value === 'error') return 'danger'
 	if (value === 'running') return 'warning'
 	return 'info'
 }
 
 const statusText = (value: string) => {
-	if (value === 'success') return t('common.status.success')
-	if (value === 'failed') return t('common.status.failed')
+	if (value === 'finish') return t('common.status.success')
+	if (value === 'error') return t('common.status.failed')
 	if (value === 'running') return t('common.status.running')
 	return t('common.status.unknown')
 }
 
-const refresh = () => {
-	console.log('refresh executions')
+const loadStats = async () => {
+	statsLoading.value = true
+	try {
+		const res = await agentLog.postAgentGetBefore7DayAgentRunnerStatic({})
+		if (!res.isSuccess || !res.result) throw new Error('load stats failed')
+		const avgElapsedValue = (res.result as typeof res.result & { avg_elapsed_time?: string })
+			.avg_elapsed_time
+		stats.runTotal = res.result.run_total || 0
+		stats.successTotal = res.result.success_total || 0
+		stats.errorTotal = res.result.error_total || 0
+		stats.processTotal = res.result.process_total || 0
+		stats.avgElapsedSec = Number(res.result.avg_elapsed_sec || avgElapsedValue || 0)
+	} catch (error) {
+		console.error(error)
+		ElMessage.error(t('pages.execution.messages.loadStatsFailed'))
+	} finally {
+		statsLoading.value = false
+	}
+}
+
+const loadExecutions = async (pageIndex = pagination.pageIndex) => {
+	tableLoading.value = true
+	try {
+		const res = await agentLog.postAgentGetAgentRunnerPageList({
+			keyword: keyword.value.trim(),
+			status: getStatusQuery(),
+			source: getSourceQuery(),
+			pageIndex,
+			pageSize: pagination.pageSize
+		})
+		if (!res.isSuccess || !res.result) throw new Error('load executions failed')
+		executions.value = (res.result.model || []).map(mapExecutionRow)
+		pagination.pageIndex = res.result.currentPage || pageIndex
+		pagination.pageSize = res.result.pageSize || pagination.pageSize
+		pagination.totalCount = res.result.totalCount || 0
+		pagination.totalPages = res.result.totalPages || 0
+	} catch (error) {
+		console.error(error)
+		ElMessage.error(t('pages.execution.messages.loadListFailed'))
+	} finally {
+		tableLoading.value = false
+	}
+}
+
+const refresh = async () => {
+	await Promise.all([loadStats(), loadExecutions(pagination.pageIndex)])
+}
+
+const handleSearch = async () => {
+	pagination.pageIndex = 1
+	await loadExecutions(1)
+}
+
+const handlePageChange = async (page: number) => {
+	pagination.pageIndex = page
+	await loadExecutions(page)
+}
+
+const handleSizeChange = async (size: number) => {
+	pagination.pageSize = size
+	pagination.pageIndex = 1
+	await loadExecutions(1)
 }
 
 const resetFilters = () => {
 	keyword.value = ''
 	status.value = ''
-	dateRange.value = null
+	source.value = ''
+	handleSearch()
 }
+
+onMounted(() => {
+	refresh()
+})
 </script>
 
 <style lang="less" scoped>
@@ -285,6 +446,12 @@ const resetFilters = () => {
 		box-shadow: var(--shadow-sm);
 	}
 
+	.pagination {
+		display: flex;
+		justify-content: flex-end;
+		margin-top: 16px;
+	}
+
 	.side-panels {
 		display: grid;
 		grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));

+ 7 - 6
apps/web/src/views/agent/components/EditModal.vue

@@ -86,7 +86,7 @@
 											@click="
 												openPromptTemplatePicker(
 													'systemPrompt',
-													form.mode === 'quick-answer' ? 'agent-system-prompt' : 'system-prompt'
+													form.mode === 'quick-answer' ? 'system-prompt' : 'agent-system-prompt'
 												)
 											"
 										>
@@ -98,7 +98,7 @@
 										v-model="form.config.basic_config.system_prompt"
 										mode="prompt"
 										type="textarea"
-										:rows="8"
+										:rows="15"
 										placeholder="请输入系统提示词,可直接编写角色、目标、约束和回答风格"
 									/>
 								</div>
@@ -138,7 +138,7 @@
 										v-model="form.config.basic_config.context_template"
 										mode="prompt"
 										type="textarea"
-										:rows="5"
+										:rows="15"
 										placeholder="请输入上下文模板,用于约束问答模式下的上下文组织方式"
 									/>
 								</div>
@@ -525,7 +525,7 @@
 										<el-input
 											v-model="form.config.advanced_config.fallback_prompt"
 											type="textarea"
-											:rows="3"
+											:rows="15"
 											placeholder="请输入模型兜底时使用的提示词"
 										/>
 									</div>
@@ -761,7 +761,7 @@
 											ref="rewritePromptSystemInputRef"
 											v-model="form.config.advanced_config.rewrite_prompt_system"
 											type="textarea"
-											:rows="3"
+											:rows="15"
 											placeholder="请输入问题改写时的系统提示词"
 										/>
 									</div>
@@ -800,7 +800,7 @@
 											ref="rewritePromptUserInputRef"
 											v-model="form.config.advanced_config.rewrite_prompt_user"
 											type="textarea"
-											:rows="3"
+											:rows="15"
 											placeholder="请输入问题改写时的用户提示词"
 										/>
 									</div>
@@ -946,6 +946,7 @@ const form = reactive<AgentFormData>({
 	mode: 'smart-reasoning',
 	config: {
 		basic_config: {
+			agent_mode: 'smart-reasoning',
 			system_prompt: '',
 			context_template: ''
 		},

+ 1 - 1
apps/web/src/views/chat/api/chat.api.ts

@@ -89,7 +89,7 @@ export async function sendChatMessage(params: ChatRequestParams) {
 	})
 }
 
-export async function createSession(name: string) {
+export async function createSession(name?: string) {
 	return aiChat.postSessionCreate({ name })
 }
 

+ 38 - 6
apps/web/src/views/chat/composables/useChatStream.ts

@@ -47,15 +47,41 @@ export function useChatStream() {
 		}
 	}
 
+	const getErrorMessage = (payload: any) => {
+		if (!payload || typeof payload !== 'object') return ''
+		return `${payload.error || payload.errors?.message || payload.message || payload.msg || ''}`.trim()
+	}
+
+	const normalizeDirectResponseError = (result: unknown) => {
+		const payload =
+			typeof result === 'string'
+				? (() => {
+						const text = result.trim()
+						if (!text.startsWith('{') || !text.endsWith('}')) return null
+						try {
+							return JSON.parse(text)
+						} catch {
+							return null
+						}
+					})()
+				: result && typeof result === 'object'
+					? result
+					: null
+
+		if (!payload || Array.isArray(payload)) return null
+		if (payload.response_type || payload.data) return null
+		if (payload.isSuccess === false) {
+			return new Error(getErrorMessage(payload) || 'Request failed')
+		}
+
+		return null
+	}
+
 	const normalizeRequestError = async (error: any) => {
 		if (!error) return new Error('Unknown error')
 		if (error instanceof Error) return error
 
-		const message =
-			error?.error ||
-			error?.message ||
-			error?.msg ||
-			(error?.status ? `HTTP error! status: ${error.status}` : 'Request failed')
+		const message = getErrorMessage(error) || (error?.status ? `HTTP error! status: ${error.status}` : 'Request failed')
 
 		return new Error(message)
 	}
@@ -103,7 +129,13 @@ export function useChatStream() {
 					throw await normalizeRequestError(chunk.error)
 				}
 
-				const result = `${chunk.result ?? ''}`
+				const directResponseError = normalizeDirectResponseError(chunk.result)
+				if (directResponseError) {
+					throw directResponseError
+				}
+
+				const result =
+					typeof chunk.result === 'string' ? chunk.result : `${chunk.result ?? ''}`
 				if (!result.trim()) continue
 
 				const results = parseSseBlock(result)

+ 3 - 19
apps/web/src/views/chat/index.vue

@@ -283,8 +283,7 @@ const createDefaultConfig = (type: ChatTargetType = 'agent'): ChatTargetConfig =
 		type,
 		knowledgeBaseIds: [],
 		knowledgeIds: [],
-		// 如果有模型列表,默认选中第一个模型
-		summaryModelId: modelOptions.value?.[0]?.value || '',
+		summaryModelId: '',
 		disableTitle: false,
 		enableMemory: true,
 		agentId: type === 'agent' ? getDefaultAgentId() : '',
@@ -333,11 +332,6 @@ const getConversationConfig = (conversationId: string) => {
 			...cloneConfig(conversation.targetConfig as ChatTargetConfig)
 		}
 
-		// 如果恢复的配置中没有模型ID,则使用当前可用的第一个模型
-		if (!config.summaryModelId && modelOptions.value?.length > 0) {
-			config.summaryModelId = modelOptions.value[0]?.value!
-		}
-
 		return config
 	}
 
@@ -370,16 +364,6 @@ const loadChatOptions = async () => {
 		knowledgeBaseOptions.value = knowledgeBases
 		modelOptions.value = models
 
-		// 确保如果有模型且当前未选择模型,则默认选中第一个
-		if (modelOptions.value?.length > 0) {
-			if (
-				!settingsDraft.summaryModelId ||
-				!modelOptions.value.some((m) => m.value === settingsDraft.summaryModelId)
-			) {
-				settingsDraft.summaryModelId = modelOptions.value[0]?.value!
-			}
-		}
-
 		if (activeTargetType.value === 'agent' && !settingsDraft.agentId) {
 			settingsDraft.agentId = getDefaultAgentId()
 		}
@@ -957,8 +941,8 @@ const handleSelectConversation = async (bizId: string) => {
  */
 const handleNewChat = async () => {
 	try {
-		const defaultName = `${t('pages.chat.newConversation')} ${new Date().toLocaleTimeString()}`
-		const res = await createSession(defaultName)
+		// const defaultName = `${t('pages.chat.newConversation')} ${new Date().toLocaleTimeString()}`
+		const res = await createSession()
 		if (res.isSuccess && res.result) {
 			await loadConversations(true)
 			const targetConv = conversations.value.find((c) => c.id === res.result)

+ 28 - 3
apps/web/src/views/editor/Editor.vue

@@ -99,6 +99,7 @@ const saveAgentTimer = ref<number | undefined>(undefined)
 const saveVarsTimer = ref<number | undefined>(undefined)
 const isHydrating = ref(false)
 const notifyTimestamps = new Map<string, number>()
+const workflowRefreshKey = 'workflow-list-refresh'
 
 const id = route.params?.id as string
 const projectMap = JSON.parse(localStorage.getItem('workflow-map') || '{}') as Record<
@@ -376,18 +377,42 @@ const handleRename = () => {
 	inputRef.value?.select()
 }
 
+const closeEditorAndRefreshWorkflow = async () => {
+	localStorage.setItem(workflowRefreshKey, `${Date.now()}`)
+
+	try {
+		const openerHash = window.opener?.location?.hash || ''
+		if (openerHash.startsWith('#/workflow')) {
+			window.opener.location.reload()
+		}
+	} catch (error) {
+		console.warn('closeEditorAndRefreshWorkflow error', error)
+	}
+
+	window.close()
+	if (!window.closed) {
+		await router.replace('/workflow')
+	}
+}
+
 // 删除本地缓存中的当前工作流并退出编辑页。
 const handleDelete = () => {
 	ElMessageBox.confirm(t('common.confirmDelete.message'), t('common.confirmDelete.title'), {
 		confirmButtonText: t('common.confirm'),
 		cancelButtonText: t('common.cancel'),
 		type: 'warning'
-	}).then(() => {
-		agent.postAgentDoDeleteAgent({
+	}).then(async () => {
+		const response = await agent.postAgentDoDeleteAgent({
 			id: id
 		})
+		if (!response?.isSuccess) {
+			ElMessage.error('删除失败')
+			return
+		}
+		delete projectMap[id]
+		localStorage.setItem('workflow-map', JSON.stringify(projectMap))
 		ElMessage.success('删除成功')
-		router.push('/')
+		await closeEditorAndRefreshWorkflow()
 	})
 }
 

+ 163 - 45
apps/web/src/views/model/ModelManage.vue

@@ -6,24 +6,43 @@
 					<el-button type="primary" @click="openCreateModel">
 						<el-icon>
 							<Plus />
-						</el-icon> 新建模型
+						</el-icon>
+						新建模型
 					</el-button>
 					<el-button @click="getAllModelList()">
 						<el-icon>
 							<Refresh />
-						</el-icon> 刷新
+						</el-icon>
+						刷新
 					</el-button>
 				</div>
 				<div class="action-bar__right">
-					<el-input v-model="searchForm.keyword" clearable placeholder="关键词搜索模型名称/标识" class="search-input"
-						@keyup.enter="handleSearch" />
-					<el-select v-model="searchForm.type" clearable placeholder="模型类型" style="width: 160px" @change="handleSearch">
+					<el-input
+						v-model="searchForm.keyword"
+						clearable
+						placeholder="关键词搜索模型名称/标识"
+						class="search-input"
+						@keyup.enter="handleSearch"
+					/>
+					<el-select
+						v-model="searchForm.type"
+						clearable
+						placeholder="模型类型"
+						style="width: 160px"
+						@change="handleSearch"
+					>
 						<el-option label="对话模型" value="KnowledgeQA" />
 						<el-option label="Embedding模型" value="Embedding" />
 						<el-option label="Rerank模型" value="Rerank" />
 						<el-option label="视觉模型" value="VLLM" />
 					</el-select>
-					<el-select v-model="searchForm.source" clearable placeholder="来源" style="width: 140px" @change="handleSearch">
+					<el-select
+						v-model="searchForm.source"
+						clearable
+						placeholder="来源"
+						style="width: 140px"
+						@change="handleSearch"
+					>
 						<el-option label="本地" value="local" />
 						<el-option label="服务商" value="remote" />
 					</el-select>
@@ -32,8 +51,16 @@
 				</div>
 			</div>
 
-			<div v-loading="modelLoading" class="grid" :class="{ 'grid--empty': !allModels.length && !modelLoading }">
-				<el-empty v-if="!allModels.length && !modelLoading" description="暂无配置的模型" class="empty" />
+			<div
+				v-loading="modelLoading"
+				class="grid"
+				:class="{ 'grid--empty': !allModels.length && !modelLoading }"
+			>
+				<el-empty
+					v-if="!allModels.length && !modelLoading"
+					description="暂无配置的模型"
+					class="empty"
+				/>
 
 				<div v-for="row in allModels" :key="row.id" class="card" @click="openDetailModel(row.id)">
 					<div class="card-head">
@@ -85,9 +112,16 @@
 			</div>
 
 			<div v-if="pagination.total > 0" class="pagination-wrap">
-				<el-pagination v-model:current-page="pagination.pageIndex" v-model:page-size="pagination.pageSize" background
-					layout="total, sizes, prev, pager, next, jumper" :total="pagination.total" :page-sizes="[10, 20, 50, 100]"
-					@current-change="handlePageChange" @size-change="handleSizeChange" />
+				<el-pagination
+					v-model:current-page="pagination.pageIndex"
+					v-model:page-size="pagination.pageSize"
+					background
+					layout="total, sizes, prev, pager, next, jumper"
+					:total="pagination.total"
+					:page-sizes="[10, 20, 50, 100]"
+					@current-change="handlePageChange"
+					@size-change="handleSizeChange"
+				/>
 			</div>
 		</el-card>
 
@@ -115,11 +149,21 @@
 				</el-descriptions-item>
 				<el-descriptions-item v-if="currentDetailModel.source === 'remote'" label="连接测试">
 					<div class="model-check-box">
-						<el-button type="primary" plain :loading="detailCheckLoading" @click="checkCurrentDetailModel">
+						<el-button
+							type="primary"
+							plain
+							:loading="detailCheckLoading"
+							@click="checkCurrentDetailModel"
+						>
 							测试连接
 						</el-button>
-						<el-alert v-if="detailCheckResult.message" :type="detailCheckResult.success ? 'success' : 'error'"
-							:title="detailCheckResult.message" :closable="false" show-icon />
+						<el-alert
+							v-if="detailCheckResult.message"
+							:type="detailCheckResult.success ? 'success' : 'error'"
+							:title="detailCheckResult.message"
+							:closable="false"
+							show-icon
+						/>
 					</div>
 				</el-descriptions-item>
 				<el-descriptions-item label="创建时间">{{
@@ -134,8 +178,19 @@
 			</template>
 		</el-dialog>
 
-		<el-drawer v-model="showModelDialog" direction="rtl" :title="currentModelId ? '编辑模型' : '新建模型'" width="700px">
-			<el-form ref="modelFormRef" :model="modelForm" :rules="modelRules" label-width="120px" label-position="top">
+		<el-drawer
+			v-model="showModelDialog"
+			direction="rtl"
+			:title="currentModelId ? '编辑模型' : '新建模型'"
+			width="700px"
+		>
+			<el-form
+				ref="modelFormRef"
+				:model="modelForm"
+				:rules="modelRules"
+				label-width="120px"
+				label-position="top"
+			>
 				<el-form-item label="模型来源" prop="source">
 					<el-radio-group v-model="modelForm.source" @change="handleSourceChange">
 						<el-radio label="local">本地Ollama</el-radio>
@@ -144,7 +199,11 @@
 				</el-form-item>
 
 				<el-form-item v-if="modelForm.source === 'remote'" label="模型类型" prop="type">
-					<el-select v-model="modelForm.type" placeholder="请选择模型类型" @change="handleTypeChange">
+					<el-select
+						v-model="modelForm.type"
+						placeholder="请选择模型类型"
+						@change="handleTypeChange"
+					>
 						<el-option label="对话模型" value="KnowledgeQA" />
 						<el-option label="Embedding模型" value="Embedding" />
 						<el-option label="Rerank模型" value="Rerank" />
@@ -154,20 +213,32 @@
 
 				<el-form-item v-if="modelForm.source === 'local'" label="本地模型" prop="name">
 					<el-select v-model="modelForm.name" placeholder="请选择" style="width: 100%">
-						<el-option v-for="model in availableLocalModels" :key="model.name" :label="model.name"
-							:value="model.name" />
+						<el-option
+							v-for="model in availableLocalModels"
+							:key="model.name"
+							:label="model.name"
+							:value="model.name"
+						/>
 					</el-select>
 				</el-form-item>
 
 				<el-form-item v-if="modelForm.source === 'remote'" label="服务商" prop="provider">
-					<el-select v-model="modelForm.provider" placeholder="请选择服务商" @change="handleProviderChange"
-						style="width: 100%">
+					<el-select
+						v-model="modelForm.provider"
+						placeholder="请选择服务商"
+						@change="handleProviderChange"
+						style="width: 100%"
+					>
 						<el-option v-for="p in providers" :key="p.value" :label="p.label" :value="p.value" />
 					</el-select>
 				</el-form-item>
 
 				<el-form-item label="模型名称" prop="name">
-					<el-input v-model="modelForm.name" :readonly="modelForm.source === 'local'" placeholder="如:llama3.1" />
+					<el-input
+						v-model="modelForm.name"
+						:readonly="modelForm.source === 'local'"
+						placeholder="如:llama3.1"
+					/>
 				</el-form-item>
 
 				<el-form-item label="显示名称" prop="title">
@@ -175,31 +246,62 @@
 				</el-form-item>
 
 				<el-form-item label="描述" prop="description">
-					<el-input v-model="modelForm.description" type="textarea" rows="2" placeholder="模型描述..." />
+					<el-input
+						v-model="modelForm.description"
+						type="textarea"
+						rows="2"
+						placeholder="模型描述..."
+					/>
 				</el-form-item>
 
 				<el-form-item v-if="modelForm.source === 'remote'" label="API地址" prop="base_url">
-					<el-autocomplete v-model="modelForm.base_url" :fetch-suggestions="queryBaseUrlSuggestions" clearable
-						placeholder="可从默认地址选择,或手动输入" style="width: 100%" />
+					<el-autocomplete
+						v-model="modelForm.base_url"
+						:fetch-suggestions="queryBaseUrlSuggestions"
+						clearable
+						placeholder="可从默认地址选择,或手动输入"
+						style="width: 100%"
+					/>
 				</el-form-item>
 
 				<el-form-item v-if="modelForm.source === 'remote'" label="API Key" prop="api_key">
 					<el-input v-model="modelForm.api_key" type="password" placeholder="输入你的API密钥" />
 				</el-form-item>
-				<el-form-item v-if="modelForm.source === 'remote' && modelForm.type === 'Embedding'" label="维度"
-					prop="dimension">
-					<el-input-number v-model="modelForm.dimension" :min="1" :step="1" controls-position="right"
-						style="width: 100%" placeholder="请输入向量维度" />
+				<el-form-item
+					v-if="modelForm.source === 'remote' && modelForm.type === 'Embedding'"
+					label="维度"
+					prop="dimension"
+				>
+					<el-input-number
+						v-model="modelForm.dimension"
+						:min="1"
+						:step="1"
+						controls-position="right"
+						style="width: 100%"
+						placeholder="请输入向量维度"
+					/>
 				</el-form-item>
 
-				<el-form-item v-if="modelForm.source === 'remote' && modelForm.type === 'Embedding'" label="截断Tokens"
-					prop="truncate_prompt_tokens">
-					<el-input-number v-model="modelForm.truncate_prompt_tokens" :min="0" :step="1" controls-position="right"
-						style="width: 100%" placeholder="请输入截断 token 数" />
+				<el-form-item
+					v-if="modelForm.source === 'remote' && modelForm.type === 'Embedding'"
+					label="截断Tokens"
+					prop="truncate_prompt_tokens"
+				>
+					<el-input-number
+						v-model="modelForm.truncate_prompt_tokens"
+						:min="-1"
+						:step="1"
+						controls-position="right"
+						style="width: 100%"
+						placeholder="请输入截断 token 数"
+					/>
 				</el-form-item>
 
-				<el-form-item v-if="modelForm.source === 'remote' && modelForm.type === 'KnowledgeQA'" label="支持视觉"
-					prop="supports_vision">
+				<el-form-item
+					v-if="modelForm.source === 'remote' && modelForm.type === 'KnowledgeQA'"
+					label="支持视觉"
+					prop="supports_vision"
+				>
 					<el-switch v-model="modelForm.supports_vision" />
 				</el-form-item>
 
@@ -208,20 +310,26 @@
 						<div class="header-config__top">
 							<span>自定义请求头(可选)</span>
 							<el-button type="primary" link @click="addCustomHeader">
-								<el-icon>
-									<Plus />
-								</el-icon>添加请求头
+								<el-icon> <Plus /> </el-icon>添加请求头
 							</el-button>
 						</div>
 						<div class="header-config__desc">
 							调用远程模型API时附加的HTTP请求头,常用于鉴权、链路追踪等场景
 						</div>
 						<div class="header-config__rows">
-							<div v-for="(item, index) in customHeaderList" :key="index" class="header-config__row">
+							<div
+								v-for="(item, index) in customHeaderList"
+								:key="index"
+								class="header-config__row"
+							>
 								<el-input v-model="item.key" placeholder="Header名称" />
 								<el-input v-model="item.value" placeholder="Header值" />
-								<el-button link type="danger" :disabled="customHeaderList.length === 1"
-									@click="removeCustomHeader(index)">
+								<el-button
+									link
+									type="danger"
+									:disabled="customHeaderList.length === 1"
+									@click="removeCustomHeader(index)"
+								>
 									删除
 								</el-button>
 							</div>
@@ -230,11 +338,21 @@
 				</el-form-item>
 				<el-form-item>
 					<div v-if="modelForm.source === 'remote'" class="model-check-box">
-						<el-button type="primary" plain :loading="formCheckLoading" @click="checkModelFormConnection">
+						<el-button
+							type="primary"
+							plain
+							:loading="formCheckLoading"
+							@click="checkModelFormConnection"
+						>
 							测试连接
 						</el-button>
-						<el-alert v-if="formCheckResult.message" :type="formCheckResult.success ? 'success' : 'error'"
-							:title="formCheckResult.message" :closable="false" show-icon />
+						<el-alert
+							v-if="formCheckResult.message"
+							:type="formCheckResult.success ? 'success' : 'error'"
+							:title="formCheckResult.message"
+							:closable="false"
+							show-icon
+						/>
 					</div>
 				</el-form-item>
 			</el-form>
@@ -340,7 +458,7 @@ const modelRules = {
 			},
 			trigger: 'blur'
 		}
-	],
+	]
 }
 
 const typeLabelMap: Record<string, string> = {

+ 7 - 12
apps/web/src/views/resource/components/McpPanel.vue

@@ -294,19 +294,15 @@
 				<el-empty v-if="!filteredToolDetailList.length" description="暂无工具" />
 				<el-scrollbar v-else max-height="420px">
 					<el-collapse>
-						<el-collapse-item
-							v-for="item in filteredToolDetailList"
-							:key="item.name"
-							:title="item.name"
-						>
+						<el-collapse-item v-for="item in filteredToolDetailList" :key="item.name">
+							<template #title>
+								<div>
+									<div class="tool-desc text-16px text-primary">{{ item.name }}</div>
+									<div class="tool-desc text-12px">{{ item.description }}</div>
+								</div>
+							</template>
 							<el-card>
 								<el-descriptions direction="vertical" :column="1">
-									<el-descriptions-item label="名称">
-										<div class="tool-desc">{{ item.name }}</div>
-									</el-descriptions-item>
-									<el-descriptions-item label="描述">
-										<div class="tool-desc">{{ item.description }}</div>
-									</el-descriptions-item>
 									<el-descriptions-item label="参数结构">
 										<CodeEditor
 											:model-value="JSON.stringify(item.inputSchema, null, 2)"
@@ -331,7 +327,6 @@ import { computed, onMounted, reactive, ref } from 'vue'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { Plus, Search, MoreFilled } from '@element-plus/icons-vue'
 import { resource } from '@repo/api-service'
-import { XMarkdown } from 'vue-element-plus-x'
 import CodeEditor from '@/components/CodeEditor/CodeEditor.vue'
 
 type McpPageResponse = Awaited<ReturnType<typeof resource.postMcpPageList>>

+ 3 - 1
packages/api-service/index.ts

@@ -14,6 +14,7 @@ const storageProvider = knowledgeApi.storageProvider
 const wiki = knowledgeApi.wiki
 const aiChat = aiChatApi.aiChat
 const resource = resourceApi.resource
+const agentLog = agentApi.agentLog
 
 export {
 	agent,
@@ -25,5 +26,6 @@ export {
 	agentApplication,
 	wiki,
 	aiChat,
-	resource
+	resource,
+	agentLog
 }

+ 260 - 0
packages/api-service/schema/agent.openapi.json

@@ -23,9 +23,269 @@
 		{
 			"name": "tools",
 			"description": "工具相关接口"
+		},
+		{
+			"name": "agentLog",
+			"description": "日志相关接口"
 		}
 	],
 	"paths": {
+		"/api/agent/getBefore7DayAgentRunnerStatic": {
+			"post": {
+				"summary": "获取7天前的智能体运行状态",
+				"deprecated": false,
+				"description": "",
+				"tags": ["agentLog"],
+				"parameters": [
+					{
+						"name": "Authorization",
+						"in": "header",
+						"description": "",
+						"example": "bpm_client_1519145361682206720",
+						"schema": {
+							"type": "string"
+						}
+					}
+				],
+				"requestBody": {
+					"content": {
+						"application/json": {
+							"schema": {
+								"type": "object",
+								"properties": {}
+							},
+							"example": {}
+						}
+					},
+					"required": true
+				},
+				"responses": {
+					"200": {
+						"description": "",
+						"content": {
+							"application/json": {
+								"schema": {
+									"type": "object",
+									"properties": {
+										"isSuccess": {
+											"type": "boolean"
+										},
+										"code": {
+											"type": "integer"
+										},
+										"result": {
+											"type": "object",
+											"properties": {
+												"avg_elapsed_sec": {
+													"type": "string"
+												},
+												"error_total": {
+													"type": "integer"
+												},
+												"process_total": {
+													"type": "integer"
+												},
+												"run_total": {
+													"type": "integer"
+												},
+												"success_total": {
+													"type": "integer"
+												}
+											},
+											"required": [
+												"avg_elapsed_sec",
+												"error_total",
+												"process_total",
+												"run_total",
+												"success_total"
+											]
+										},
+										"isAuthorized": {
+											"type": "boolean"
+										}
+									},
+									"required": ["isSuccess", "code", "result", "isAuthorized"]
+								},
+								"example": {
+									"isSuccess": true,
+									"code": 1,
+									"result": {
+										"avg_elapsed_time": "2000.00",
+										"error_total": 0,
+										"run_total": 1,
+										"success_total": 1
+									},
+									"isAuthorized": true
+								}
+							}
+						},
+						"headers": {}
+					}
+				},
+				"security": []
+			}
+		},
+		"/api/agent/getAgentRunnerPageList": {
+			"post": {
+				"summary": "获取智能体运行日志分页列表",
+				"deprecated": false,
+				"description": "",
+				"tags": ["agentLog"],
+				"parameters": [
+					{
+						"name": "Authorization",
+						"in": "header",
+						"description": "",
+						"example": "bpm_client_1519145361682206720",
+						"schema": {
+							"type": "string"
+						}
+					}
+				],
+				"requestBody": {
+					"content": {
+						"application/json": {
+							"schema": {
+								"type": "object",
+								"properties": {
+									"keyword": {
+										"type": "string"
+									},
+									"status": {
+										"type": "string"
+									},
+									"source": {
+										"type": "string"
+									},
+									"pageIndex": {
+										"type": "integer"
+									},
+									"pageSize": {
+										"type": "integer"
+									}
+								},
+								"required": ["keyword", "status", "source", "pageIndex", "pageSize"]
+							},
+							"example": {
+								"keyword": "",
+								"status": "running,finish,error",
+								"source": "manual,schedule,webhook,api",
+								"pageIndex": 1,
+								"pageSize": 20
+							}
+						}
+					},
+					"required": true
+				},
+				"responses": {
+					"200": {
+						"description": "",
+						"content": {
+							"application/json": {
+								"schema": {
+									"type": "object",
+									"properties": {
+										"isSuccess": {
+											"type": "boolean"
+										},
+										"code": {
+											"type": "integer"
+										},
+										"result": {
+											"type": "object",
+											"properties": {
+												"currentPage": {
+													"type": "integer"
+												},
+												"hasNextPage": {
+													"type": "boolean"
+												},
+												"hasPreviousPage": {
+													"type": "boolean"
+												},
+												"model": {
+													"type": "array",
+													"items": {
+														"type": "object",
+														"properties": {
+															"agentName": {
+																"type": "string"
+															},
+															"beginDate": {
+																"type": "string"
+															},
+															"endDate": {
+																"type": "string"
+															},
+															"runnerId": {
+																"type": "string"
+															},
+															"status": {
+																"type": "string"
+															},
+															"useTime": {
+																"type": "integer"
+															}
+														}
+													}
+												},
+												"pageSize": {
+													"type": "integer"
+												},
+												"totalCount": {
+													"type": "integer"
+												},
+												"totalPages": {
+													"type": "integer"
+												}
+											},
+											"required": [
+												"currentPage",
+												"hasNextPage",
+												"hasPreviousPage",
+												"model",
+												"pageSize",
+												"totalCount",
+												"totalPages"
+											]
+										},
+										"isAuthorized": {
+											"type": "boolean"
+										}
+									},
+									"required": ["isSuccess", "code", "result", "isAuthorized"]
+								},
+								"example": {
+									"isSuccess": true,
+									"code": 1,
+									"result": {
+										"currentPage": 1,
+										"hasNextPage": false,
+										"hasPreviousPage": false,
+										"model": [
+											{
+												"agentName": "智能编演示",
+												"beginDate": "2026-06-03 16:34:01",
+												"endDate": "2026-06-03 16:34:03",
+												"runnerId": "3b73469d-329d-493e-bc69-d0f3f8e33c72",
+												"status": "finish",
+												"useTime": 2000
+											}
+										],
+										"pageSize": 20,
+										"totalCount": 1,
+										"totalPages": 1
+									},
+									"isAuthorized": true
+								}
+							}
+						},
+						"headers": {}
+					}
+				},
+				"security": []
+			}
+		},
 		"/api/agent/doDeleteAgent": {
 			"post": {
 				"summary": "删除智能体",

+ 70 - 0
packages/api-service/servers/api/agentLog.ts

@@ -0,0 +1,70 @@
+// @ts-ignore
+/* eslint-disable */
+import request from '@repo/api-client'
+
+/** 获取智能体运行日志分页列表 POST /api/agent/getAgentRunnerPageList */
+export async function postAgentGetAgentRunnerPageList(
+  body: {
+    keyword: string
+    status: string
+    source: string
+    pageIndex: number
+    pageSize: number
+  },
+  options?: { [key: string]: any }
+) {
+  return request<{
+    isSuccess: boolean
+    code: number
+    result: {
+      currentPage: number
+      hasNextPage: boolean
+      hasPreviousPage: boolean
+      model: {
+        agentName?: string
+        beginDate?: string
+        endDate?: string
+        runnerId?: string
+        status?: string
+        useTime?: number
+      }[]
+      pageSize: number
+      totalCount: number
+      totalPages: number
+    }
+    isAuthorized: boolean
+  }>('/api/agent/getAgentRunnerPageList', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json'
+    },
+    data: body,
+    ...(options || {})
+  })
+}
+
+/** 获取7天前的智能体运行状态 POST /api/agent/getBefore7DayAgentRunnerStatic */
+export async function postAgentGetBefore7DayAgentRunnerStatic(
+  body: {},
+  options?: { [key: string]: any }
+) {
+  return request<{
+    isSuccess: boolean
+    code: number
+    result: {
+      avg_elapsed_sec: string
+      error_total: number
+      process_total: number
+      run_total: number
+      success_total: number
+    }
+    isAuthorized: boolean
+  }>('/api/agent/getBefore7DayAgentRunnerStatic', {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json'
+    },
+    data: body,
+    ...(options || {})
+  })
+}

+ 2 - 0
packages/api-service/servers/api/index.ts

@@ -2,10 +2,12 @@
 /* eslint-disable */
 // API 更新时间:
 // API 唯一标识:
+import * as agentLog from './agentLog'
 import * as agent from './agent'
 import * as agentApplication from './agentApplication'
 import * as tools from './tools'
 export default {
+  agentLog,
   agent,
   agentApplication,
   tools