- 当前版本
- {{ selectedSkill?.version }}
+ 当前展示版本
+ {{ selectedSkill?.displayVersion }}
diff --git a/web/src/views/scripts/AuditView.js b/web/src/views/scripts/AuditView.js
index d8c1c4f..1ed72c6 100644
--- a/web/src/views/scripts/AuditView.js
+++ b/web/src/views/scripts/AuditView.js
@@ -1,122 +1,796 @@
-import { computed, ref, watch } from 'vue'
+import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
+
+import { useSystemState } from '../../composables/useSystemState.js'
+import { useToast } from '../../composables/useToast.js'
+import {
+ activateAgentAsset,
+ createAgentAssetReview,
+ createAgentAssetVersion,
+ fetchAgentAssetDetail,
+ fetchAgentAssets,
+ fetchAgentRuns
+} from '../../services/agentAssets.js'
+import { isManagerUser } from '../../utils/accessControl.js'
const TYPE_META = {
rules: {
- createButtonLabel: '新建规则',
- hintText: '规则对应报销审查 .md 文件,点击任意行查看规则内容、审核状态与版本。',
- filters: ['按规则场景筛选', '按风险等级筛选', '按负责人筛选'],
+ assetType: 'rule',
+ label: '规则',
+ typeLabel: '规则',
+ createButtonLabel: '规则已接入',
+ hintText: '规则列表已接到真实资产 API,可查看 Markdown、版本、审核状态和上线约束。',
+ searchPlaceholder: '搜索规则名称、编码或负责人',
tableColumns: {
- name: '规则文件',
- category: '规则分类',
+ name: '规则名称',
+ category: '业务域',
owner: '负责人',
- scope: '适用范围',
- runtime: '执行节点',
- version: '版本',
- metric: '命中率'
+ scope: '适用场景',
+ runtime: '风险等级',
+ version: '当前版本',
+ metric: '审核状态'
}
},
skills: {
- createButtonLabel: '新建技能',
- hintText: '技能用于承载可复用的审批、问答、解释和数据处理能力。',
- filters: ['按技能场景筛选', '按调用方式筛选', '按负责人筛选'],
+ assetType: 'skill',
+ label: '技能',
+ typeLabel: '技能',
+ createButtonLabel: '技能已接入',
+ hintText: '技能页签已接到真实资产 API,可查看输入、输出、依赖和场景信息。',
+ searchPlaceholder: '搜索技能名称、编码或负责人',
tableColumns: {
name: '技能名称',
- category: '技能分类',
+ category: '业务域',
owner: '负责人',
- scope: '适用范围',
- runtime: '调用方式',
- version: '版本',
- metric: '命中率'
+ scope: '适用场景',
+ runtime: '输入摘要',
+ version: '当前版本',
+ metric: '输出摘要'
}
},
mcp: {
- createButtonLabel: '接入 MCP',
- hintText: 'MCP 管理外部服务连接,如发票验真、预算、银行流水、OCR 与差旅平台。',
- filters: ['按服务类型筛选', '按权限筛选', '按状态筛选'],
+ assetType: 'mcp',
+ label: 'MCP',
+ typeLabel: 'MCP',
+ createButtonLabel: 'MCP 已接入',
+ hintText: 'MCP 页签已接到真实资产 API,可查看服务地址、鉴权方式、超时和降级策略。',
+ searchPlaceholder: '搜索 MCP 名称、编码或负责人',
tableColumns: {
name: 'MCP 服务',
- category: '服务类型',
+ category: '业务域',
owner: '维护人',
- scope: '服务范围',
- runtime: '调用方式',
- version: '协议',
- metric: '可用率'
+ scope: '适用场景',
+ runtime: '调用地址',
+ version: '当前版本',
+ metric: '超时配置'
}
},
- schedules: {
- createButtonLabel: '新建任务',
- hintText: '任务用于每日风险检查、知识积累、报销报账和账款信息统计。',
- filters: ['按运行频率筛选', '按产出类型筛选', '按告警级别筛选'],
+ tasks: {
+ assetType: 'task',
+ label: '任务',
+ typeLabel: '任务',
+ createButtonLabel: '任务已接入',
+ hintText: '任务页签已接到真实资产 API,可查看调度周期、执行 Agent 和最近执行结果。',
+ searchPlaceholder: '搜索任务名称、编码或负责人',
tableColumns: {
name: '任务名称',
- category: '任务类型',
+ category: '业务域',
owner: '负责人',
- scope: '统计范围',
- runtime: '运行计划',
- version: '调度',
- metric: '成功率'
+ scope: '适用场景',
+ runtime: '调度周期',
+ version: '当前版本',
+ metric: '执行 Agent'
}
}
}
-function buildAsset({
- type,
- typeLabel,
- id,
- short,
- name,
- summary,
- category,
- owner,
- reviewer = '待分配',
- scope,
- model,
- version,
- status,
- statusTone,
- hitRate,
- updatedAt,
- badgeTone,
- triggerMode,
- spotlight = false,
- fields,
- sections,
- rules,
- checks,
- triggers,
- tools,
- history,
- titles,
- publish,
- markdownContent = ''
-}) {
+const BADGE_TONES = {
+ rules: 'emerald',
+ skills: 'blue',
+ mcp: 'amber',
+ tasks: 'violet'
+}
+
+const STATUS_META = {
+ draft: { label: '草稿中', tone: 'draft' },
+ review: { label: '待审核', tone: 'warning' },
+ active: { label: '已上线', tone: 'success' },
+ disabled: { label: '已停用', tone: 'disabled' }
+}
+
+const REVIEW_META = {
+ approved: { label: '已通过', tone: 'success' },
+ pending: { label: '待审核', tone: 'warning' },
+ rejected: { label: '已驳回', tone: 'danger' }
+}
+
+const DOMAIN_LABELS = {
+ expense: '报销',
+ ar: '应收',
+ ap: '应付',
+ knowledge: '知识',
+ system: '系统'
+}
+
+const SCENARIO_LABELS = {
+ expense: '报销',
+ risk_check: '风险检查',
+ duplicate_expense: '重复报销',
+ explain: '规则解释',
+ invoice_anomaly: '票据异常',
+ accounts_payable: '应付',
+ accounts_receivable: '应收',
+ approval_required: '需审批',
+ query: '查询',
+ summary: '汇总',
+ system: '系统',
+ schedule: '调度',
+ rule_center: '规则中心',
+ review_digest: '待审摘要',
+ aging_summary: '账龄汇总',
+ invoice_validation: '发票验真'
+}
+
+const DETAIL_TITLES = {
+ rules: {
+ configTitle: '规则元信息',
+ configDesc: '展示规则编码、版本、业务域和当前审核 / 上线状态。',
+ detailTitle: '规则版本说明',
+ detailDesc: '规则正文由 Markdown 驱动,保存后会生成新的版本快照。',
+ outputTitle: '审核与上线',
+ outputDesc: '规则上线受审核状态控制,未审核通过的版本会被后端拦截。',
+ ruleListTitle: '上线要求',
+ checkListTitle: '当前状态',
+ triggerTitle: '适用场景',
+ triggerDesc: '当前规则注册到的业务场景',
+ toolTitle: '关联信息',
+ toolDesc: '规则当前审核、保存和版本快照信息',
+ historyTitle: '版本历史',
+ historyDesc: '最近 5 个规则版本',
+ publishTitle: '上线控制',
+ publishDesc: '正式上线会调用后端激活接口,审核未通过时会被拦截。'
+ },
+ skills: {
+ configTitle: '技能配置',
+ configDesc: '展示技能编码、输入摘要、版本和业务域。',
+ detailTitle: '技能结构',
+ detailDesc: '按输入、输出和依赖组织技能定义。',
+ outputTitle: '输出契约',
+ outputDesc: '技能详情重点展示输入参数、输出参数和依赖能力。',
+ ruleListTitle: '输出要求',
+ checkListTitle: '当前快照',
+ triggerTitle: '适用场景',
+ triggerDesc: '当前技能注册到的场景标签',
+ toolTitle: '依赖能力',
+ toolDesc: '技能当前依赖的数据库或其他能力',
+ historyTitle: '版本历史',
+ historyDesc: '最近版本记录',
+ publishTitle: '发布状态',
+ publishDesc: '技能当前状态由资产中心统一管理。'
+ },
+ mcp: {
+ configTitle: 'MCP 连接配置',
+ configDesc: '展示服务地址、超时和调用方式。',
+ detailTitle: '服务协议',
+ detailDesc: '按服务类型、鉴权方式和降级策略组织外部服务信息。',
+ outputTitle: '调用约束',
+ outputDesc: 'MCP 详情重点展示鉴权方式、返回策略和最近调用状态。',
+ ruleListTitle: '调用约束',
+ checkListTitle: '最近状态',
+ triggerTitle: '适用场景',
+ triggerDesc: '当前 MCP 覆盖的业务场景',
+ toolTitle: '运行信息',
+ toolDesc: '结合 AgentRun 中的 ToolCall 还原最近一次调用状态',
+ historyTitle: '版本历史',
+ historyDesc: '最近版本记录',
+ publishTitle: '服务状态',
+ publishDesc: 'MCP 资产已接入规则中心,但真实外部调用仍以后续链路集成为准。'
+ },
+ tasks: {
+ configTitle: '任务配置',
+ configDesc: '展示调度周期、执行 Agent 和任务编码。',
+ detailTitle: '任务结构',
+ detailDesc: '按调度计划、目标场景和运行结果组织任务信息。',
+ outputTitle: '运行要求',
+ outputDesc: '任务详情重点展示调度 Agent、最近运行结果和运行日志入口。',
+ ruleListTitle: '运行要求',
+ checkListTitle: '最近执行',
+ triggerTitle: '适用场景',
+ triggerDesc: '当前任务覆盖的业务场景',
+ toolTitle: '最近调用',
+ toolDesc: '根据 AgentRun 中的最近执行记录回显任务运行情况',
+ historyTitle: '版本历史',
+ historyDesc: '最近版本记录',
+ publishTitle: '调度状态',
+ publishDesc: '任务资产已接入规则中心,后续 Day 4 运行时会继续消费这些配置。'
+ }
+}
+
+const STATUS_OPTIONS = [
+ { value: '', label: '全部状态' },
+ { value: 'draft', label: '草稿中' },
+ { value: 'review', label: '待审核' },
+ { value: 'active', label: '已上线' },
+ { value: 'disabled', label: '已停用' }
+]
+
+function normalizeText(value) {
+ return String(value || '').trim()
+}
+
+function makeShort(value) {
+ const text = normalizeText(value).replace(/\s+/g, '')
+ if (!text) {
+ return 'AG'
+ }
+ return text.slice(0, 2).toUpperCase()
+}
+
+function formatDateTime(value) {
+ if (!value) {
+ return '未记录'
+ }
+
+ const date = new Date(value)
+ if (Number.isNaN(date.getTime())) {
+ return String(value)
+ }
+
+ return new Intl.DateTimeFormat('zh-CN', {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit',
+ hour: '2-digit',
+ minute: '2-digit',
+ hour12: false
+ })
+ .format(date)
+ .replace(/\//g, '-')
+}
+
+function resolveDomainLabel(value) {
+ return DOMAIN_LABELS[value] || normalizeText(value) || '未分类'
+}
+
+function resolveStatusMeta(value) {
+ return STATUS_META[value] || { label: normalizeText(value) || '未知状态', tone: 'draft' }
+}
+
+function resolveReviewMeta(value) {
+ return REVIEW_META[value] || { label: '暂无审核', tone: 'draft' }
+}
+
+function formatScenarioList(items) {
+ if (!Array.isArray(items) || !items.length) {
+ return '未配置场景'
+ }
+
+ return items
+ .map((item) => SCENARIO_LABELS[item] || item)
+ .filter(Boolean)
+ .join(' / ')
+}
+
+function buildHistory(recentVersions = []) {
+ return recentVersions.map((item) => ({
+ version: item.version,
+ note: item.change_note || '无版本说明',
+ time: formatDateTime(item.created_at),
+ content: item.content,
+ contentType: item.content_type,
+ createdBy: item.created_by,
+ isCurrent: Boolean(item.is_current)
+ }))
+}
+
+function resolveTypeKey(assetType) {
+ if (assetType === 'rule') {
+ return 'rules'
+ }
+ if (assetType === 'skill') {
+ return 'skills'
+ }
+ if (assetType === 'mcp') {
+ return 'mcp'
+ }
+ return 'tasks'
+}
+
+function formatSeverity(value) {
+ const severity = normalizeText(value).toLowerCase()
+ if (severity === 'high') {
+ return '高风险'
+ }
+ if (severity === 'medium') {
+ return '中风险'
+ }
+ if (severity === 'low') {
+ return '低风险'
+ }
+ return '未配置'
+}
+
+function formatInputSummary(items) {
+ if (!Array.isArray(items) || !items.length) {
+ return '无输入'
+ }
+ return `${items.length} 项输入`
+}
+
+function formatOutputSummary(items) {
+ if (!Array.isArray(items) || !items.length) {
+ return '无输出'
+ }
+ return `${items.length} 项输出`
+}
+
+function formatTaskRisk(scenarios) {
+ if (Array.isArray(scenarios) && scenarios.includes('risk_check')) {
+ return '高风险'
+ }
+ if (
+ Array.isArray(scenarios) &&
+ (scenarios.includes('accounts_receivable') || scenarios.includes('accounts_payable'))
+ ) {
+ return '中风险'
+ }
+ return '常规'
+}
+
+function findLatestTaskRun(runs, assetId) {
+ return runs.find((item) => item.task_id === assetId) || null
+}
+
+function findLatestMcpCall(runs, assetCode) {
+ const expectedToolName = normalizeText(assetCode).replace(/^mcp\./, '')
+
+ for (const run of runs) {
+ for (const toolCall of run.tool_calls || []) {
+ const toolName = normalizeText(toolCall.tool_name)
+ if (
+ toolName === expectedToolName ||
+ toolName.endsWith(expectedToolName) ||
+ expectedToolName.endsWith(toolName)
+ ) {
+ return {
+ run,
+ toolCall
+ }
+ }
+ }
+ }
+
+ return null
+}
+
+function buildRowRuntime(asset, typeKey) {
+ if (typeKey === 'rules') {
+ return formatSeverity(asset.config_json?.severity)
+ }
+ if (typeKey === 'skills') {
+ return formatInputSummary(asset.config_json?.input_schema)
+ }
+ if (typeKey === 'mcp') {
+ return normalizeText(asset.config_json?.endpoint) || '未配置地址'
+ }
+ return normalizeText(asset.config_json?.cron) || '未配置调度'
+}
+
+function buildRowMetric(asset, typeKey) {
+ if (typeKey === 'rules') {
+ return asset.reviewer ? `审核人:${asset.reviewer}` : '待分配审核人'
+ }
+ if (typeKey === 'skills') {
+ return '进入详情查看输出'
+ }
+ if (typeKey === 'mcp') {
+ return asset.config_json?.timeout_ms ? `${asset.config_json.timeout_ms} ms` : '未配置超时'
+ }
+ return normalizeText(asset.config_json?.agent) || '未配置 Agent'
+}
+
+function buildListItem(asset) {
+ const typeKey = resolveTypeKey(asset.asset_type)
+ const statusMeta = resolveStatusMeta(asset.status)
+
return {
- type,
- typeLabel,
- id,
- short,
- name,
- summary,
- category,
- owner,
- reviewer,
- scope,
- model,
- version,
- status,
- statusTone,
- hitRate,
- updatedAt,
- badgeTone,
- triggerMode,
- spotlight,
- markdownContent,
- fields,
- promptSections: sections,
- outputRules: rules,
- tests: checks,
- triggers,
- tools,
+ id: asset.id,
+ type: typeKey,
+ typeLabel: TYPE_META[typeKey].typeLabel,
+ short: makeShort(asset.name),
+ name: asset.name,
+ code: asset.code,
+ summary: asset.description,
+ category: resolveDomainLabel(asset.domain),
+ owner: asset.owner,
+ reviewer: asset.reviewer || '待分配',
+ scope: formatScenarioList(asset.scenario_json),
+ model: buildRowRuntime(asset, typeKey),
+ version: asset.current_version || '-',
+ status: statusMeta.label,
+ statusValue: asset.status,
+ statusTone: statusMeta.tone,
+ hitRate: buildRowMetric(asset, typeKey),
+ updatedAt: formatDateTime(asset.updated_at),
+ badgeTone: BADGE_TONES[typeKey],
+ spotlight: asset.status === 'active',
+ domainValue: asset.domain
+ }
+}
+
+function buildRuleFields(detail) {
+ return [
+ { label: '规则编码', value: detail.code },
+ { label: '业务域', value: resolveDomainLabel(detail.domain) },
+ { label: '适用场景', value: formatScenarioList(detail.scenario_json) },
+ { label: '当前版本', value: detail.current_version || '-' }
+ ]
+}
+
+function buildSkillFields(detail) {
+ const content = detail.current_version_content || {}
+ return [
+ { label: '技能编码', value: detail.code },
+ { label: '业务域', value: resolveDomainLabel(detail.domain) },
+ {
+ label: '输入参数',
+ value: Array.isArray(content.inputs) && content.inputs.length ? content.inputs.join('、') : '未配置'
+ },
+ {
+ label: '输出参数',
+ value: Array.isArray(content.outputs) && content.outputs.length ? content.outputs.join('、') : '未配置'
+ }
+ ]
+}
+
+function buildMcpFields(detail, latestCall) {
+ const content = detail.current_version_content || {}
+ return [
+ { label: '服务编码', value: detail.code },
+ { label: '调用地址', value: normalizeText(detail.config_json?.endpoint) || '未配置' },
+ { label: '鉴权方式', value: normalizeText(content.auth_mode) || '未配置' },
+ {
+ label: '最近调用',
+ value: latestCall ? `${latestCall.toolCall.status} / ${formatDateTime(latestCall.run.started_at)}` : '暂无调用记录'
+ }
+ ]
+}
+
+function buildTaskFields(detail, latestRun) {
+ const content = detail.current_version_content || {}
+ return [
+ { label: '任务编码', value: detail.code },
+ { label: 'Cron', value: normalizeText(detail.config_json?.cron) || normalizeText(content.schedule) || '未配置' },
+ { label: '执行 Agent', value: normalizeText(detail.config_json?.agent) || normalizeText(content.target_agent) || '未配置' },
+ { label: '风险等级', value: formatTaskRisk(detail.scenario_json) },
+ {
+ label: '最近执行',
+ value: latestRun ? formatDateTime(latestRun.started_at) : '暂无执行记录'
+ }
+ ]
+}
+
+function buildFields(detail, typeKey, latestRun, latestCall) {
+ if (typeKey === 'rules') {
+ return buildRuleFields(detail)
+ }
+ if (typeKey === 'skills') {
+ return buildSkillFields(detail)
+ }
+ if (typeKey === 'mcp') {
+ return buildMcpFields(detail, latestCall)
+ }
+ return buildTaskFields(detail, latestRun)
+}
+
+function buildPromptSections(detail, typeKey, latestRun, latestCall) {
+ const content = detail.current_version_content || {}
+
+ if (typeKey === 'skills') {
+ return [
+ {
+ title: '输入参数',
+ intent: '技能入口',
+ content: Array.isArray(content.inputs) && content.inputs.length ? content.inputs.join('\n') : '未配置输入参数。'
+ },
+ {
+ title: '输出参数',
+ intent: '技能产出',
+ content: Array.isArray(content.outputs) && content.outputs.length ? content.outputs.join('\n') : '未配置输出参数。'
+ },
+ {
+ title: '依赖能力',
+ intent: '外部依赖',
+ content:
+ Array.isArray(content.dependencies) && content.dependencies.length
+ ? content.dependencies.join('\n')
+ : '当前技能未声明外部依赖。'
+ }
+ ]
+ }
+
+ if (typeKey === 'mcp') {
+ return [
+ {
+ title: '服务类型',
+ intent: '协议说明',
+ content: normalizeText(content.service_type) || '未配置服务类型。'
+ },
+ {
+ title: '鉴权方式',
+ intent: '安全要求',
+ content: normalizeText(content.auth_mode) || '未配置鉴权方式。'
+ },
+ {
+ title: '降级策略',
+ intent: '失败处理',
+ content: normalizeText(content.degrade_strategy) || '未配置降级策略。'
+ }
+ ]
+ }
+
+ return [
+ {
+ title: '任务场景',
+ intent: '调度目标',
+ content: formatScenarioList(detail.scenario_json)
+ },
+ {
+ title: '执行 Agent',
+ intent: '运行主体',
+ content: normalizeText(content.target_agent) || normalizeText(detail.config_json?.agent) || '未配置执行 Agent。'
+ },
+ {
+ title: '最近执行结果',
+ intent: '运行反馈',
+ content: latestRun?.result_summary || latestRun?.error_message || '暂无执行记录。'
+ }
+ ]
+}
+
+function buildOutputRules(detail, typeKey, latestRun, latestCall) {
+ const content = detail.current_version_content || {}
+
+ if (typeKey === 'rules') {
+ return [
+ '规则 Markdown 保存后会生成新版本。',
+ '未审核通过的规则版本不能正式上线。',
+ '版本切换当前只影响前端展示内容,不会直接回滚后端版本。'
+ ]
+ }
+
+ if (typeKey === 'skills') {
+ return [
+ `输入参数:${Array.isArray(content.inputs) && content.inputs.length ? content.inputs.join('、') : '未配置'}`,
+ `输出参数:${Array.isArray(content.outputs) && content.outputs.length ? content.outputs.join('、') : '未配置'}`,
+ `依赖能力:${Array.isArray(content.dependencies) && content.dependencies.length ? content.dependencies.join('、') : '未声明'}`
+ ]
+ }
+
+ if (typeKey === 'mcp') {
+ return [
+ `服务地址:${normalizeText(detail.config_json?.endpoint) || '未配置'}`,
+ `鉴权方式:${normalizeText(content.auth_mode) || '未配置'}`,
+ `降级策略:${normalizeText(content.degrade_strategy) || '未配置'}`
+ ]
+ }
+
+ return [
+ `调度周期:${normalizeText(detail.config_json?.cron) || normalizeText(content.schedule) || '未配置'}`,
+ `执行 Agent:${normalizeText(detail.config_json?.agent) || normalizeText(content.target_agent) || '未配置'}`,
+ `风险等级:${formatTaskRisk(detail.scenario_json)}`,
+ `最近执行结果:${latestRun?.status || '暂无执行记录'}`
+ ]
+}
+
+function buildTests(detail, typeKey, latestRun, latestCall) {
+ if (typeKey === 'rules') {
+ const reviewMeta = resolveReviewMeta(detail.latest_review?.review_status)
+ return [
+ {
+ name: '审核状态',
+ input: detail.latest_review?.version || detail.current_version || '暂无版本',
+ result: reviewMeta.label,
+ tone: reviewMeta.tone
+ },
+ {
+ name: '上线状态',
+ input: detail.current_version || '暂无版本',
+ result: resolveStatusMeta(detail.status).label,
+ tone: resolveStatusMeta(detail.status).tone
+ }
+ ]
+ }
+
+ if (typeKey === 'skills') {
+ const content = detail.current_version_content || {}
+ return [
+ {
+ name: '输入数量',
+ input: detail.current_version || '暂无版本',
+ result: `${content.inputs?.length || 0} 项`,
+ tone: 'success'
+ },
+ {
+ name: '输出数量',
+ input: detail.current_version || '暂无版本',
+ result: `${content.outputs?.length || 0} 项`,
+ tone: 'success'
+ }
+ ]
+ }
+
+ if (typeKey === 'mcp') {
+ return [
+ {
+ name: '最近调用状态',
+ input: latestCall?.run?.run_id || '暂无调用',
+ result: latestCall?.toolCall?.status || '未记录',
+ tone: latestCall?.toolCall?.status === 'failed' ? 'danger' : 'success'
+ },
+ {
+ name: '最近调用耗时',
+ input: latestCall?.toolCall?.tool_name || '暂无调用',
+ result:
+ typeof latestCall?.toolCall?.duration_ms === 'number'
+ ? `${latestCall.toolCall.duration_ms} ms`
+ : '未记录',
+ tone: 'success'
+ }
+ ]
+ }
+
+ return [
+ {
+ name: '最近运行状态',
+ input: latestRun?.run_id || '暂无运行',
+ result: latestRun?.status || '未记录',
+ tone: latestRun?.status === 'failed' || latestRun?.status === 'blocked' ? 'danger' : 'success'
+ },
+ {
+ name: '结果摘要',
+ input: latestRun?.agent || normalizeText(detail.config_json?.agent) || '未配置',
+ result: latestRun?.result_summary || '暂无摘要',
+ tone: 'success'
+ }
+ ]
+}
+
+function buildTools(detail, typeKey, latestRun, latestCall) {
+ const content = detail.current_version_content || {}
+
+ if (typeKey === 'skills') {
+ return (content.dependencies || []).map((item) => ({
+ name: item,
+ scope: '技能依赖',
+ mode: '读取',
+ tone: 'safe'
+ }))
+ }
+
+ if (typeKey === 'mcp') {
+ return [
+ {
+ name: normalizeText(content.service_type) || '未配置服务类型',
+ scope: '服务类型',
+ mode: 'MCP',
+ tone: 'active'
+ },
+ {
+ name: normalizeText(content.auth_mode) || '未配置鉴权方式',
+ scope: '鉴权',
+ mode: '安全',
+ tone: 'safe'
+ },
+ {
+ name: latestCall?.run?.run_id || '暂无调用记录',
+ scope: '最近 Run',
+ mode: latestCall?.toolCall?.status || '未执行',
+ tone: latestCall?.toolCall?.status === 'failed' ? 'danger' : 'active'
+ }
+ ]
+ }
+
+ return [
+ {
+ name: normalizeText(detail.config_json?.agent) || normalizeText(content.target_agent) || '未配置 Agent',
+ scope: '执行 Agent',
+ mode: '调度',
+ tone: 'active'
+ },
+ {
+ name: latestRun?.run_id || '暂无执行记录',
+ scope: '最近 Run',
+ mode: latestRun?.status || '未执行',
+ tone: latestRun?.status === 'failed' || latestRun?.status === 'blocked' ? 'danger' : 'active'
+ },
+ {
+ name: latestRun?.permission_level || '未记录',
+ scope: '权限级别',
+ mode: 'Trace',
+ tone: 'safe'
+ }
+ ]
+}
+
+function buildPublishDescription(detail, typeKey) {
+ if (typeKey === 'rules') {
+ if (detail.status === 'active') {
+ return '当前规则版本已经上线,仍可继续保存新版本并重新走审核。'
+ }
+ return '当前规则需要先完成审核,再调用上线接口正式激活。'
+ }
+
+ return DETAIL_TITLES[typeKey].publishDesc
+}
+
+function buildDetailViewModel(detail, runs) {
+ const typeKey = resolveTypeKey(detail.asset_type)
+ const latestRun = typeKey === 'tasks' ? findLatestTaskRun(runs, detail.id) : null
+ const latestCall = typeKey === 'mcp' ? findLatestMcpCall(runs, detail.code) : null
+ const statusMeta = resolveStatusMeta(detail.status)
+ const reviewMeta = resolveReviewMeta(detail.latest_review?.review_status)
+ const history = buildHistory(detail.recent_versions || [])
+ const previewVersion = history.find((item) => item.isCurrent) || history[0] || null
+ const previewMarkdown =
+ detail.current_version_content_type === 'markdown'
+ ? String(previewVersion?.content ?? detail.current_version_content ?? '')
+ : ''
+ const titles = DETAIL_TITLES[typeKey]
+
+ return {
+ id: detail.id,
+ type: typeKey,
+ typeLabel: TYPE_META[typeKey].typeLabel,
+ short: makeShort(detail.name),
+ name: detail.name,
+ code: detail.code,
+ summary: detail.description,
+ owner: detail.owner,
+ reviewer: detail.reviewer || detail.latest_review?.reviewer || '待分配',
+ category: resolveDomainLabel(detail.domain),
+ scope: formatScenarioList(detail.scenario_json),
+ version: detail.current_version || '-',
+ currentVersion: detail.current_version || '-',
+ displayVersion: previewVersion?.version || detail.current_version || '-',
+ status: statusMeta.label,
+ statusValue: detail.status,
+ statusTone: statusMeta.tone,
+ hitRate: buildRowMetric(detail, typeKey),
+ updatedAt: formatDateTime(detail.updated_at),
+ badgeTone: BADGE_TONES[typeKey],
+ markdownContent: previewMarkdown,
+ currentVersionContentType: detail.current_version_content_type,
+ currentVersionChangeNote: detail.current_version_change_note || '无版本说明',
+ reviewStatusLabel: reviewMeta.label,
+ reviewStatusTone: reviewMeta.tone,
+ reviewStatusValue: detail.latest_review?.review_status || '',
+ reviewTimeLabel: formatDateTime(detail.latest_review?.reviewed_at),
+ reviewNote: detail.latest_review?.review_note || '',
+ latestRun,
+ latestCall,
+ fields: buildFields(detail, typeKey, latestRun, latestCall),
+ promptSections:
+ typeKey === 'rules' ? [] : buildPromptSections(detail, typeKey, latestRun, latestCall),
+ outputRules: buildOutputRules(detail, typeKey, latestRun, latestCall),
+ tests: buildTests(detail, typeKey, latestRun, latestCall),
+ triggers: detail.scenario_json?.length ? detail.scenario_json.map((item) => SCENARIO_LABELS[item] || item) : ['未配置场景'],
+ tools:
+ typeKey === 'rules'
+ ? [
+ {
+ name: detail.latest_review?.reviewer || '待分配审核人',
+ scope: '审核负责人',
+ mode: reviewMeta.label,
+ tone: reviewMeta.tone
+ },
+ {
+ name: detail.current_version || '暂无版本',
+ scope: '当前版本',
+ mode: detail.current_version_change_note || '无版本说明',
+ tone: 'safe'
+ }
+ ]
+ : buildTools(detail, typeKey, latestRun, latestCall),
history,
configTitle: titles.configTitle,
configDesc: titles.configDesc,
@@ -133,702 +807,176 @@ function buildAsset({
historyTitle: titles.historyTitle,
historyDesc: titles.historyDesc,
publishTitle: titles.publishTitle,
- publishDesc: titles.publishDesc,
- publishMeta: publish.meta,
- publishState: publish.state,
- heroStats: [
- { label: type === 'schedules' ? '调度计划' : '版本', value: version },
- { label: '状态', value: status, kind: 'status' },
- { label: type === 'mcp' ? '服务可用率' : type === 'schedules' ? '运行成功率' : '触发命中率', value: hitRate },
- { label: '负责人', value: owner }
- ]
+ publishDesc: buildPublishDescription(detail, typeKey),
+ publishMeta:
+ typeKey === 'rules'
+ ? `最近保存:${formatDateTime(detail.updated_at)}`
+ : latestRun
+ ? `最近运行:${formatDateTime(latestRun.started_at)}`
+ : `最近更新:${formatDateTime(detail.updated_at)}`,
+ publishState: statusMeta.label,
+ latestReviewVersion: detail.latest_review?.version || detail.current_version || '-',
+ loading: false
}
}
-const assets = [
- buildAsset({
- type: 'rules',
- typeLabel: '规则',
- id: 'SKL-001',
- short: 'DR',
- name: '重复报销识别规则.md',
- summary: '识别同一发票号、金额、商户和日期组合下的重复报销风险。',
- category: '报销审查规则',
- owner: '财务风控组',
- reviewer: '周敏',
- scope: '发票与费用明细',
- model: '提交时 + 夜间复核',
- version: 'v1.9',
- status: '已上线',
- statusTone: 'success',
- hitRate: '96.8%',
- updatedAt: '2026-05-09 08:10',
- badgeTone: 'emerald',
- triggerMode: '申请提交 / 财务初审 / 夜间批扫',
- spotlight: true,
- fields: [
- { label: '规则名称', value: '重复报销识别规则.md' },
- { label: '文件路径', value: 'skills/reimbursement/duplicate-invoice.md' },
- { label: '规则等级', value: '高风险' },
- { label: '执行节点', value: '提交时 + 夜间复核' }
- ],
- markdownContent: `# 重复报销识别规则
+function incrementVersion(version) {
+ const normalized = normalizeText(version).replace(/^v/i, '')
+ const match = normalized.match(/^(\d+)\.(\d+)\.(\d+)$/)
-## 目标
-识别同一发票、同一商户金额组合或高度相似附件导致的重复报销风险。
+ if (!match) {
+ return 'v1.0.0'
+ }
-## 适用范围
-- 费用类型:差旅、办公采购、业务招待、供应商报账
-- 执行节点:申请提交、财务初审、夜间批扫
-- 风险等级:高风险
-
-## 输入字段
-- invoice_code:发票代码
-- invoice_number:发票号码
-- seller_name:销售方名称
-- seller_tax_no:销售方税号
-- total_amount:价税合计金额
-- issue_date:开票日期
-- attachment_hash:附件影像哈希
-- applicant_id:申请人
-- reimbursement_id:当前报销单号
-
-## 判断规则
-1. 发票代码和发票号码均一致时,直接命中高风险。
-2. 发票号码缺失时,销售方、金额、开票日期和附件哈希相似度超过 0.92,进入人工复核。
-3. 已驳回或已作废单据只作为参考证据,不直接判定重复。
-4. 同一供应商周期性账单需按账期字段排除误报。
-
-## 输出
-\`\`\`json
-{
- "risk_code": "duplicate_invoice",
- "risk_level": "high",
- "matched_reimbursement_ids": [],
- "evidence": [],
- "suggestion": "人工复核或要求申请人补充说明"
+ const major = Number(match[1])
+ const minor = Number(match[2])
+ const patch = Number(match[3]) + 1
+ return `v${major}.${minor}.${patch}`
}
-\`\`\`
-## 管理员备注
-- 调整阈值前需要抽样复核最近 7 日误报样例。
-- 新增排除条件必须补充测试样例。`,
- sections: [
- { title: '输入字段', intent: '审查依赖', content: '发票号、销售方、金额、开票日期、附件 OCR、历史报销单号、提交人和成本中心。' },
- { title: '判断逻辑', intent: '规则主体', content: '同一发票号直接命中;发票号缺失时按销售方、金额、日期和影像哈希计算相似度。' },
- { title: '输出格式', intent: '风险结果', content: '输出重复风险标签、历史单据编号、相似字段、相似度和建议处置动作。' }
- ],
- rules: ['命中同一发票号时必须标记高风险。', '相似度超过 0.92 时进入人工复核。', '必须返回历史单据证据,不允许只给结论。'],
- checks: [
- { name: '同发票号重复', input: '发票号完全一致', result: '通过', tone: 'success' },
- { name: '影像相似重复', input: '发票号模糊但金额日期一致', result: '通过', tone: 'success' },
- { name: '商户简称误报', input: '同商户不同日期', result: '待评审', tone: 'warning' }
- ],
- triggers: ['重复发票', '重复报销', '历史账本比对', '夜间风险扫描'],
- tools: [
- { name: '发票 OCR MCP', scope: '票面字段识别', mode: '只读', tone: 'safe' },
- { name: '报销账本', scope: '历史单据检索', mode: '只读', tone: 'safe' },
- { name: '风险标记服务', scope: '写入审查结果', mode: '写入', tone: 'active' }
- ],
- history: [
- { version: 'v1.9', note: '收紧金额差异阈值', time: '05-09 08:10' },
- { version: 'v1.8', note: '加入附件哈希比对', time: '05-06 18:30' },
- { version: 'v1.7', note: '补充同供应商周期账单排除条件', time: '05-02 16:15' },
- { version: 'v1.6', note: '增加历史驳回单据参考证据', time: '04-28 11:20' },
- { version: 'v1.5', note: '上线发票号精确匹配规则', time: '04-22 09:40' }
- ],
- titles: {
- configTitle: '规则文件配置',
- configDesc: '技能以 .md 文件维护报销审查规则,配置规则路径、等级和执行节点。',
- detailTitle: '规则结构',
- detailDesc: '按输入字段、判断逻辑和输出格式组织审查规则。',
- outputTitle: '产出与校验',
- outputDesc: '确保规则能稳定输出风险标签、证据和处置建议。',
- ruleListTitle: '输出要求',
- checkListTitle: '测试样例',
- triggerTitle: '触发规则',
- triggerDesc: '当前命中策略',
- toolTitle: '依赖能力',
- toolDesc: '规则运行时调用',
- historyTitle: '版本历史',
- historyDesc: '最近变更',
- publishTitle: '发布控制',
- publishDesc: '规则已通过核心样例,可进入线上审查链路。',
- },
- publish: { meta: '最近评审:2026-05-09 08:10', state: '可发布' }
- }),
- buildAsset({
- type: 'rules',
- typeLabel: '规则',
- id: 'SKL-002',
- short: 'TS',
- name: '差旅标准超额检查.md',
- summary: '按城市、职级和费用类型检查差旅报销是否超过制度标准。',
- category: '差旅审查规则',
- owner: '制度运营组',
- reviewer: '刘佳',
- scope: '差旅报销',
- model: '提交时触发',
- version: 'v2.4',
- status: '待评审',
- statusTone: 'warning',
- hitRate: '91.4%',
- updatedAt: '2026-05-08 18:35',
- badgeTone: 'amber',
- triggerMode: '申请提交 / 审批中心',
- fields: [
- { label: '规则名称', value: '差旅标准超额检查.md' },
- { label: '文件路径', value: 'skills/travel/standard-overrun.md' },
- { label: '规则等级', value: '中高风险' },
- { label: '执行节点', value: '提交时触发' }
- ],
- markdownContent: `# 差旅标准超额检查
-
-## 目标
-根据公司差旅制度,检查住宿、交通、餐补等费用是否超过员工职级和城市等级对应标准。
-
-## 适用范围
-- 单据类型:差旅报销、差旅借款冲销
-- 费用类型:住宿、机票、火车票、出租车、餐补
-- 执行节点:申请提交、审批中心
-
-## 输入字段
-- employee_grade:员工职级
-- destination_city:目的地城市
-- city_tier:城市等级
-- expense_type:费用类型
-- expense_amount:费用金额
-- travel_days:出差天数
-- policy_refs:制度条款映射
-- exception_reason:例外说明
-
-## 判断规则
-1. 按城市等级和员工职级读取报销标准上限。
-2. 单项费用超过标准时,计算超额金额和超额比例。
-3. 会议指定酒店、客户指定地点等例外原因存在时,转人工复核。
-4. 无例外说明且超额比例超过 20%,标记中高风险。
-
-## 输出
-\`\`\`json
-{
- "risk_code": "travel_standard_overrun",
- "risk_level": "medium_high",
- "over_limit_amount": 0,
- "policy_refs": [],
- "exception_required": true,
- "suggestion": "补充例外说明或调整报销金额"
+function buildReviewNote(status) {
+ if (status === 'approved') {
+ return '通过任务规则中心审核。'
+ }
+ if (status === 'rejected') {
+ return '在任务规则中心驳回当前版本。'
+ }
+ return '提交任务规则中心待审核。'
}
-\`\`\`
-
-## 管理员备注
-- 城市等级表每季度同步一次。
-- 制度条款更新后必须同步更新 policy_refs。`,
- sections: [
- { title: '制度映射', intent: '条款来源', content: '关联城市等级、员工职级、住宿/交通/餐补标准和例外审批条款。' },
- { title: '判断逻辑', intent: '标准比对', content: '按目的地城市等级和职级读取上限,逐条计算超额金额。' },
- { title: '输出格式', intent: '审批依据', content: '输出超额金额、制度引用、例外条件和补充说明要求。' }
- ],
- rules: ['必须给出超额金额。', '必须引用制度条款。', '例外审批必须标记人工复核。'],
- checks: [
- { name: '住宿超标', input: '一线城市住宿超 18%', result: '通过', tone: 'success' },
- { name: '会议酒店例外', input: '指定会议酒店超标', result: '评审中', tone: 'warning' }
- ],
- triggers: ['差旅标准', '住宿超标', '交通超标', '例外审批'],
- tools: [
- { name: '制度知识库', scope: '条款引用', mode: '只读', tone: 'safe' },
- { name: '员工组织服务', scope: '职级与部门', mode: '只读', tone: 'safe' }
- ],
- history: [
- { version: 'v2.4', note: '新增会议酒店例外分支', time: '05-08 18:35' },
- { version: 'v2.3', note: '同步 2026 差旅制度', time: '05-06 10:20' },
- { version: 'v2.2', note: '调整新一线城市住宿上限', time: '05-01 14:05' },
- { version: 'v2.1', note: '增加餐补按天数校验', time: '04-25 17:30' },
- { version: 'v2.0', note: '重构城市等级映射表', time: '04-18 10:10' }
- ],
- titles: {
- configTitle: '规则文件配置',
- configDesc: '维护差旅制度条款映射、城市等级表和例外审批条件。',
- detailTitle: '规则结构',
- detailDesc: '按制度映射、判断逻辑和输出格式组织规则。',
- outputTitle: '产出与校验',
- outputDesc: '超额检查结果进入审批意见和员工补充说明。',
- ruleListTitle: '输出要求',
- checkListTitle: '测试样例',
- triggerTitle: '触发规则',
- triggerDesc: '当前命中策略',
- toolTitle: '依赖能力',
- toolDesc: '规则运行时调用',
- historyTitle: '版本历史',
- historyDesc: '最近变更',
- publishTitle: '评审控制',
- publishDesc: '当前规则仍有例外分支待评审。',
- },
- publish: { meta: '最近评审:2026-05-08 18:35', state: '待评审' }
- }),
- buildAsset({
- type: 'rules',
- typeLabel: '规则',
- id: 'SKL-003',
- short: 'AT',
- name: '异常附件完整性检查.md',
- summary: '检查报销附件是否缺失、重复、模糊或与费用类型不匹配。',
- category: '附件审查规则',
- owner: '财务共享组',
- reviewer: '赵宁',
- scope: '票据与附件',
- model: '提交时触发',
- version: 'v1.3',
- status: '草稿中',
- statusTone: 'draft',
- hitRate: '84.6%',
- updatedAt: '2026-05-07 16:20',
- badgeTone: 'violet',
- triggerMode: '申请提交 / 补件复核',
- fields: [
- { label: '规则名称', value: '异常附件完整性检查.md' },
- { label: '文件路径', value: 'skills/reimbursement/attachment-integrity.md' },
- { label: '规则等级', value: '中风险' },
- { label: '执行节点', value: '提交时触发' }
- ],
- markdownContent: `# 异常附件完整性检查
-
-## 目标
-检查报销单据附件是否完整、清晰,并确认附件类型与费用明细匹配。
-
-## 适用范围
-- 单据类型:日常报销、差旅报销、供应商报账
-- 附件类型:发票、行程单、合同、审批截图、付款凭证
-- 执行节点:申请提交、补件复核
-
-## 输入字段
-- reimbursement_id:报销单号
-- expense_type:费用类型
-- attachment_list:附件列表
-- attachment_ocr_text:附件 OCR 文本
-- required_attachment_types:必需附件类型
-- applicant_note:申请人说明
-
-## 判断规则
-1. 必需附件缺失时,标记补件风险。
-2. 附件 OCR 置信度低于 0.75 时,标记影像不清晰。
-3. 附件类型与费用类型不匹配时,进入人工复核。
-4. 相同附件重复上传时,提示申请人清理重复附件。
-
-## 输出
-\`\`\`json
-{
- "risk_code": "attachment_integrity",
- "risk_level": "medium",
- "missing_attachments": [],
- "invalid_attachments": [],
- "suggestion": "补充缺失附件或重新上传清晰附件"
-}
-\`\`\`
-
-## 管理员备注
-- 必需附件清单由费用类型配置驱动。
-- 新增费用类型时必须同步补充 required_attachment_types。`,
- sections: [
- { title: '附件清单', intent: '审查依赖', content: '读取费用类型对应的必需附件、已上传附件、OCR 文本和申请人说明。' },
- { title: '判断逻辑', intent: '规则主体', content: '检查缺失、模糊、重复和类型不匹配四类附件问题。' },
- { title: '输出格式', intent: '补件结果', content: '输出缺失附件、异常附件、风险等级和补件建议。' }
- ],
- rules: ['缺少必需附件时必须阻断提交。', '影像不清晰时允许提交但标记补件。', '重复附件只提示清理,不计入高风险。'],
- checks: [
- { name: '缺少发票', input: '费用类型要求发票但未上传', result: '通过', tone: 'success' },
- { name: '附件模糊', input: 'OCR 置信度 0.62', result: '通过', tone: 'success' },
- { name: '合同附件误判', input: '合同页被识别为发票', result: '待修复', tone: 'warning' }
- ],
- triggers: ['附件缺失', '附件模糊', '补件复核', '类型不匹配'],
- tools: [
- { name: '附件 OCR MCP', scope: '附件文字识别', mode: '只读', tone: 'safe' },
- { name: '费用类型配置', scope: '必需附件清单', mode: '只读', tone: 'safe' }
- ],
- history: [
- { version: 'v1.3', note: '新增附件类型与费用类型匹配检查', time: '05-07 16:20' },
- { version: 'v1.2', note: '补充 OCR 置信度阈值', time: '05-04 11:35' },
- { version: 'v1.1', note: '加入重复附件提示', time: '04-29 15:10' },
- { version: 'v1.0', note: '上线缺失附件检查', time: '04-24 09:00' },
- { version: 'v0.9', note: '完成规则草案评审', time: '04-20 17:45' }
- ],
- titles: {
- configTitle: '规则文件配置',
- configDesc: '技能以 .md 文件维护报销审查规则,配置规则路径、等级和执行节点。',
- detailTitle: '规则结构',
- detailDesc: '按附件清单、判断逻辑和输出格式组织规则。',
- outputTitle: '产出与校验',
- outputDesc: '附件检查结果进入提交拦截和补件提示。',
- ruleListTitle: '输出要求',
- checkListTitle: '测试样例',
- triggerTitle: '触发规则',
- triggerDesc: '当前命中策略',
- toolTitle: '依赖能力',
- toolDesc: '规则运行时调用',
- historyTitle: '版本历史',
- historyDesc: '最近变更',
- publishTitle: '评审控制',
- publishDesc: '当前规则仍有误判样例待修复。',
- },
- publish: { meta: '最近评审:2026-05-07 16:20', state: '草稿中' }
- }),
- buildAsset({
- type: 'skills',
- typeLabel: '技能',
- id: 'SKL-AI-001',
- short: 'AP',
- name: '审批意见生成技能',
- summary: '基于单据、规则命中和制度依据生成可复用的审批意见。',
- category: '审批辅助技能',
- owner: '审批运营组',
- reviewer: '韩悦',
- scope: '财务审批',
- model: '审批中心按钮调用',
- version: 'v1.8',
- status: '已上线',
- statusTone: 'success',
- hitRate: '88.4%',
- updatedAt: '2026-05-09 10:20',
- badgeTone: 'blue',
- triggerMode: '审批中心显式调用',
- fields: [
- { label: '技能名称', value: '审批意见生成技能' },
- { label: '技能分类', value: '审批辅助技能' },
- { label: '调用入口', value: '审批中心 / 单据详情' },
- { label: '默认模型', value: 'GPT-5.4' }
- ],
- sections: [
- { title: '输入上下文', intent: '读取范围', content: '读取当前单据、规则命中结果、制度引用、附件状态和历史审批结论。' },
- { title: '生成策略', intent: '审批表达', content: '按通过、驳回、补件三类场景生成审批意见,并明确对应依据。' },
- { title: '输出格式', intent: '写回字段', content: '输出审批意见、判断依据、补件动作和可编辑的推荐话术。' }
- ],
- rules: ['必须引用规则命中或制度条款。', '驳回意见必须给出补充动作。', '不替代审批人最终决策。'],
- checks: [
- { name: '高风险驳回意见', input: '重复发票 + 缺附件', result: '通过', tone: 'success' },
- { name: '低风险通过意见', input: '规则全部通过', result: '通过', tone: 'success' }
- ],
- triggers: ['生成审批意见', '通过意见', '驳回意见', '补件说明'],
- tools: [
- { name: '规则命中结果', scope: '读取风险依据', mode: '只读', tone: 'safe' },
- { name: '制度知识库', scope: '条款引用', mode: '只读', tone: 'safe' },
- { name: '审批意见草稿', scope: '写入建议', mode: '写入', tone: 'active' }
- ],
- history: [
- { version: 'v1.8', note: '优化高风险驳回话术', time: '05-09 10:20' },
- { version: 'v1.7', note: '补充补件意见模板', time: '05-04 14:30' }
- ],
- titles: {
- configTitle: '技能配置',
- configDesc: '定义技能入口、适用范围、默认模型和上下文读取边界。',
- detailTitle: '技能流程',
- detailDesc: '按输入上下文、生成策略和输出格式组织技能行为。',
- outputTitle: '输出契约与测试样例',
- outputDesc: '确保技能在审批场景下输出稳定、可编辑且可追溯。',
- ruleListTitle: '输出要求',
- checkListTitle: '测试样例',
- triggerTitle: '触发入口',
- triggerDesc: '当前可调用场景',
- toolTitle: '依赖能力',
- toolDesc: '技能运行时调用',
- historyTitle: '版本历史',
- historyDesc: '最近变更',
- publishTitle: '发布控制',
- publishDesc: '当前技能已通过核心测试,可在审批中心使用。',
- },
- publish: { meta: '最近评审:2026-05-09 10:20', state: '可用' }
- }),
- buildAsset({
- type: 'skills',
- typeLabel: '技能',
- id: 'SKL-AI-002',
- short: 'EX',
- name: '风险解释技能',
- summary: '把规则命中的风险原因解释成员工可理解的补件或修正建议。',
- category: '风险解释技能',
- owner: '财务共享组',
- reviewer: '赵宁',
- scope: '员工自助',
- model: '风险提示入口调用',
- version: 'v1.4',
- status: '待评审',
- statusTone: 'warning',
- hitRate: '82.1%',
- updatedAt: '2026-05-08 15:10',
- badgeTone: 'amber',
- triggerMode: '风险拦截后调用',
- fields: [
- { label: '技能名称', value: '风险解释技能' },
- { label: '技能分类', value: '风险解释技能' },
- { label: '调用入口', value: '提交拦截 / 补件提示' },
- { label: '默认模型', value: 'GPT-5.4-Mini' }
- ],
- sections: [
- { title: '输入上下文', intent: '读取范围', content: '读取风险标签、规则证据、附件状态、制度条款和当前流程节点。' },
- { title: '解释策略', intent: '用户表达', content: '用原因、影响、处理建议三段式解释风险,不暴露内部评分细节。' },
- { title: '输出格式', intent: '员工提示', content: '输出可执行的补件动作、重传要求或人工复核说明。' }
- ],
- rules: ['不展示内部风控分值。', '建议必须可执行。', '附件缺失时必须列出材料名称。'],
- checks: [
- { name: '住宿超标解释', input: '酒店单晚超标 18%', result: '通过', tone: 'success' },
- { name: '附件缺失解释', input: '缺少发票附件', result: '评审中', tone: 'warning' }
- ],
- triggers: ['为什么被拦截', '补件说明', '风险原因', '重新提交建议'],
- tools: [
- { name: '风险标签读取', scope: '异常原因', mode: '只读', tone: 'safe' },
- { name: '规则文件引用', scope: '解释依据', mode: '只读', tone: 'safe' }
- ],
- history: [
- { version: 'v1.4', note: '新增补件导向模板', time: '05-08 15:10' },
- { version: 'v1.3', note: '优化员工侧语气', time: '05-03 11:40' }
- ],
- titles: {
- configTitle: '技能配置',
- configDesc: '定义技能入口、适用范围、默认模型和风险解释边界。',
- detailTitle: '技能流程',
- detailDesc: '按输入上下文、解释策略和输出格式组织技能行为。',
- outputTitle: '输出契约与测试样例',
- outputDesc: '确保风险解释清晰、可执行且不过度暴露内部规则。',
- ruleListTitle: '输出要求',
- checkListTitle: '测试样例',
- triggerTitle: '触发入口',
- triggerDesc: '当前可调用场景',
- toolTitle: '依赖能力',
- toolDesc: '技能运行时调用',
- historyTitle: '版本历史',
- historyDesc: '最近变更',
- publishTitle: '发布控制',
- publishDesc: '当前技能仍需补充附件缺失场景样例。',
- },
- publish: { meta: '最近评审:2026-05-08 15:10', state: '待评审' }
- }),
- buildAsset({
- type: 'mcp',
- typeLabel: 'MCP',
- id: 'MCP-001',
- short: 'IV',
- name: '发票验真 MCP',
- summary: '连接税务票据验真、OCR 识别和发票状态查询能力。',
- category: '票据服务',
- owner: '平台集成组',
- scope: '发票验真',
- model: 'API 调用',
- version: 'MCP 1.0',
- status: '健康',
- statusTone: 'success',
- hitRate: '99.3%',
- updatedAt: '2026-05-09 07:50',
- badgeTone: 'blue',
- triggerMode: '规则调用 / 批量任务调用',
- fields: [
- { label: '服务名称', value: '发票验真 MCP' },
- { label: '服务地址', value: 'mcp://invoice-verification' },
- { label: '鉴权方式', value: 'API Key + IP 白名单' },
- { label: '降级策略', value: '转 OCR 本地解析并标记待验真' }
- ],
- sections: [
- { title: '输入参数', intent: '服务调用', content: '附件文件、发票代码、发票号码、开票日期、金额和请求来源 trace_id。' },
- { title: '调用链路', intent: '外部服务', content: '先 OCR 识别票面字段,再调用验真接口查询真伪、作废、红冲状态。' },
- { title: '返回结构', intent: '标准输出', content: '返回票据字段、验真状态、异常原因、耗时和外部服务流水号。' }
- ],
- rules: ['超时时间 8 秒。', '只读外部系统,不写入第三方。', '失败时必须返回可降级状态。'],
- checks: [
- { name: '健康检查', input: '最近一次探活', result: '通过', tone: 'success' },
- { name: '批量验真', input: '436 张票据', result: '通过', tone: 'success' }
- ],
- triggers: ['发票 OCR', '验真查询', '作废红冲', '重复报销规则'],
- tools: [
- { name: '税务票据平台', scope: '验真查询', mode: '外部', tone: 'active' },
- { name: '密钥管理', scope: 'API Key 加密', mode: '安全', tone: 'safe' }
- ],
- history: [
- { version: '07:50', note: '健康检查通过,延迟 580ms', time: '今日' },
- { version: '02:00', note: '夜间任务批量核验 436 张票据', time: '今日' }
- ],
- titles: {
- configTitle: 'MCP 连接配置',
- configDesc: '配置外部服务地址、鉴权方式、权限范围和失败降级策略。',
- detailTitle: '服务协议',
- detailDesc: '按输入参数、调用链路和返回结构描述 MCP 能力。',
- outputTitle: '返回与检查',
- outputDesc: '服务输出会被审查规则、审批中心和任务消费。',
- ruleListTitle: '调用约束',
- checkListTitle: '健康检查',
- triggerTitle: '服务场景',
- triggerDesc: '当前被哪些能力调用',
- toolTitle: '外部依赖',
- toolDesc: 'MCP 背后连接',
- historyTitle: '调用记录',
- historyDesc: '最近运行',
- publishTitle: '连接状态',
- publishDesc: '服务健康,可供规则与任务调用。',
- },
- publish: { meta: '最近探活:2026-05-09 07:50', state: '可用' }
- }),
- buildAsset({
- type: 'schedules',
- typeLabel: '任务',
- id: 'JOB-001',
- short: 'RK',
- name: '每日风险巡检',
- summary: '每天定时检查报销、报账、发票和账款数据,输出待处理风险队列。',
- category: '风险巡检',
- owner: '财务风控组',
- scope: '全量待审与已付单据',
- model: '每天 02:00',
- version: '0 2 * * *',
- status: '已调度',
- statusTone: 'success',
- hitRate: '100%',
- updatedAt: '2026-05-09 02:18',
- badgeTone: 'emerald',
- triggerMode: 'Cron 定时调度',
- fields: [
- { label: '任务名称', value: '每日风险巡检' },
- { label: 'Cron', value: '0 2 * * *' },
- { label: '扫描窗口', value: 'T-1 00:00 至 23:59' },
- { label: '告警阈值', value: '高风险 >= 10 或单笔 >= 50,000' }
- ],
- sections: [
- { title: '数据抽取', intent: '扫描范围', content: '读取昨日新增报销、报账、发票、付款流水和审批反馈。' },
- { title: '规则执行', intent: '风险判断', content: '运行重复报销、超标、作废票、异常付款等技能,并调用必要 MCP。' },
- { title: '结果写入', intent: '任务产出', content: '生成风险日报、风险工单、审计日志,并通知负责人。' }
- ],
- rules: ['任务失败必须告警。', '每次运行记录规则版本和扫描窗口。', '高风险工单必须进入审批中心。'],
- checks: [
- { name: '今日运行', input: '扫描 2146 条记录', result: '成功', tone: 'success' },
- { name: '高风险推送', input: '19 条风险工单', result: '成功', tone: 'success' }
- ],
- triggers: ['每日风险检查', '发票验真', '账款核对', '风险日报'],
- tools: [
- { name: '报销审查技能', scope: '规则执行', mode: '调用', tone: 'active' },
- { name: '发票验真 MCP', scope: '票据核验', mode: '调用', tone: 'active' },
- { name: '账款流水 MCP', scope: '付款核对', mode: '调用', tone: 'active' }
- ],
- history: [
- { version: '02:18', note: '今日运行成功,生成 19 条高风险工单', time: '今日' },
- { version: '02:00', note: '任务开始,扫描 2026-05-08 数据', time: '今日' }
- ],
- titles: {
- configTitle: '任务配置',
- configDesc: '配置运行频率、扫描范围、依赖能力和告警出口。',
- detailTitle: '任务流程',
- detailDesc: '从数据抽取、规则执行到结果写入描述调度链路。',
- outputTitle: '产出与检查',
- outputDesc: '任务产出进入风险看板、审批中心和审计留痕。',
- ruleListTitle: '运行要求',
- checkListTitle: '最近检查',
- triggerTitle: '任务场景',
- triggerDesc: '当前覆盖的日常运营事项',
- toolTitle: '依赖能力',
- toolDesc: '任务运行时调用',
- historyTitle: '运行记录',
- historyDesc: '最近执行',
- publishTitle: '调度控制',
- publishDesc: '任务已纳入每日自动巡检。',
- },
- publish: { meta: '最近运行:2026-05-09 02:18', state: '已调度' }
- }),
- buildAsset({
- type: 'schedules',
- typeLabel: '任务',
- id: 'JOB-002',
- short: 'FN',
- name: '每日报销报账与账款统计',
- summary: '每天统计报销、报账、付款和账款信息,更新运营总览和财务日报。',
- category: '财务统计',
- owner: '财务运营组',
- scope: '报销、报账、账款',
- model: '每天 06:00',
- version: '0 6 * * *',
- status: '已调度',
- statusTone: 'success',
- hitRate: '99.1%',
- updatedAt: '2026-05-09 06:12',
- badgeTone: 'blue',
- triggerMode: 'Cron 定时调度',
- fields: [
- { label: '任务名称', value: '每日报销报账与账款统计' },
- { label: 'Cron', value: '0 6 * * *' },
- { label: '统计口径', value: '自然日 + 财务月' },
- { label: '刷新对象', value: '运营总览、财务日报、账款看板' }
- ],
- sections: [
- { title: '账款同步', intent: '外部数据', content: '拉取银行流水、付款结果和供应商收款状态。' },
- { title: '指标聚合', intent: '统计口径', content: '按部门、费用类型、报账状态、付款状态和账龄聚合。' },
- { title: '日报刷新', intent: '看板产出', content: '写入财务日报快照,并刷新运营总览。' }
- ],
- rules: ['统计口径必须记录。', '账款同步失败时保留上一版快照并告警。', '日报刷新后写入审计日志。'],
- checks: [
- { name: '日报刷新', input: '总览指标更新', result: '成功', tone: 'success' },
- { name: '账款匹配', input: '识别 8 条超期待付款', result: '成功', tone: 'success' }
- ],
- triggers: ['报销统计', '报账统计', '账款统计', '财务日报'],
- tools: [
- { name: '账款流水 MCP', scope: '付款与流水', mode: '调用', tone: 'active' },
- { name: '报销报账数据库', scope: '统计读取', mode: '只读', tone: 'safe' }
- ],
- history: [
- { version: '06:12', note: '日报刷新完成,总览看板已更新', time: '今日' },
- { version: '06:04', note: '账款匹配完成,识别 8 条超期待付款', time: '今日' }
- ],
- titles: {
- configTitle: '任务配置',
- configDesc: '配置统计口径、维度、账款数据源和看板刷新策略。',
- detailTitle: '任务流程',
- detailDesc: '从账款同步、指标聚合到日报刷新描述调度链路。',
- outputTitle: '产出与检查',
- outputDesc: '统计产出用于总览、日报和账款追踪。',
- ruleListTitle: '运行要求',
- checkListTitle: '最近检查',
- triggerTitle: '任务场景',
- triggerDesc: '当前覆盖的日常运营事项',
- toolTitle: '依赖能力',
- toolDesc: '任务运行时调用',
- historyTitle: '运行记录',
- historyDesc: '最近执行',
- publishTitle: '调度控制',
- publishDesc: '任务已纳入每日财务运营统计。',
- },
- publish: { meta: '最近运行:2026-05-09 06:12', state: '已调度' }
- })
-]
export default {
name: 'AuditView',
emits: ['detail-open-change'],
- setup(props, { emit }) {
- const tabs = [
- { id: 'rules', label: '规则' },
- { id: 'skills', label: '技能' },
- { id: 'mcp', label: 'MCP' },
- { id: 'schedules', label: '任务' }
- ]
+ setup(_, { emit }) {
+ const { toast } = useToast()
+ const { currentUser } = useSystemState()
+
+ const tabs = Object.entries(TYPE_META).map(([id, meta]) => ({
+ id,
+ label: meta.label
+ }))
+
const activeType = ref('rules')
const selectedSkill = ref(null)
const versionSwitchTarget = ref(null)
const keyword = ref('')
+ const activeFilterPopover = ref('')
+ const selectedDomain = ref('')
+ const selectedOwner = ref('')
+ const selectedStatus = ref('')
+ const loading = ref(false)
+ const errorMessage = ref('')
+ const detailLoading = ref(false)
+ const detailError = ref('')
+ const actionState = ref('')
+ const runLoading = ref(false)
+ const runs = ref([])
+ const assetBuckets = ref({
+ rules: [],
+ skills: [],
+ mcp: [],
+ tasks: []
+ })
+ const isAdmin = computed(() => isManagerUser(currentUser.value))
const activeMeta = computed(() => TYPE_META[activeType.value])
- const filters = computed(() => activeMeta.value.filters)
+ const activeTabLabel = computed(() => activeMeta.value.label)
+ const currentAssets = computed(() => assetBuckets.value[activeType.value] || [])
+ const searchPlaceholder = computed(() => activeMeta.value.searchPlaceholder)
const createButtonLabel = computed(() => activeMeta.value.createButtonLabel)
const hintText = computed(() => activeMeta.value.hintText)
const tableColumns = computed(() => activeMeta.value.tableColumns)
- const searchPlaceholder = computed(() => {
- const label = tabs.find((tab) => tab.id === activeType.value)?.label || '内容'
- return `搜索${label}`
+ const selectedSkillIsRule = computed(() => selectedSkill.value?.type === 'rules')
+ const canManageSelected = computed(() => isAdmin.value && Boolean(selectedSkill.value))
+ const canEditMarkdown = computed(() => canManageSelected.value && selectedSkillIsRule.value)
+ const detailBusy = computed(() => Boolean(actionState.value))
+ const showReviewNote = computed(
+ () => selectedSkillIsRule.value && (selectedSkill.value?.reviewNote || selectedSkill.value?.reviewTimeLabel)
+ )
+ const domainOptions = computed(() => {
+ const uniqueValues = [...new Set(currentAssets.value.map((item) => item.domainValue).filter(Boolean))]
+ return [
+ { value: '', label: '全部业务域' },
+ ...uniqueValues.map((value) => ({
+ value,
+ label: resolveDomainLabel(value)
+ }))
+ ]
+ })
+ const ownerOptions = computed(() => {
+ const uniqueOwners = [...new Set(currentAssets.value.map((item) => item.owner).filter(Boolean))]
+ return [
+ { value: '', label: '全部负责人' },
+ ...uniqueOwners.map((value) => ({
+ value,
+ label: value
+ }))
+ ]
+ })
+ const selectedDomainLabel = computed(
+ () => domainOptions.value.find((item) => item.value === selectedDomain.value)?.label || '业务域'
+ )
+ const selectedOwnerLabel = computed(
+ () => ownerOptions.value.find((item) => item.value === selectedOwner.value)?.label || '负责人'
+ )
+ const selectedStatusLabel = computed(
+ () => STATUS_OPTIONS.find((item) => item.value === selectedStatus.value)?.label || '状态'
+ )
+ const activeFilterTokens = computed(() => {
+ const tokens = []
+
+ if (selectedDomain.value) {
+ tokens.push(`业务域:${resolveDomainLabel(selectedDomain.value)}`)
+ }
+ if (selectedStatus.value) {
+ tokens.push(`状态:${resolveStatusMeta(selectedStatus.value).label}`)
+ }
+ if (selectedOwner.value) {
+ tokens.push(`负责人:${selectedOwner.value}`)
+ }
+ if (keyword.value.trim()) {
+ tokens.push(`搜索:${keyword.value.trim()}`)
+ }
+
+ return tokens
+ })
+ const canActivateSelected = computed(() => {
+ if (!selectedSkillIsRule.value || !canManageSelected.value || detailBusy.value) {
+ return false
+ }
+
+ return selectedSkill.value?.reviewStatusValue === 'approved' && selectedSkill.value?.statusValue !== 'active'
+ })
+ const activateBlockedReason = computed(() => {
+ if (!selectedSkillIsRule.value) {
+ return ''
+ }
+ if (!canManageSelected.value) {
+ return '仅管理员可执行审核和上线。'
+ }
+ if (selectedSkill.value?.statusValue === 'active') {
+ return '当前规则版本已经上线。'
+ }
+ if (selectedSkill.value?.reviewStatusValue !== 'approved') {
+ return '当前规则版本未审核通过,不能上线。'
+ }
+ return ''
})
const visibleSkills = computed(() => {
const normalizedKeyword = keyword.value.trim().toLowerCase()
- const scopedAssets = assets.filter((item) => item.type === activeType.value)
- if (!normalizedKeyword) {
- return scopedAssets
- }
+ return currentAssets.value.filter((item) => {
+ const matchesKeyword = normalizedKeyword
+ ? [item.name, item.code, item.summary, item.owner, item.scope]
+ .filter(Boolean)
+ .some((value) => String(value).toLowerCase().includes(normalizedKeyword))
+ : true
+ const matchesDomain = selectedDomain.value ? item.domainValue === selectedDomain.value : true
+ const matchesOwner = selectedOwner.value ? item.owner === selectedOwner.value : true
+ const matchesStatus = selectedStatus.value ? item.statusValue === selectedStatus.value : true
- return scopedAssets.filter((item) =>
- [item.name, item.summary, item.category, item.owner, item.scope]
- .filter(Boolean)
- .some((value) => String(value).toLowerCase().includes(normalizedKeyword))
- )
+ return matchesKeyword && matchesDomain && matchesOwner && matchesStatus
+ })
})
watch(
@@ -839,11 +987,150 @@ export default {
{ immediate: true }
)
- function openVersionSwitch(version) {
- if (!selectedSkill.value || version.version === selectedSkill.value.version) {
+ watch(activeType, () => {
+ selectedSkill.value = null
+ versionSwitchTarget.value = null
+ resetFilters()
+ loadAssets({ force: true }).catch((error) => {
+ errorMessage.value = error?.message || '资产数据加载失败,请稍后重试。'
+ })
+ })
+
+ function resetFilters() {
+ keyword.value = ''
+ selectedDomain.value = ''
+ selectedOwner.value = ''
+ selectedStatus.value = ''
+ activeFilterPopover.value = ''
+ }
+
+ function toggleFilterPopover(name) {
+ activeFilterPopover.value = activeFilterPopover.value === name ? '' : name
+ }
+
+ function closeFilterPopover() {
+ activeFilterPopover.value = ''
+ }
+
+ function selectFilter(name, value) {
+ if (name === 'domain') {
+ selectedDomain.value = value
+ }
+ if (name === 'owner') {
+ selectedOwner.value = value
+ }
+ if (name === 'status') {
+ selectedStatus.value = value
+ }
+ closeFilterPopover()
+ }
+
+ function handleDocumentClick(event) {
+ const target = event.target
+ if (!(target instanceof Element)) {
+ closeFilterPopover()
+ return
+ }
+ if (!target.closest('.picker-filter')) {
+ closeFilterPopover()
+ }
+ }
+
+ function resolveActor() {
+ return currentUser.value?.name || currentUser.value?.username || 'system'
+ }
+
+ async function loadRuns(options = {}) {
+ if (runLoading.value && !options.force) {
return
}
+ runLoading.value = true
+ try {
+ const payload = await fetchAgentRuns({ limit: 50 })
+ runs.value = Array.isArray(payload) ? payload : []
+ } finally {
+ runLoading.value = false
+ }
+ }
+
+ async function loadAssets(options = {}) {
+ loading.value = true
+ errorMessage.value = ''
+
+ try {
+ const payload = await fetchAgentAssets({ assetType: activeMeta.value.assetType })
+ assetBuckets.value = {
+ ...assetBuckets.value,
+ [activeType.value]: Array.isArray(payload) ? payload.map(buildListItem) : []
+ }
+ } catch (error) {
+ assetBuckets.value = {
+ ...assetBuckets.value,
+ [activeType.value]: []
+ }
+ errorMessage.value = error?.message || '资产数据加载失败,请稍后重试。'
+ if (!options.silent) {
+ toast(errorMessage.value)
+ }
+ } finally {
+ loading.value = false
+ }
+ }
+
+ async function refreshCurrentAssets() {
+ await loadAssets({ force: true, silent: true })
+ }
+
+ async function loadSelectedAssetDetail(assetId) {
+ detailLoading.value = true
+ detailError.value = ''
+
+ try {
+ if (!runs.value.length) {
+ await loadRuns()
+ }
+ const detail = await fetchAgentAssetDetail(assetId)
+ selectedSkill.value = buildDetailViewModel(detail, runs.value)
+ } catch (error) {
+ detailError.value = error?.message || '资产详情加载失败,请稍后重试。'
+ toast(detailError.value)
+ } finally {
+ detailLoading.value = false
+ }
+ }
+
+ function openAssetDetail(asset) {
+ selectedSkill.value = {
+ ...asset,
+ fields: [],
+ promptSections: [],
+ outputRules: [],
+ tests: [],
+ triggers: [],
+ tools: [],
+ history: [],
+ markdownContent: '',
+ displayVersion: asset.version,
+ loading: true,
+ reviewStatusLabel: '加载中',
+ reviewStatusTone: 'draft'
+ }
+ versionSwitchTarget.value = null
+ loadSelectedAssetDetail(asset.id).catch(() => {})
+ }
+
+ function closeDetail() {
+ selectedSkill.value = null
+ detailError.value = ''
+ detailLoading.value = false
+ versionSwitchTarget.value = null
+ }
+
+ function openVersionSwitch(version) {
+ if (!selectedSkill.value || version.version === selectedSkill.value.displayVersion) {
+ return
+ }
versionSwitchTarget.value = version
}
@@ -856,33 +1143,153 @@ export default {
return
}
- selectedSkill.value.version = versionSwitchTarget.value.version
- selectedSkill.value.updatedAt = versionSwitchTarget.value.time
-
- if (versionSwitchTarget.value.markdownContent) {
- selectedSkill.value.markdownContent = versionSwitchTarget.value.markdownContent
- } else {
- selectedSkill.value.markdownContent = `${selectedSkill.value.markdownContent}\n\n`
+ selectedSkill.value.displayVersion = versionSwitchTarget.value.version
+ if (typeof versionSwitchTarget.value.content === 'string') {
+ selectedSkill.value.markdownContent = versionSwitchTarget.value.content
}
-
versionSwitchTarget.value = null
}
+ async function saveRuleMarkdown() {
+ if (!selectedSkill.value || !selectedSkillIsRule.value || !canEditMarkdown.value || detailBusy.value) {
+ return
+ }
+
+ if (!normalizeText(selectedSkill.value.markdownContent)) {
+ toast('规则 Markdown 内容不能为空。')
+ return
+ }
+
+ const nextVersion = incrementVersion(selectedSkill.value.currentVersion)
+ actionState.value = 'save-markdown'
+
+ try {
+ await createAgentAssetVersion(
+ selectedSkill.value.id,
+ {
+ version: nextVersion,
+ content: selectedSkill.value.markdownContent,
+ content_type: 'markdown',
+ change_note: '通过任务规则中心保存 Markdown 规则内容。',
+ created_by: resolveActor()
+ },
+ { actor: resolveActor() }
+ )
+ await refreshCurrentAssets()
+ await loadSelectedAssetDetail(selectedSkill.value.id)
+ toast(`规则 Markdown 已保存为 ${nextVersion}。`)
+ } catch (error) {
+ toast(error?.message || '规则 Markdown 保存失败,请稍后重试。')
+ } finally {
+ actionState.value = ''
+ }
+ }
+
+ async function reviewSelectedRule(reviewStatus) {
+ if (!selectedSkill.value || !selectedSkillIsRule.value || !canManageSelected.value || detailBusy.value) {
+ return
+ }
+
+ actionState.value = `review-${reviewStatus}`
+
+ try {
+ await createAgentAssetReview(
+ selectedSkill.value.id,
+ {
+ version: selectedSkill.value.displayVersion || selectedSkill.value.currentVersion,
+ reviewer: resolveActor(),
+ review_status: reviewStatus,
+ review_note: buildReviewNote(reviewStatus)
+ },
+ { actor: resolveActor() }
+ )
+ await refreshCurrentAssets()
+ await loadSelectedAssetDetail(selectedSkill.value.id)
+ toast(`当前规则版本已标记为${resolveReviewMeta(reviewStatus).label}。`)
+ } catch (error) {
+ toast(error?.message || '规则审核提交失败,请稍后重试。')
+ } finally {
+ actionState.value = ''
+ }
+ }
+
+ async function activateSelectedRule() {
+ if (!selectedSkill.value || !selectedSkillIsRule.value || !canManageSelected.value || detailBusy.value) {
+ return
+ }
+
+ actionState.value = 'activate'
+
+ try {
+ await activateAgentAsset(selectedSkill.value.id, { actor: resolveActor() })
+ await refreshCurrentAssets()
+ await loadSelectedAssetDetail(selectedSkill.value.id)
+ toast('规则已正式上线。')
+ } catch (error) {
+ toast(error?.message || '规则上线失败,请稍后重试。')
+ } finally {
+ actionState.value = ''
+ }
+ }
+
+ onMounted(() => {
+ document.addEventListener('click', handleDocumentClick)
+ loadAssets({ force: true }).catch(() => {})
+ loadRuns().catch(() => {})
+ })
+
+ onBeforeUnmount(() => {
+ document.removeEventListener('click', handleDocumentClick)
+ })
+
return {
tabs,
- filters,
activeType,
- createButtonLabel,
- hintText,
- keyword,
- searchPlaceholder,
- tableColumns,
+ activeTabLabel,
selectedSkill,
versionSwitchTarget,
+ keyword,
+ createButtonLabel,
+ hintText,
+ searchPlaceholder,
+ tableColumns,
visibleSkills,
+ loading,
+ errorMessage,
+ detailLoading,
+ detailError,
+ selectedDomain,
+ selectedOwner,
+ selectedStatus,
+ selectedDomainLabel,
+ selectedOwnerLabel,
+ selectedStatusLabel,
+ domainOptions,
+ ownerOptions,
+ statusOptions: STATUS_OPTIONS,
+ activeFilterPopover,
+ activeFilterTokens,
+ canManageSelected,
+ canEditMarkdown,
+ canActivateSelected,
+ activateBlockedReason,
+ selectedSkillIsRule,
+ detailBusy,
+ actionState,
+ showReviewNote,
+ openAssetDetail,
+ closeDetail,
+ resetFilters,
+ toggleFilterPopover,
+ selectFilter,
+ closeFilterPopover,
openVersionSwitch,
cancelVersionSwitch,
- confirmVersionSwitch
+ confirmVersionSwitch,
+ saveRuleMarkdown,
+ reviewSelectedRule,
+ activateSelectedRule,
+ loadAssets
}
}
}