初始化
This commit is contained in:
317
easyflow-ui-admin/app/src/views/ai/workflow/WorkflowDesign.vue
Normal file
317
easyflow-ui-admin/app/src/views/ai/workflow/WorkflowDesign.vue
Normal 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>
|
||||
Reference in New Issue
Block a user