feat(ai): 添加 AI 写作、知识库、思维导图和工作流功能
- 新增 AI 写作功能,包括示例点击、重置和停止流等功能 - 实现 AI 知识库管理,支持创建、编辑和删除知识库 - 添加 AI 思维导图功能,支持预览和管理思维导图 - 实现 AI 工作流管理,支持创建、编辑和删除工作流 - 优化 API 调用,使用 Vben 组件库和 Vue 3 相关特性
This commit is contained in:
80
apps/web-antd/src/views/ai/workflow/data.ts
Normal file
80
apps/web-antd/src/views/ai/workflow/data.ts
Normal file
@@ -0,0 +1,80 @@
|
||||
import type { VbenFormSchema } from '#/adapter/form';
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { DICT_TYPE, getDictOptions } from '#/utils';
|
||||
|
||||
/** 列表的搜索表单 */
|
||||
export function useGridFormSchema(): VbenFormSchema[] {
|
||||
return [
|
||||
{
|
||||
fieldName: 'code',
|
||||
label: '流程标识',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
fieldName: 'name',
|
||||
label: '流程名称',
|
||||
component: 'Input',
|
||||
},
|
||||
{
|
||||
fieldName: 'status',
|
||||
label: '状态',
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
allowClear: true,
|
||||
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
|
||||
},
|
||||
},
|
||||
{
|
||||
fieldName: 'createTime',
|
||||
label: '创建时间',
|
||||
component: 'RangePicker',
|
||||
componentProps: {
|
||||
placeholder: ['开始时间', '结束时间'],
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
allowClear: true,
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
/** 列表的字段 */
|
||||
export function useGridColumns(): VxeTableGridOptions['columns'] {
|
||||
return [
|
||||
{
|
||||
field: 'id',
|
||||
title: '编号',
|
||||
},
|
||||
{
|
||||
field: 'code',
|
||||
title: '流程标识',
|
||||
},
|
||||
{
|
||||
field: 'name',
|
||||
title: '流程名称',
|
||||
},
|
||||
{
|
||||
field: 'createTime',
|
||||
title: '创建时间',
|
||||
formatter: 'formatDateTime',
|
||||
},
|
||||
{
|
||||
field: 'remark',
|
||||
title: '备注',
|
||||
},
|
||||
{
|
||||
field: 'status',
|
||||
title: '状态',
|
||||
cellRender: {
|
||||
name: 'CellDict',
|
||||
props: { type: DICT_TYPE.COMMON_STATUS },
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 130,
|
||||
fixed: 'right',
|
||||
slots: { default: 'actions' },
|
||||
},
|
||||
];
|
||||
}
|
||||
289
apps/web-antd/src/views/ai/workflow/form/index.vue
Normal file
289
apps/web-antd/src/views/ai/workflow/form/index.vue
Normal file
@@ -0,0 +1,289 @@
|
||||
<script setup lang="ts">
|
||||
import { onBeforeUnmount, onMounted, provide, ref } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
|
||||
import { confirm, Page } from '@vben/common-ui';
|
||||
import { useTabs } from '@vben/hooks';
|
||||
import { ArrowLeft } from '@vben/icons';
|
||||
|
||||
import { Button, Card, message } from 'ant-design-vue';
|
||||
|
||||
import { getModelSimpleList } from '#/api/ai/model/model';
|
||||
import { createWorkflow, getWorkflow, updateWorkflow } from '#/api/ai/workflow';
|
||||
import { createModel, deployModel, updateModel } from '#/api/bpm/model';
|
||||
import { AiModelTypeEnum, CommonStatusEnum } from '#/utils';
|
||||
|
||||
import BasicInfo from './modules/basic-info.vue';
|
||||
import WorkflowDesign from './modules/workflow-design.vue';
|
||||
|
||||
defineOptions({ name: 'AiWorkflowCreate' });
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
// 基础信息组件引用
|
||||
const basicInfoRef = ref<InstanceType<typeof BasicInfo>>();
|
||||
// 工作流设计组件引用
|
||||
const workflowDesignRef = ref<InstanceType<typeof WorkflowDesign>>();
|
||||
|
||||
/** 步骤校验函数 */
|
||||
const validateBasic = async () => {
|
||||
await basicInfoRef.value?.validate();
|
||||
};
|
||||
|
||||
/** 工作流设计校验 */
|
||||
const validateWorkflow = async () => {
|
||||
await workflowDesignRef.value?.validate();
|
||||
};
|
||||
|
||||
const currentStep = ref(-1); // 步骤控制。-1 用于,一开始全部不展示等当前页面数据初始化完成
|
||||
|
||||
const steps = [
|
||||
{ title: '基本信息', validator: validateBasic },
|
||||
{ title: '工作流设计', validator: validateWorkflow },
|
||||
];
|
||||
|
||||
// 表单数据
|
||||
const formData: any = ref({
|
||||
id: undefined,
|
||||
name: '',
|
||||
code: '',
|
||||
remark: '',
|
||||
graph: '',
|
||||
status: CommonStatusEnum.ENABLE,
|
||||
});
|
||||
|
||||
const llmProvider = ref<any>([]);
|
||||
const workflowData = ref<any>({});
|
||||
provide('workflowData', workflowData);
|
||||
|
||||
/** 初始化数据 */
|
||||
const actionType = route.params.type as string;
|
||||
const initData = async () => {
|
||||
if (actionType === 'update') {
|
||||
const workflowId = route.params.id as string;
|
||||
formData.value = await getWorkflow(workflowId);
|
||||
workflowData.value = JSON.parse(formData.value.graph);
|
||||
}
|
||||
const models = await getModelSimpleList(AiModelTypeEnum.CHAT);
|
||||
llmProvider.value = {
|
||||
llm: () =>
|
||||
models.map(({ id, name }) => ({
|
||||
value: id,
|
||||
label: name,
|
||||
})),
|
||||
knowledge: () => [],
|
||||
internal: () => [],
|
||||
};
|
||||
|
||||
// 设置当前步骤
|
||||
currentStep.value = 0;
|
||||
};
|
||||
|
||||
/** 校验所有步骤数据是否完整 */
|
||||
const validateAllSteps = async () => {
|
||||
// 基本信息校验
|
||||
try {
|
||||
await validateBasic();
|
||||
} catch {
|
||||
currentStep.value = 0;
|
||||
throw new Error('请完善基本信息');
|
||||
}
|
||||
|
||||
// 表单设计校验
|
||||
try {
|
||||
await validateWorkflow();
|
||||
} catch {
|
||||
currentStep.value = 1;
|
||||
throw new Error('请完善工作流信息');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
/** 保存操作 */
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
// 保存前校验所有步骤的数据
|
||||
await validateAllSteps();
|
||||
|
||||
// 更新表单数据
|
||||
const data = {
|
||||
...formData.value,
|
||||
graph: JSON.stringify(workflowData.value),
|
||||
};
|
||||
await (actionType === 'update'
|
||||
? updateWorkflow(data)
|
||||
: createWorkflow(data));
|
||||
|
||||
// 保存成功,提示并跳转到列表页
|
||||
message.success('保存成功');
|
||||
tabs.closeCurrentTab();
|
||||
await router.push({ name: 'AiWorkflow' });
|
||||
} catch (error: any) {
|
||||
console.error('保存失败:', error);
|
||||
message.warning(error.message || '请完善所有步骤的必填信息');
|
||||
}
|
||||
};
|
||||
|
||||
/** 发布操作 */
|
||||
const handleDeploy = async () => {
|
||||
try {
|
||||
// 修改场景下直接发布,新增场景下需要先确认
|
||||
if (!formData.value.id) {
|
||||
await confirm('是否确认发布该流程?');
|
||||
}
|
||||
// 校验所有步骤
|
||||
await validateAllSteps();
|
||||
|
||||
// 更新表单数据
|
||||
const modelData = {
|
||||
...formData.value,
|
||||
};
|
||||
|
||||
// 先保存所有数据
|
||||
if (formData.value.id) {
|
||||
await updateModel(modelData);
|
||||
} else {
|
||||
const result = await createModel(modelData);
|
||||
formData.value.id = result.id;
|
||||
}
|
||||
|
||||
// 发布
|
||||
await deployModel(formData.value.id);
|
||||
message.success('发布成功');
|
||||
// TODO 返回列表页
|
||||
await router.push({ name: '/ai/workflow' });
|
||||
} catch (error: any) {
|
||||
console.error('发布失败:', error);
|
||||
message.warning(error.message || '发布失败');
|
||||
}
|
||||
};
|
||||
|
||||
/** 步骤切换处理 */
|
||||
const handleStepClick = async (index: number) => {
|
||||
try {
|
||||
if (index !== 0) {
|
||||
await validateBasic();
|
||||
}
|
||||
if (index !== 1) {
|
||||
await validateWorkflow();
|
||||
}
|
||||
|
||||
// 切换步骤
|
||||
currentStep.value = index;
|
||||
} catch (error) {
|
||||
console.error('步骤切换失败:', error);
|
||||
message.warning('请先完善当前步骤必填信息');
|
||||
}
|
||||
};
|
||||
|
||||
const tabs = useTabs();
|
||||
|
||||
/** 返回列表页 */
|
||||
const handleBack = () => {
|
||||
// 关闭当前页签
|
||||
tabs.closeCurrentTab();
|
||||
// 跳转到列表页,使用路径, 目前后端的路由 name: 'name'+ menuId
|
||||
router.push({ path: '/ai/workflow' });
|
||||
};
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
await initData();
|
||||
});
|
||||
|
||||
/** 添加组件卸载前的清理 */
|
||||
onBeforeUnmount(() => {
|
||||
// 清理所有的引用
|
||||
basicInfoRef.value = undefined;
|
||||
workflowDesignRef.value = undefined;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page auto-content-height>
|
||||
<div class="mx-auto">
|
||||
<!-- 头部导航栏 -->
|
||||
<div
|
||||
class="absolute inset-x-0 top-0 z-10 flex h-12 items-center border-b bg-white px-5"
|
||||
>
|
||||
<!-- 左侧标题 -->
|
||||
<div class="flex w-[200px] items-center overflow-hidden">
|
||||
<ArrowLeft
|
||||
class="size-5 flex-shrink-0 cursor-pointer"
|
||||
@click="handleBack"
|
||||
/>
|
||||
<span
|
||||
class="ml-2.5 truncate text-base"
|
||||
:title="formData.name || '创建AI 工作流'"
|
||||
>
|
||||
{{ formData.name || '创建AI 工作流' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 步骤条 -->
|
||||
<div class="flex h-full flex-1 items-center justify-center">
|
||||
<div class="flex h-full w-[400px] items-center justify-between">
|
||||
<div
|
||||
v-for="(step, index) in steps"
|
||||
:key="index"
|
||||
class="relative mx-[15px] flex h-full cursor-pointer items-center"
|
||||
:class="[
|
||||
currentStep === index
|
||||
? 'border-b-2 border-solid border-blue-500 text-blue-500'
|
||||
: 'text-gray-500',
|
||||
]"
|
||||
@click="handleStepClick(index)"
|
||||
>
|
||||
<div
|
||||
class="mr-2 flex h-7 w-7 items-center justify-center rounded-full border-2 border-solid text-[15px]"
|
||||
:class="[
|
||||
currentStep === index
|
||||
? 'border-blue-500 bg-blue-500 text-white'
|
||||
: 'border-gray-300 bg-white text-gray-500',
|
||||
]"
|
||||
>
|
||||
{{ index + 1 }}
|
||||
</div>
|
||||
<span class="whitespace-nowrap text-base font-bold">{{
|
||||
step.title
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧按钮 -->
|
||||
<div class="flex w-[200px] items-center justify-end gap-2">
|
||||
<Button
|
||||
v-if="actionType === 'update'"
|
||||
type="primary"
|
||||
@click="handleDeploy"
|
||||
>
|
||||
发 布
|
||||
</Button>
|
||||
<Button type="primary" @click="handleSave">
|
||||
<span v-if="actionType === 'definition'">恢 复</span>
|
||||
<span v-else>保 存</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 主体内容 -->
|
||||
<Card :body-style="{ padding: '10px' }" class="mb-4">
|
||||
<div class="mt-[50px]">
|
||||
<!-- 第一步:基本信息 -->
|
||||
<div v-if="currentStep === 0" class="mx-auto w-4/6">
|
||||
<BasicInfo v-model="formData" ref="basicInfoRef" />
|
||||
</div>
|
||||
<!-- 第二步:表单设计 -->
|
||||
<WorkflowDesign
|
||||
v-if="currentStep === 1"
|
||||
v-model="formData"
|
||||
:provider="llmProvider"
|
||||
ref="workflowDesignRef"
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
</Page>
|
||||
</template>
|
||||
@@ -0,0 +1,72 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Rule } from 'ant-design-vue/es/form';
|
||||
|
||||
import { ref } from 'vue';
|
||||
|
||||
import { Form, Input, Select } from 'ant-design-vue';
|
||||
|
||||
import { DICT_TYPE, getIntDictOptions } from '#/utils';
|
||||
|
||||
// 创建本地数据副本
|
||||
const modelData = defineModel<any>();
|
||||
// 表单引用
|
||||
const formRef = ref();
|
||||
const rules: Record<string, Rule[]> = {
|
||||
code: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
|
||||
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }],
|
||||
status: [{ required: true, message: '状态不能为空', trigger: 'change' }],
|
||||
};
|
||||
|
||||
/** 表单校验 */
|
||||
const validate = async () => {
|
||||
await formRef.value?.validate();
|
||||
};
|
||||
|
||||
defineExpose({ validate });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form
|
||||
ref="formRef"
|
||||
:model="modelData"
|
||||
:rules="rules"
|
||||
:label-col="{ span: 4 }"
|
||||
:wrapper-col="{ span: 20 }"
|
||||
class="mt-5"
|
||||
>
|
||||
<Form.Item label="流程标识" name="code" class="mb-5">
|
||||
<Input
|
||||
class="w-full"
|
||||
v-model:value="modelData.code"
|
||||
allow-clear
|
||||
placeholder="请输入流程标识"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="流程名称" name="name" class="mb-5">
|
||||
<Input
|
||||
v-model:value="modelData.name"
|
||||
allow-clear
|
||||
placeholder="请输入流程名称"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="状态" name="status" class="mb-5">
|
||||
<Select
|
||||
class="w-full"
|
||||
v-model:value="modelData.status"
|
||||
allow-clear
|
||||
placeholder="请选择状态"
|
||||
>
|
||||
<Select.Option
|
||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||
:key="dict.value"
|
||||
:value="dict.value"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label="流程描述" name="description" class="mb-5">
|
||||
<Input.TextArea v-model:value="modelData.description" allow-clear />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</template>
|
||||
@@ -0,0 +1,286 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import { inject, ref } from 'vue';
|
||||
|
||||
import { useVbenDrawer } from '@vben/common-ui';
|
||||
|
||||
import { Button, Input, Select } from 'ant-design-vue';
|
||||
|
||||
import { testWorkflow } from '#/api/ai/workflow';
|
||||
import Tinyflow from '#/components/Tinyflow/Tinyflow.vue';
|
||||
|
||||
defineProps<{
|
||||
provider: any;
|
||||
}>();
|
||||
|
||||
const tinyflowRef = ref();
|
||||
const workflowData = inject('workflowData') as Ref;
|
||||
const params4Test = ref<any[]>([]);
|
||||
const paramsOfStartNode = ref<any>({});
|
||||
const testResult = ref(null);
|
||||
const loading = ref(false);
|
||||
const error = ref(null);
|
||||
|
||||
const [Drawer, drawerApi] = useVbenDrawer({
|
||||
footer: false,
|
||||
closeOnClickModal: false,
|
||||
modal: false
|
||||
});
|
||||
/** 展示工作流测试抽屉 */
|
||||
const testWorkflowModel = () => {
|
||||
drawerApi.open();
|
||||
const startNode = getStartNode();
|
||||
|
||||
// 获取参数定义
|
||||
const parameters = startNode.data?.parameters || [];
|
||||
const paramDefinitions = {};
|
||||
|
||||
// 加入参数选项方便用户添加非必须参数
|
||||
parameters.forEach((param) => {
|
||||
paramDefinitions[param.name] = param;
|
||||
});
|
||||
|
||||
function mergeIfRequiredButNotSet(target) {
|
||||
const needPushList = [];
|
||||
for (const key in paramDefinitions) {
|
||||
const param = paramDefinitions[key];
|
||||
|
||||
if (param.required) {
|
||||
const item = target.find((item) => item.key === key);
|
||||
|
||||
if (!item) {
|
||||
needPushList.push({
|
||||
key: param.name,
|
||||
value: param.defaultValue || '',
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
target.push(...needPushList);
|
||||
}
|
||||
// 自动装载需必填的参数
|
||||
mergeIfRequiredButNotSet(params4Test.value);
|
||||
|
||||
paramsOfStartNode.value = paramDefinitions;
|
||||
};
|
||||
|
||||
/** 运行流程 */
|
||||
const goRun = async () => {
|
||||
try {
|
||||
const val = tinyflowRef.value.getData();
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
testResult.value = null;
|
||||
// / 查找start节点
|
||||
const startNode = getStartNode();
|
||||
|
||||
// 获取参数定义
|
||||
const parameters = startNode.data?.parameters || [];
|
||||
const paramDefinitions = {};
|
||||
parameters.forEach((param) => {
|
||||
paramDefinitions[param.name] = param.dataType;
|
||||
});
|
||||
|
||||
// 参数类型转换
|
||||
const convertedParams = {};
|
||||
for (const { key, value } of params4Test.value) {
|
||||
const paramKey = key.trim();
|
||||
if (!paramKey) continue;
|
||||
|
||||
let dataType = paramDefinitions[paramKey];
|
||||
if (!dataType) {
|
||||
dataType = 'String';
|
||||
}
|
||||
|
||||
try {
|
||||
convertedParams[paramKey] = convertParamValue(value, dataType);
|
||||
} catch (error_) {
|
||||
throw new Error(`参数 ${paramKey} 转换失败: ${error_.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
const data = {
|
||||
graph: JSON.stringify(val),
|
||||
params: convertedParams,
|
||||
};
|
||||
|
||||
const response = await testWorkflow(data);
|
||||
testResult.value = response;
|
||||
} catch (error_) {
|
||||
error.value =
|
||||
error_.response?.data?.message || '运行失败,请检查参数和网络连接';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
/** 获取开始节点 */
|
||||
const getStartNode = () => {
|
||||
const val = tinyflowRef.value.getData();
|
||||
const startNode = val.nodes.find((node) => node.type === 'startNode');
|
||||
if (!startNode) {
|
||||
throw new Error('流程缺少开始节点');
|
||||
}
|
||||
return startNode;
|
||||
};
|
||||
|
||||
/** 添加参数项 */
|
||||
const addParam = () => {
|
||||
params4Test.value.push({ key: '', value: '' });
|
||||
};
|
||||
|
||||
/** 删除参数项 */
|
||||
const removeParam = (index) => {
|
||||
params4Test.value.splice(index, 1);
|
||||
};
|
||||
|
||||
/** 类型转换函数 */
|
||||
const convertParamValue = (value, dataType) => {
|
||||
if (value === '') return null; // 空值处理
|
||||
|
||||
switch (dataType) {
|
||||
case 'Number': {
|
||||
const num = Number(value);
|
||||
if (isNaN(num)) throw new Error('非数字格式');
|
||||
return num;
|
||||
}
|
||||
case 'String': {
|
||||
return String(value);
|
||||
}
|
||||
case 'Boolean': {
|
||||
if (value.toLowerCase() === 'true') return true;
|
||||
if (value.toLowerCase() === 'false') return false;
|
||||
throw new Error('必须为 true/false');
|
||||
}
|
||||
case 'Array':
|
||||
case 'Object': {
|
||||
try {
|
||||
return JSON.parse(value);
|
||||
} catch (error_) {
|
||||
throw new Error(`JSON格式错误: ${error_.message}`);
|
||||
}
|
||||
}
|
||||
default: {
|
||||
throw new Error(`不支持的类型: ${dataType}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
/** 表单校验 */
|
||||
const validate = async () => {
|
||||
// 获取最新的流程数据
|
||||
if (!workflowData.value) {
|
||||
throw new Error('请设计流程');
|
||||
}
|
||||
workflowData.value = tinyflowRef.value.getData();
|
||||
return true;
|
||||
};
|
||||
|
||||
defineExpose({ validate });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative" style="width: 100%; height: 700px">
|
||||
<Tinyflow
|
||||
v-if="workflowData"
|
||||
ref="tinyflowRef"
|
||||
class-name="custom-class"
|
||||
:style="{ width: '100%', height: '100%' }"
|
||||
:data="workflowData"
|
||||
:provider="provider"
|
||||
/>
|
||||
<div class="absolute right-[30px] top-[30px]">
|
||||
<Button
|
||||
@click="testWorkflowModel"
|
||||
type="primary"
|
||||
v-hasPermi="['ai:workflow:test']"
|
||||
>
|
||||
测试
|
||||
</Button>
|
||||
</div>
|
||||
<Drawer title="工作流测试">
|
||||
<fieldset>
|
||||
<legend class="ml-2"><h3>运行参数配置</h3></legend>
|
||||
<div class="p-2">
|
||||
<div
|
||||
class="mb-1 flex justify-around"
|
||||
v-for="(param, index) in params4Test"
|
||||
:key="index"
|
||||
>
|
||||
<Select class="w-[200px]" v-model="param.key" placeholder="参数名">
|
||||
<Select.Option
|
||||
v-for="(value, key) in paramsOfStartNode"
|
||||
:key="key"
|
||||
:value="key"
|
||||
:disabled="!!value?.disabled"
|
||||
>
|
||||
{{ value?.description || key }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
<Input
|
||||
class="w-[200px]"
|
||||
v-model:value="param.value"
|
||||
placeholder="参数值"
|
||||
/>
|
||||
<Button danger plain circle @click="removeParam(index)">
|
||||
<template #icon>
|
||||
<span class="icon-[ant-design--delete-outlined]"></span>
|
||||
</template>
|
||||
</Button>
|
||||
</div>
|
||||
<Button type="primary" plain @click="addParam">添加参数</Button>
|
||||
</div>
|
||||
</fieldset>
|
||||
<fieldset class="mt-2" style="background-color: #f8f9fa">
|
||||
<legend class="ml-2"><h3>运行结果</h3></legend>
|
||||
<div class="p-2">
|
||||
<div v-if="loading"><el-text type="primary">执行中...</el-text></div>
|
||||
<div v-else-if="error">
|
||||
<el-text type="danger">{{ error }}</el-text>
|
||||
</div>
|
||||
<pre v-else-if="testResult" class="result-content">
|
||||
{{ JSON.stringify(testResult, null, 2) }}
|
||||
</pre>
|
||||
<div v-else style="color: #909399">点击运行查看结果</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
<Button
|
||||
class="mt-2"
|
||||
size="large"
|
||||
style="width: 100%; color: white; background-color: #67c23a"
|
||||
@click="goRun"
|
||||
>
|
||||
运行流程
|
||||
</Button>
|
||||
</Drawer>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="css" scoped>
|
||||
.result-content {
|
||||
max-height: 300px;
|
||||
padding: 12px;
|
||||
overflow: auto;
|
||||
font-family: Monaco, Consolas, monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
white-space: pre-wrap;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-inline-size: auto;
|
||||
padding: 12px 16px;
|
||||
margin: 0;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
legend {
|
||||
padding: 0 10px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
}
|
||||
</style>
|
||||
@@ -1,28 +1,128 @@
|
||||
<script lang="ts" setup>
|
||||
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
|
||||
|
||||
import { Page } from '@vben/common-ui';
|
||||
|
||||
import { Button } from 'ant-design-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
|
||||
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import { deleteWorkflow, getWorkflowPage } from '#/api/ai/workflow';
|
||||
import { $t } from '#/locales';
|
||||
import { router } from '#/router';
|
||||
|
||||
import { useGridColumns, useGridFormSchema } from './data';
|
||||
|
||||
/** 刷新表格 */
|
||||
function onRefresh() {
|
||||
gridApi.query();
|
||||
}
|
||||
|
||||
/** 创建 */
|
||||
function handleCreate() {
|
||||
router.push({
|
||||
name: 'AiWorkflowCreate',
|
||||
});
|
||||
}
|
||||
|
||||
/** 编辑 */
|
||||
function handleEdit(row: any) {
|
||||
router.push({
|
||||
name: 'AiWorkflowCreate',
|
||||
query: {
|
||||
id: row.id,
|
||||
type: 'update',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/** 删除 */
|
||||
async function handleDelete(row: any) {
|
||||
const hideLoading = message.loading({
|
||||
content: $t('ui.actionMessage.deleting', [row.name]),
|
||||
key: 'action_key_msg',
|
||||
});
|
||||
try {
|
||||
await deleteWorkflow(row.id as number);
|
||||
message.success({
|
||||
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
|
||||
key: 'action_key_msg',
|
||||
});
|
||||
onRefresh();
|
||||
} finally {
|
||||
hideLoading();
|
||||
}
|
||||
}
|
||||
|
||||
const [Grid, gridApi] = useVbenVxeGrid({
|
||||
formOptions: {
|
||||
schema: useGridFormSchema(),
|
||||
},
|
||||
gridOptions: {
|
||||
columns: useGridColumns(),
|
||||
height: 'auto',
|
||||
keepSource: true,
|
||||
proxyConfig: {
|
||||
ajax: {
|
||||
query: async ({ page }, formValues) => {
|
||||
return await getWorkflowPage({
|
||||
pageNo: page.currentPage,
|
||||
pageSize: page.pageSize,
|
||||
...formValues,
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
rowConfig: {
|
||||
keyField: 'id',
|
||||
},
|
||||
toolbarConfig: {
|
||||
refresh: { code: 'query' },
|
||||
search: true,
|
||||
},
|
||||
} as VxeTableGridOptions<any>,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Page>
|
||||
<Button
|
||||
danger
|
||||
type="link"
|
||||
target="_blank"
|
||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3"
|
||||
>
|
||||
该功能支持 Vue3 + element-plus 版本!
|
||||
</Button>
|
||||
<br />
|
||||
<Button
|
||||
type="link"
|
||||
target="_blank"
|
||||
href="https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/ai/workflow/index.vue"
|
||||
>
|
||||
可参考
|
||||
https://github.com/yudaocode/yudao-ui-admin-vue3/blob/master/src/views/ai/workflow/index.vue
|
||||
代码,pull request 贡献给我们!
|
||||
</Button>
|
||||
<Page auto-content-height>
|
||||
<Grid table-title="AI 工作流列表">
|
||||
<template #toolbar-tools>
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('ui.actionTitle.create', ['AI 工作流']),
|
||||
type: 'primary',
|
||||
icon: ACTION_ICON.ADD,
|
||||
auth: ['ai:workflow:create'],
|
||||
onClick: handleCreate,
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
<template #actions="{ row }">
|
||||
<TableAction
|
||||
:actions="[
|
||||
{
|
||||
label: $t('common.edit'),
|
||||
type: 'link',
|
||||
icon: ACTION_ICON.EDIT,
|
||||
auth: ['ai:workflow:update'],
|
||||
onClick: handleEdit.bind(null, row),
|
||||
},
|
||||
{
|
||||
label: $t('common.delete'),
|
||||
type: 'link',
|
||||
danger: true,
|
||||
icon: ACTION_ICON.DELETE,
|
||||
auth: ['ai:workflow:delete'],
|
||||
popConfirm: {
|
||||
title: $t('ui.actionMessage.deleteConfirm', [row.id]),
|
||||
confirm: handleDelete.bind(null, row),
|
||||
},
|
||||
},
|
||||
]"
|
||||
/>
|
||||
</template>
|
||||
</Grid>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user