初始化

This commit is contained in:
2026-02-22 18:56:10 +08:00
commit 26677972a6
3112 changed files with 255972 additions and 0 deletions

View File

@@ -0,0 +1,317 @@
<script setup lang="ts">
import { computed, onMounted, onUnmounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { getOptions, sortNodes } from '@easyflow/utils';
import { ArrowLeft, Position } from '@element-plus/icons-vue';
import { Tinyflow } from '@tinyflow-ai/vue';
import { ElButton, ElDrawer, ElMessage, ElSkeleton } from 'element-plus';
import { api } from '#/api/request';
import CommonSelectDataModal from '#/components/commonSelectModal/CommonSelectDataModal.vue';
import { $t } from '#/locales';
import { router } from '#/router';
import ExecResult from '#/views/ai/workflow/components/ExecResult.vue';
import SingleRun from '#/views/ai/workflow/components/SingleRun.vue';
import WorkflowForm from '#/views/ai/workflow/components/WorkflowForm.vue';
import WorkflowSteps from '#/views/ai/workflow/components/WorkflowSteps.vue';
import { getCustomNode } from './customNode/index';
import nodeNames from './customNode/nodeNames';
import '@tinyflow-ai/vue/dist/index.css';
const route = useRoute();
// vue
onMounted(async () => {
document.addEventListener('keydown', handleKeydown);
await Promise.all([
loadCustomNode(),
getLlmList(),
getKnowledgeList(),
getWorkflowInfo(workflowId.value),
]);
showTinyFlow.value = true;
});
onUnmounted(() => {
document.removeEventListener('keydown', handleKeydown);
});
// variables
const tinyflowRef = ref<InstanceType<typeof Tinyflow> | null>(null);
const workflowId = ref(route.query.id);
const workflowInfo = ref<any>({});
const runParams = ref<any>(null);
const tinyFlowData = ref<any>(null);
const llmList = ref<any>([]);
const knowledgeList = ref<any>([]);
const provider = computed(() => ({
llm: () => getOptions('title', 'id', llmList.value),
knowledge: () => getOptions('title', 'id', knowledgeList.value),
searchEngine: (): any => [
{
value: 'bocha-search',
label: $t('aiWorkflow.bochaSearch'),
},
],
}));
const customNode = ref();
const showTinyFlow = ref(false);
const saveLoading = ref(false);
const handleKeydown = (event: KeyboardEvent) => {
// 检查是否是 Ctrl+S
if ((event.ctrlKey || event.metaKey) && event.key === 's') {
event.preventDefault(); // 阻止浏览器默认保存行为
if (!saveLoading.value) {
handleSave(true);
}
}
};
const drawerVisible = ref(false);
const initState = ref(false);
const singleNode = ref<any>();
const singleRunVisible = ref(false);
const workflowForm = ref();
const workflowSelectRef = ref();
const updateWorkflowNode = ref<any>(null);
const pluginSelectRef = ref();
const updatePluginNode = ref<any>(null);
const pageLoading = ref(false);
const chainInfo = ref<any>(null);
// functions
async function loadCustomNode() {
customNode.value = await getCustomNode({
handleChosen: (nodeName: string, updateNodeData: any, value: string) => {
const v = [];
if (value) {
v.push(value);
}
if (nodeName === nodeNames.workflowNode) {
workflowSelectRef.value.openDialog(v);
updateWorkflowNode.value = updateNodeData;
}
if (nodeName === nodeNames.pluginNode) {
pluginSelectRef.value.openDialog(v);
updatePluginNode.value = updateNodeData;
}
},
});
}
async function runWorkflow() {
if (!saveLoading.value) {
await handleSave().then(() => {
getWorkflowInfo(workflowId.value);
getRunningParams();
});
}
}
async function handleSave(showMsg: boolean = false) {
saveLoading.value = true;
await api
.post('/api/v1/workflow/update', {
id: workflowId.value,
content: tinyflowRef.value?.getData(),
})
.then((res) => {
saveLoading.value = false;
if (res.errorCode === 0 && showMsg) {
ElMessage.success(res.message);
}
});
}
async function getWorkflowInfo(workflowId: any) {
api.get(`/api/v1/workflow/detail?id=${workflowId}`).then((res) => {
workflowInfo.value = res.data;
tinyFlowData.value = workflowInfo.value.content
? JSON.parse(workflowInfo.value.content)
: {};
});
}
async function getLlmList() {
api.get('/api/v1/model/list').then((res) => {
llmList.value = res.data;
});
}
async function getKnowledgeList() {
api.get('/api/v1/documentCollection/list').then((res) => {
knowledgeList.value = res.data;
});
}
function getRunningParams() {
api
.get(`/api/v1/workflow/getRunningParameters?id=${workflowId.value}`)
.then((res) => {
if (res.errorCode === 0) {
runParams.value = res.data;
drawerVisible.value = true;
}
});
}
function onSubmit() {
initState.value = !initState.value;
}
async function runIndependently(node: any) {
if (node.type === 'loopNode') {
ElMessage.warning($t('message.notSupported'));
return;
}
await handleSave();
singleNode.value = node;
singleRunVisible.value = true;
}
function resumeChain(data: any) {
workflowForm.value?.resume(data);
}
function handleChoose(nodeName: string, value: any) {
if (nodeName === nodeNames.workflowNode) {
handleWorkflowNodeUpdate(value[0]);
}
if (nodeName === nodeNames.pluginNode) {
handlePluginNodeUpdate(value[0]);
}
}
function handleWorkflowNodeUpdate(chooseId: any) {
pageLoading.value = true;
api
.get('/api/v1/workflowNode/getChainParams', {
params: {
currentId: workflowId.value,
workflowId: chooseId,
},
})
.then((res) => {
pageLoading.value = false;
updateWorkflowNode.value(res.data);
});
}
function handlePluginNodeUpdate(chooseId: any) {
pageLoading.value = true;
api
.get('/api/v1/pluginItem/getTinyFlowData', {
params: {
id: chooseId,
},
})
.then((res) => {
pageLoading.value = false;
updatePluginNode.value(res.data);
});
}
function onAsyncExecute(info: any) {
chainInfo.value = info;
}
</script>
<template>
<div class="head-div h-full w-full" v-loading="pageLoading">
<CommonSelectDataModal
ref="workflowSelectRef"
page-url="/api/v1/workflow/page"
@get-data="(v) => handleChoose(nodeNames.workflowNode, v)"
/>
<CommonSelectDataModal
:title="$t('menus.ai.plugin')"
width="730"
ref="pluginSelectRef"
page-url="/api/v1/plugin/page"
:has-parent="true"
single-select
@get-data="(v) => handleChoose(nodeNames.pluginNode, v)"
/>
<ElDrawer
v-model="singleRunVisible"
:title="singleNode?.data?.title"
destroy-on-close
size="600px"
>
<SingleRun :node="singleNode" :workflow-id="workflowId" />
</ElDrawer>
<ElDrawer v-model="drawerVisible" :title="$t('button.run')" size="600px">
<div class="mb-2.5 font-semibold">{{ $t('aiWorkflow.params') }}</div>
<WorkflowForm
ref="workflowForm"
:workflow-id="workflowId"
:workflow-params="runParams"
:on-submit="onSubmit"
:on-async-execute="onAsyncExecute"
:tiny-flow-data="tinyFlowData"
/>
<div class="mb-2.5 font-semibold">{{ $t('aiWorkflow.steps') }}</div>
<WorkflowSteps
:workflow-id="workflowId"
:node-json="sortNodes(tinyFlowData)"
:init-signal="initState"
:polling-data="chainInfo"
@resume="resumeChain"
/>
<div class="mb-2.5 mt-2.5 font-semibold">
{{ $t('aiWorkflow.result') }}
</div>
<ExecResult
:workflow-id="workflowId"
:node-json="sortNodes(tinyFlowData)"
:init-signal="initState"
:polling-data="chainInfo"
/>
</ElDrawer>
<div class="flex items-center justify-between border-b p-2.5">
<div>
<ElButton :icon="ArrowLeft" link @click="router.back()">
<span
class="max-w-[500px] overflow-hidden text-ellipsis text-nowrap text-base"
style="font-size: 14px"
:title="workflowInfo.title"
>
{{ workflowInfo.title }}
</span>
</ElButton>
</div>
<div>
<ElButton :disabled="saveLoading" :icon="Position" @click="runWorkflow">
{{ $t('button.runTest') }}
</ElButton>
<ElButton
type="primary"
:disabled="saveLoading"
@click="handleSave(true)"
>
{{ $t('button.save') }}(ctrl+s)
</ElButton>
</div>
</div>
<Tinyflow
ref="tinyflowRef"
v-if="showTinyFlow"
class="tiny-flow-container"
:data="JSON.parse(JSON.stringify(tinyFlowData))"
:provider="provider"
:custom-nodes="customNode"
:on-node-execute="runIndependently"
/>
<ElSkeleton class="load-div" v-else :rows="5" animated />
</div>
</template>
<style scoped>
:deep(.tf-toolbar-container-body) {
height: calc(100vh - 365px) !important;
overflow-y: auto;
}
:deep(.agentsflow) {
height: calc(100vh - 130px) !important;
}
.head-div {
background-color: var(--el-bg-color);
}
.tiny-flow-container {
width: 100%;
height: calc(100vh - 150px);
}
.load-div {
margin: 20px;
}
</style>