support self-check

This commit is contained in:
锦恢 2025-07-03 13:48:58 +08:00
parent 9294275874
commit dcabd47a20
11 changed files with 104 additions and 89 deletions

View File

@ -1,15 +1,15 @@
<template> <template>
<el-dialog v-model="showDiagram" width="800px" append-to-body class="no-padding-dialog"> <el-dialog v-model="showDiagram" width="800px" append-to-body class="no-padding-dialog">
<template #title> <template #header>
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<span>Tool Diagram</span> <span>Tool Diagram</span>
&ensp; &ensp;
<el-button size="small" type="primary" @click="() => context.reset()">重置</el-button> <el-button size="small" type="primary" @click="() => context.reset()">{{ t("reset") }}</el-button>
<!-- 自检程序弹出表单 --> <!-- 自检程序弹出表单 -->
<el-popover placement="top" width="350" trigger="click" v-model:visible="testFormVisible"> <el-popover placement="top" width="350" trigger="click" v-model:visible="testFormVisible">
<template #reference> <template #reference>
<el-button size="small" type="primary"> <el-button size="small" type="primary">
开启自检程序 {{ t('start-auto-detect') }}
</el-button> </el-button>
</template> </template>
@ -20,9 +20,9 @@
<span style="opacity: 0.7;">enableXmlWrapper</span> <span style="opacity: 0.7;">enableXmlWrapper</span>
</div> </div>
<div style="text-align: right;"> <div style="text-align: right;">
<el-button size="small" @click="testFormVisible = false">取消</el-button> <el-button size="small" @click="testFormVisible = false">{{ t("cancel") }}</el-button>
<el-button size="small" type="primary" @click="onTestConfirm"> <el-button size="small" type="primary" @click="onTestConfirm">
确认 {{ t("confirm") }}
</el-button> </el-button>
</div> </div>
</el-popover> </el-popover>
@ -44,8 +44,10 @@ import { nextTick, provide, ref } from 'vue';
import Diagram from './diagram.vue'; import Diagram from './diagram.vue';
import { makeNodeTest, topoSortParallel, type DiagramContext, type DiagramState } from './diagram'; import { makeNodeTest, topoSortParallel, type DiagramContext, type DiagramState } from './diagram';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { useI18n } from 'vue-i18n';
const showDiagram = ref(true); const showDiagram = ref(true);
const { t } = useI18n();
const caption = ref(''); const caption = ref('');
const showCaption = ref(false); const showCaption = ref(false);

View File

@ -326,82 +326,86 @@ function renderSvg() {
.attr('fill', 'var(--main-color)') .attr('fill', 'var(--main-color)')
.attr('font-weight', 600) .attr('font-weight', 600)
.text(d => d.labels?.[0]?.text || 'Tool'); .text(d => d.labels?.[0]?.text || 'Tool');
nodeGroupEnter.append('g').attr('class', 'node-status');
// // enter+update
nodeGroupEnter.append('g') const nodeStatusGroup = nodeGroup.merge(nodeGroupEnter).select('.node-status');
.attr('class', 'node-status')
.each(function (d) { //
const status = state.dataView.get(d.id)?.status || 'waiting'; nodeStatusGroup.each(function (d) {
const g = d3.select(this); const g = d3.select(this);
if (status === 'running') { g.selectAll('*').remove(); //
// +
g.append('circle') const status = state.dataView.get(d.id)?.status || 'waiting';
.attr('cx', d.width / 2 - 32) if (status === 'running') {
.attr('cy', d.height - 16) g.append('circle')
.attr('r', 6) // .attr('cx', d.width / 2 - 32)
.attr('fill', 'none') .attr('cy', d.height - 16)
.attr('stroke', 'var(--main-color)') // 使 .attr('r', 6)
.attr('stroke-width', 3) .attr('fill', 'none')
.attr('stroke-dasharray', 20) .attr('stroke', 'var(--main-color)')
.attr('stroke-dashoffset', 0) .attr('stroke-width', 3)
.append('animateTransform') .attr('stroke-dasharray', 20)
.attr('attributeName', 'transform') .attr('stroke-dashoffset', 0)
.attr('attributeType', 'XML') .append('animateTransform')
.attr('type', 'rotate') .attr('attributeName', 'transform')
.attr('from', `0 ${(d.width / 2 - 32)} ${(d.height - 16)}`) .attr('attributeType', 'XML')
.attr('to', `360 ${(d.width / 2 - 32)} ${(d.height - 16)}`) .attr('type', 'rotate')
.attr('dur', '1s') .attr('from', `0 ${(d.width / 2 - 32)} ${(d.height - 16)}`)
.attr('repeatCount', 'indefinite'); .attr('to', `360 ${(d.width / 2 - 32)} ${(d.height - 16)}`)
g.append('text') .attr('dur', '1s')
.attr('x', d.width / 2 - 16) .attr('repeatCount', 'indefinite');
.attr('y', d.height - 12) g.append('text')
.attr('font-size', 13) .attr('x', d.width / 2 - 16)
.attr('fill', 'var(--main-color)') .attr('y', d.height - 12)
.text('running'); .attr('font-size', 13)
} else if (status === 'waiting') { .attr('fill', 'var(--main-color)')
g.append('circle') .text('running');
.attr('cx', d.width / 2 - 32) } else if (status === 'waiting') {
.attr('cy', d.height - 16) g.append('circle')
.attr('r', 6) .attr('cx', d.width / 2 - 32)
.attr('fill', 'none') .attr('cy', d.height - 16)
.attr('stroke', '#bdbdbd') .attr('r', 6)
.attr('stroke-width', 3); .attr('fill', 'none')
g.append('text') .attr('stroke', '#bdbdbd')
.attr('x', d.width / 2 - 16) .attr('stroke-width', 3);
.attr('y', d.height - 12) g.append('text')
.attr('font-size', 13) .attr('x', d.width / 2 - 16)
.attr('fill', '#bdbdbd') .attr('y', d.height - 12)
.text('waiting'); .attr('font-size', 13)
} else if (status === 'success') { .attr('fill', '#bdbdbd')
g.append('circle') .text('waiting');
.attr('cx', d.width / 2 - 32) } else if (status === 'success') {
.attr('cy', d.height - 16) g.append('circle')
.attr('r', 6) // waiting .attr('cx', d.width / 2 - 32)
.attr('fill', 'none') .attr('cy', d.height - 16)
.attr('stroke', '#4caf50') .attr('r', 6)
.attr('stroke-width', 3); .attr('fill', 'none')
g.append('text') .attr('stroke', '#4caf50')
.attr('x', d.width / 2 - 16) .attr('stroke-width', 3);
.attr('y', d.height - 12) g.append('text')
.attr('font-size', 13) .attr('x', d.width / 2 - 16)
.attr('fill', '#4caf50') .attr('y', d.height - 12)
.text('success'); .attr('font-size', 13)
} else if (status === 'error') { .attr('fill', '#4caf50')
g.append('circle') .text('success');
.attr('cx', d.width / 2 - 32) } else if (status === 'error') {
.attr('cy', d.height - 16) g.append('circle')
.attr('r', 6) // waiting .attr('cx', d.width / 2 - 32)
.attr('fill', 'none') .attr('cy', d.height - 16)
.attr('stroke', '#f44336') .attr('r', 6)
.attr('stroke-width', 3); .attr('fill', 'none')
g.append('text') .attr('stroke', '#f44336')
.attr('x', d.width / 2 - 16) .attr('stroke-width', 3);
.attr('y', d.height - 12) g.append('text')
.attr('font-size', 13) .attr('x', d.width / 2 - 16)
.attr('fill', '#f44336') .attr('y', d.height - 12)
.text('error'); .attr('font-size', 13)
} .attr('fill', '#f44336')
}); .text('error');
}
});
// enter // enter
nodeGroupEnter nodeGroupEnter
.transition() .transition()

View File

@ -184,5 +184,6 @@
"export-filename": "اسم ملف التصدير", "export-filename": "اسم ملف التصدير",
"how-to-use": "كيفية الاستخدام؟", "how-to-use": "كيفية الاستخدام؟",
"is-required": "هو حقل مطلوب", "is-required": "هو حقل مطلوب",
"edit-ai-mook-prompt": "تحرير إشارات AI Mook" "edit-ai-mook-prompt": "تحرير إشارات AI Mook",
"start-auto-detect": "بدء عملية الفحص الذاتي"
} }

View File

@ -184,5 +184,6 @@
"export-filename": "Exportdateiname", "export-filename": "Exportdateiname",
"how-to-use": "Wie benutzt man?", "how-to-use": "Wie benutzt man?",
"is-required": "ist ein Pflichtfeld", "is-required": "ist ein Pflichtfeld",
"edit-ai-mook-prompt": "AI Mook-Prompts bearbeiten" "edit-ai-mook-prompt": "AI Mook-Prompts bearbeiten",
"start-auto-detect": "Selbsttest starten"
} }

View File

@ -184,5 +184,6 @@
"export-filename": "Export filename", "export-filename": "Export filename",
"how-to-use": "How to use?", "how-to-use": "How to use?",
"is-required": "is a required field", "is-required": "is a required field",
"edit-ai-mook-prompt": "Edit AI Mook prompts" "edit-ai-mook-prompt": "Edit AI Mook prompts",
"start-auto-detect": "Start self-check"
} }

View File

@ -184,5 +184,6 @@
"export-filename": "Nom du fichier d'exportation", "export-filename": "Nom du fichier d'exportation",
"how-to-use": "Comment utiliser ?", "how-to-use": "Comment utiliser ?",
"is-required": "est un champ obligatoire", "is-required": "est un champ obligatoire",
"edit-ai-mook-prompt": "Modifier les invites AI Mook" "edit-ai-mook-prompt": "Modifier les invites AI Mook",
"start-auto-detect": "Démarrer l'autovérification"
} }

View File

@ -184,5 +184,6 @@
"export-filename": "エクスポートファイル名", "export-filename": "エクスポートファイル名",
"how-to-use": "使用方法", "how-to-use": "使用方法",
"is-required": "は必須フィールドです", "is-required": "は必須フィールドです",
"edit-ai-mook-prompt": "AI Mookプロンプトを編集" "edit-ai-mook-prompt": "AI Mookプロンプトを編集",
"start-auto-detect": "自己診断を開始"
} }

View File

@ -184,5 +184,6 @@
"export-filename": "내보내기 파일 이름", "export-filename": "내보내기 파일 이름",
"how-to-use": "사용 방법?", "how-to-use": "사용 방법?",
"is-required": "는 필수 필드입니다", "is-required": "는 필수 필드입니다",
"edit-ai-mook-prompt": "AI Mook 프롬프트 편집" "edit-ai-mook-prompt": "AI Mook 프롬프트 편집",
"start-auto-detect": "자체 점검 시작"
} }

View File

@ -184,5 +184,6 @@
"export-filename": "Имя файла экспорта", "export-filename": "Имя файла экспорта",
"how-to-use": "Как использовать?", "how-to-use": "Как использовать?",
"is-required": "является обязательным полем", "is-required": "является обязательным полем",
"edit-ai-mook-prompt": "Редактировать подсказки AI Mook" "edit-ai-mook-prompt": "Редактировать подсказки AI Mook",
"start-auto-detect": "Запустить самопроверку"
} }

View File

@ -184,5 +184,6 @@
"export-filename": "导出文件名", "export-filename": "导出文件名",
"how-to-use": "如何使用?", "how-to-use": "如何使用?",
"is-required": "是必填字段", "is-required": "是必填字段",
"edit-ai-mook-prompt": "编辑 AI Mook 提示词" "edit-ai-mook-prompt": "编辑 AI Mook 提示词",
"start-auto-detect": "开启自检程序"
} }

View File

@ -184,5 +184,6 @@
"export-filename": "導出文件名", "export-filename": "導出文件名",
"how-to-use": "如何使用?", "how-to-use": "如何使用?",
"is-required": "是必填欄位", "is-required": "是必填欄位",
"edit-ai-mook-prompt": "編輯AI Mook提示詞" "edit-ai-mook-prompt": "編輯AI Mook提示詞",
"start-auto-detect": "開啟自檢程序"
} }