Commit 047b9f32 by 芋道源码 Committed by Gitee

!749 feat: AI工作流

Merge pull request !749 from Lesan/master-ai工作流
parents 3d4c8f24 ce3b95d1
import request from '@/config/axios'
export const getWorkflowPage = async (params) => {
return await request.get({ url: '/ai/workflow/page', params })
}
export const getWorkflow = async (id) => {
return await request.get({ url: '/ai/workflow/get?id=' + id })
}
export const createWorkflow = async (data) => {
return await request.post({ url: '/ai/workflow/create', data })
}
export const updateWorkflow = async (data) => {
return await request.put({ url: '/ai/workflow/update', data })
}
export const deleteWorkflow = async (id) => {
return await request.delete({ url: '/ai/workflow/delete?id=' + id })
}
export const updateWorkflowModel = async (data) => {
return await request.put({ url: '/ai/workflow/updateWorkflowModel', data })
}
<template>
<div ref="divRef" :class="['tinyflow', className]" :style="style" style="height: 100%"> </div>
</template>
<script setup lang="ts">
import { Item, Tinyflow as TinyflowNative } from './ui'
import './ui/index.css'
import { onMounted, onUnmounted, ref } from 'vue'
const props = defineProps<{
className?: string
style?: Record<string, string>
data?: Record<string, any>
provider?: {
llm?: () => Item[] | Promise<Item[]>
knowledge?: () => Item[] | Promise<Item[]>
internal?: () => Item[] | Promise<Item[]>
}
}>()
const divRef = ref<HTMLDivElement | null>(null)
let tinyflow: TinyflowNative | null = null
// 定义默认的 provider 方法
const defaultProvider = {
llm: () => [] as Item[],
knowledge: () => [] as Item[],
internal: () => [] as Item[]
}
onMounted(() => {
if (divRef.value) {
// 合并默认 provider 和传入的 props.provider
const mergedProvider = {
...defaultProvider,
...props.provider
}
tinyflow = new TinyflowNative({
element: divRef.value as Element,
data: props.data || {},
provider: mergedProvider
})
}
})
onUnmounted(() => {
if (tinyflow) {
tinyflow.destroy()
tinyflow = null
}
})
const getData = () => {
if (tinyflow) {
return tinyflow.getData()
}
console.warn('Tinyflow instance is not initialized')
return null
}
defineExpose({
getData
})
</script>
import { Edge } from '@xyflow/svelte';
import { Node as Node_2 } from '@xyflow/svelte';
import { useSvelteFlow } from '@xyflow/svelte';
import { Viewport } from '@xyflow/svelte';
export declare type Item = {
value: number | string;
label: string;
children?: Item[];
};
export declare class Tinyflow {
private options;
private rootEl;
private svelteFlowInstance;
constructor(options: TinyflowOptions);
private _init;
private _setOptions;
getOptions(): TinyflowOptions;
getData(): {
nodes: Node_2[];
edges: Edge[];
viewport: Viewport;
};
setData(data: TinyflowData): void;
destroy(): void;
}
export declare type TinyflowData = Partial<ReturnType<ReturnType<typeof useSvelteFlow>['toObject']>>;
export declare type TinyflowOptions = {
element: string | Element;
data?: TinyflowData;
provider?: {
llm?: () => Item[] | Promise<Item[]>;
knowledge?: () => Item[] | Promise<Item[]>;
internal?: () => Item[] | Promise<Item[]>;
};
};
export { }
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -667,6 +667,18 @@ const remainingRouter: AppRouteRecordRaw[] = [ ...@@ -667,6 +667,18 @@ const remainingRouter: AppRouteRecordRaw[] = [
hidden: true, hidden: true,
activeMenu: '/ai/knowledge' activeMenu: '/ai/knowledge'
} }
},
{
path: 'console/workflow/:type/:id',
component: () => import('@/views/ai/workflow/manager/WorkflowModelForm.vue'),
name: 'AiWorkflowUpdate',
meta: {
noCache: true,
hidden: true,
canTo: true,
title: '修改AI工作流',
activeMenu: '/ai/console/workflow'
}
} }
] ]
}, },
......
<template>
<Dialog v-model="dialogVisible" :title="dialogTitle">
<el-form
ref="formRef"
v-loading="formLoading"
:model="formData"
:rules="formRules"
label-width="80px"
>
<el-row>
<el-col :span="24">
<el-form-item label="流程标识" prop="definitionKey">
<el-input v-model="formData.definitionKey" placeholder="请输入流程标识" />
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="流程名称" prop="name">
<el-input v-model="formData.name" placeholder="请输入流程名称" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import * as WorkflowApi from '@/api/ai/workflow'
import { FormRules } from 'element-plus'
defineOptions({ name: 'AiWorkflowForm' })
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改
const formData = ref({
id: undefined,
definitionKey: '',
name: ''
})
const formRules = reactive<FormRules>({
definitionKey: [{ required: true, message: '流程标识不能为空', trigger: 'blur' }],
name: [{ required: true, message: '流程名称不能为空', trigger: 'blur' }]
})
const formRef = ref() // 表单 Ref
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
dialogTitle.value = t('action.' + type)
formType.value = type
resetForm()
// 修改时,设置数据
if (id) {
formLoading.value = true
try {
formData.value = await WorkflowApi.getWorkflow(id)
} finally {
formLoading.value = false
}
}
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
const submitForm = async () => {
// 校验表单
if (!formRef) return
const valid = await formRef.value.validate()
if (!valid) return
// 提交请求
formLoading.value = true
try {
const data = formData.value
if (formType.value === 'create') {
await WorkflowApi.createWorkflow(data)
message.success(t('common.createSuccess'))
} else {
await WorkflowApi.updateWorkflow(data)
message.success(t('common.updateSuccess'))
}
dialogVisible.value = false
// 发送操作成功的事件
emit('success')
} finally {
formLoading.value = false
}
}
/** 重置表单 */
const resetForm = () => {
formData.value = {
id: undefined,
definitionKey: '',
name: ''
}
formRef.value?.resetFields()
}
</script>
<template>
<div style="width: 100%; height: calc(100vh - 160px)">
<Tinyflow
ref="tinyflowRef"
:className="'custom-class'"
:style="{ width: '100%', height: '100%' }"
v-if="initialData"
:data="initialData"
:provider="provider"
/>
</div>
<div class="absolute top-30px right-30px">
<el-button @click="updateWorkflowModel" type="primary" v-hasPermi="['ai:workflow:update']">保存</el-button>
<el-button @click="testWorkflowModel" type="primary" v-hasPermi="['ai:workflow:test']">测试</el-button>
</div>
</template>
<script setup lang="ts">
import Tinyflow from '@/components/Tinyflow/Tinyflow.vue'
import * as WorkflowApi from '@/api/ai/workflow'
import { ApiKeyApi } from '@/api/ai/model/apiKey'
const route = useRoute()
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const tinyflowRef = ref()
const provider = ref({ llm: () => [], knowledge: () => [], internal: () => [] })
const initialData = ref()
const loadData = async () => {
try {
const [apiKeys, flowData] = await Promise.all([
ApiKeyApi.getApiKeySimpleList(),
WorkflowApi.getWorkflow(route.params.id)
])
// 更新 provider
provider.value = {
llm: () =>
apiKeys.map(({ id, name }) => ({
value: id,
label: name
})),
knowledge: () => [],
internal: () => []
}
// 更新流程图数据
initialData.value = JSON.parse(flowData.model)
} catch {}
}
const updateWorkflowModel = async () => {
try {
const model = tinyflowRef.value.getData()
const data = {
model: JSON.stringify(model),
id: route.params.id
}
await message.confirm('确认保存流程模型?')
await WorkflowApi.updateWorkflowModel(data)
message.success(t('common.updateSuccess'))
await loadData()
} catch {}
}
const testWorkflowModel = () => {
// TODO @lesan 测试
}
watchEffect(() => {
if (route.params.id) {
loadData()
}
})
</script>
<template>
<!-- 搜索工作栏 -->
<ContentWrap>
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="流程标识" prop="definitionKey">
<el-input
v-model="queryParams.definitionKey"
placeholder="请输入流程标识"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="流程名称" prop="name">
<el-input
v-model="queryParams.name"
placeholder="请输入流程名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-240px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['ai:workflow:create']">
<Icon icon="ep:plus" /> 新增
</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table-column label="编号" align="center" prop="id" :show-overflow-tooltip="true" />
<el-table-column
label="流程标识"
align="center"
prop="definitionKey"
:show-overflow-tooltip="true"
/>
<el-table-column label="流程名称" align="center" prop="name" :show-overflow-tooltip="true" />
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180"
/>
<el-table-column label="操作" align="center" width="220" fixed="right">
<template #default="scope">
<el-button
type="primary"
link
@click="openForm('update', scope.row.id)"
v-hasPermi="['ai:workflow:update']"
>
修改
</el-button>
<el-button
type="primary"
link
@click="openModelForm('update', scope.row.id)"
v-hasPermi="['ai:workflow:update']"
>
流程图
</el-button>
<el-button
link
type="danger"
@click="handleDelete(scope.row.id)"
v-hasPermi="['ai:workflow:delete']"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
<!-- 添加或修改工作流对话框 -->
<WorkflowForm ref="formRef" @success="getList" />
</template>
<script setup lang="ts">
import * as WorkflowApi from '@/api/ai/workflow'
import { dateFormatter } from '@/utils/formatTime'
import WorkflowForm from './WorkflowForm.vue'
/** AI 绘画 列表 */
defineOptions({ name: 'AiWorkflowManager' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const { push } = useRouter() // 路由
const loading = ref(true) // 列表的加载中
const list = ref([]) // 列表的数据
const total = ref(0) // 列表的总页数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
definitionKey: '',
name: '',
createTime: []
})
const queryFormRef = ref() // 搜索的表单
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await WorkflowApi.getWorkflowPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
try {
// 删除的二次确认
await message.delConfirm()
// 发起删除
await WorkflowApi.deleteWorkflow(id)
message.success(t('common.delSuccess'))
// 刷新列表
await getList()
} catch {}
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
formRef.value.open(type, id)
}
/** 修改流程模型弹窗 */
const openModelForm = async (type: string, id?: number) => {
if (type === 'create') {
await push({ name: 'AiWorkflowCreate' })
} else {
await push({
name: 'AiWorkflowUpdate',
params: { id, type }
})
}
}
/** 初始化 **/
onMounted(async () => {
getList()
})
</script>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment