Files
EasyFlow/easyflow-ui-admin/app/src/views/ai/workflow/components/SingleRun.vue
陈子默 8cfe5400fe feat: 优化工作流字段化参数配置
- 开始节点固定 user_input 并区分系统入口与自定义参数

- LLM 与知识库节点切换为字段值加上游引用配置

- 单节点调试改为字段预览与上游引用输入模式
2026-04-12 20:31:02 +08:00

252 lines
6.9 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import type { FormInstance } from 'element-plus';
import { computed, ref } from 'vue';
import { Position } from '@element-plus/icons-vue';
import { ElAlert, ElButton, ElForm, ElFormItem, ElMessage } from 'element-plus';
import { api } from '#/api/request';
import ShowJson from '#/components/json/ShowJson.vue';
import { $t } from '#/locales';
import WorkflowFormItem from '#/views/ai/workflow/components/WorkflowFormItem.vue';
import { buildSingleRunModel } from '../../../../../../packages/tinyflow-ui/src/utils/workflowNodeFields';
interface Props {
workflowId: any;
node: any;
}
const props = defineProps<Props>();
const singleRunForm = ref<FormInstance>();
const runParams = ref<any>({});
const submitLoading = ref(false);
const result = ref<any>('');
const singleRunModel = computed(() => buildSingleRunModel(props.node));
const isFieldMode = computed(() => singleRunModel.value.mode === 'fields');
const singleRunParameters = computed(() => singleRunModel.value.parameters);
const singleRunFields = computed(() => singleRunModel.value.fields);
const parameterDisplayNameMap = computed(() => {
return new Map(
singleRunParameters.value.map((parameter: any) => [
String(parameter.name || ''),
String(parameter.displayName || parameter.formLabel || parameter.name || ''),
]),
);
});
function buildFieldSegments(value: string) {
const source = String(value || '');
const segments: Array<{ text: string; token: boolean }> = [];
const regex = /\{\{\s*([^{}]+?)\s*}}/g;
let lastIndex = 0;
for (const match of source.matchAll(regex)) {
const matchedText = match[0] || '';
const tokenKey = String(match[1] || '').trim();
const start = match.index ?? 0;
if (start > lastIndex) {
segments.push({
text: source.slice(lastIndex, start),
token: false,
});
}
segments.push({
text: parameterDisplayNameMap.value.get(tokenKey) || tokenKey,
token: true,
});
lastIndex = start + matchedText.length;
}
if (lastIndex < source.length) {
segments.push({
text: source.slice(lastIndex),
token: false,
});
}
return segments.length > 0 ? segments : [{ text: '', token: false }];
}
function submit() {
singleRunForm.value?.validate((valid) => {
if (valid) {
const params = {
workflowId: props.workflowId,
nodeId: props.node.id,
variables: runParams.value,
};
submitLoading.value = true;
api.post('/api/v1/workflow/singleRun', params).then((res) => {
submitLoading.value = false;
result.value = res.data;
if (res.errorCode === 0) {
ElMessage.success(res.message);
} else {
ElMessage.error(res.message);
}
});
}
});
}
</script>
<template>
<div>
<ElForm label-position="top" ref="singleRunForm" :model="runParams">
<template v-if="isFieldMode">
<div class="single-run-section">
<div class="single-run-section__title">字段值</div>
<ElAlert
type="info"
:closable="false"
show-icon
title="当前节点字段按正式配置展示;下方只需填写这些字段引用到的上游值。"
/>
<div class="single-run-field-list">
<div
v-for="field in singleRunFields"
:key="field.key"
class="single-run-field-card"
>
<div class="single-run-field-card__label">
{{ field.label }}
</div>
<div
class="single-run-field-card__value"
:class="{ 'is-multiline': field.multiline }"
>
<template v-if="field.value">
<template
v-for="(segment, index) in buildFieldSegments(field.value)"
:key="`${field.key}-${index}`"
>
<span
v-if="segment.token"
class="single-run-token-chip"
>
{{ segment.text }}
</span>
<span
v-else
class="single-run-field-card__text"
>
{{ segment.text }}
</span>
</template>
</template>
<span v-else class="single-run-field-card__placeholder">
{{ field.placeholder || '未设置' }}
</span>
</div>
</div>
</div>
</div>
<div class="single-run-section">
<div class="single-run-section__title">上游引用</div>
<ElAlert
v-if="singleRunParameters.length === 0"
type="success"
:closable="false"
show-icon
title="当前字段没有引用上游参数,可直接运行。"
/>
<WorkflowFormItem
v-else
v-model:run-params="runParams"
:parameters="singleRunParameters"
/>
</div>
</template>
<WorkflowFormItem
v-else
v-model:run-params="runParams"
:parameters="singleRunParameters"
/>
<ElFormItem>
<ElButton
type="primary"
@click="submit"
:loading="submitLoading"
:icon="Position"
>
{{ $t('button.run') }}
</ElButton>
</ElFormItem>
</ElForm>
<div class="mb-2.5 mt-2.5 font-semibold">{{ $t('workflow.result') }}</div>
<ShowJson :value="result" />
</div>
</template>
<style scoped>
.single-run-section + .single-run-section {
margin-top: 20px;
}
.single-run-section__title {
margin-bottom: 10px;
font-size: 13px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.single-run-field-list {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 12px;
}
.single-run-field-card {
border: 1px solid var(--el-border-color-light);
border-radius: 10px;
padding: 12px;
background: var(--el-fill-color-blank);
}
.single-run-field-card__label {
margin-bottom: 8px;
font-size: 12px;
color: var(--el-text-color-secondary);
}
.single-run-field-card__value {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 6px;
min-height: 20px;
line-height: 1.7;
color: var(--el-text-color-primary);
white-space: nowrap;
}
.single-run-field-card__value.is-multiline {
white-space: pre-wrap;
}
.single-run-field-card__text {
white-space: inherit;
}
.single-run-field-card__placeholder {
color: var(--el-text-color-placeholder);
}
.single-run-token-chip {
display: inline-flex;
align-items: center;
max-width: 100%;
padding: 1px 8px;
border-radius: 999px;
border: 1px solid var(--el-color-primary-light-5);
background: var(--el-color-primary-light-9);
color: var(--el-color-primary);
font-size: 12px;
line-height: 20px;
}
</style>