Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ui/src/api/enums/tool.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/** TODO 后端 Workspace 工具作用域枚举值。 */
/** 后端 Workspace 工具作用域枚举值。 */
export const TOOL_SCOPE = { INTERNAL: 'INTERNAL', SHARED: 'SHARED', WORKSPACE: 'WORKSPACE' } as const

/** 后端 Workspace 工具类型枚举值。 */
Expand Down
6 changes: 6 additions & 0 deletions ui/src/workflow-canvas/WORKFLOW_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ Vue Teleport 节点继承页面上下文;画布核心不负责模型参数接
保留失效引用检查;节点级 `validate()` 统一调用表单校验。所有非登录表单仅使用 `@submit.prevent`,
保存或添加由按钮触发。

基本信息节点的用户输入、接口传参和会话变量分别由 `UserInputTable`、`ApiParameterTable`、
`ConversationVariableTable` 通过 `v-model` 编辑,使用小尺寸 `MkTable` 支持排序。字段弹窗只负责
收集和校验单个字段,通过 `submit(data, index?)` 提交;表格完成重名检查和写回后调用 `close()`。
节点入口深拷贝写回列表并发送原有字段刷新事件,保留显隐条件引用校验。用户输入与接口传参继续
交叉检查参数重名;`UserInputSettingDialog` 独立维护直接展示参数设置,删除字段时由表格清理对应设置。

表单收集节点的 `component/form-setting/FormSettingTable` 通过 `v-model` 编辑动态表单的
`FormField[]`,负责增删改、排序和重名检查;`FormFieldDialog` 复用 `MkDynamicsFormConstructor`,
通过 `submit(data, index?)` 提交,由表格写回后调用 `close()`,关闭动画结束时重置编辑状态。
Expand Down
2 changes: 1 addition & 1 deletion ui/src/workflow-canvas/core/NodeCascader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ function validate() {
const fieldGroup = getOptionsValue().find(({ value }) => value === nodeId)
if (!fieldGroup?.children?.some(({ value }) => value === fieldValue)) {
selectedValue.value = []
return Promise.reject('不存在的引用变量')
return Promise.reject('引用变量不存在')
}
return Promise.resolve()
}
Expand Down
2 changes: 1 addition & 1 deletion ui/src/workflow-canvas/icons/reply-node-icon.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<template>
<el-avatar shape="square" class="bg-warning!">
<img src="@/assets/workflow/icon_reply.svg" style="width: 65%" alt="" />
<img src="@/assets/workflow/icon_reply.svg" style="width: 64%" alt="" />
</el-avatar>
</template>
<script setup lang="ts"></script>
1 change: 1 addition & 0 deletions ui/src/workflow-canvas/nodes/ai-chat-node/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ function validateModel(_rule: unknown, _value: unknown, callback: (error?: Error
}

function validate() {
// TODO 图片选择需要必填?为什么没有必填标记?
return Promise.all([formData.value.vision ? imageCascaderRef.value?.validate() : Promise.resolve(), formRef.value?.validate()]).catch((error) =>
Promise.reject({ node: model, errMessage: error }),
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue'
import { cloneDeep } from 'lodash'
import type { FormInstance } from 'element-plus'
import type { ApiInputField } from '../../types'

const emit = defineEmits<{ submit: [data: ApiInputField, index?: number] }>()
const dialogVisible = ref(false)
const editingIndex = ref<number>()
const formRef = useTemplateRef<FormInstance>('formRef')
const currentField = ref<ApiInputField>(createField())

function createField(): ApiInputField {
return { assignment_method: 'api_input', default_value: '', desc: '', is_required: true, type: 'input', variable: '' }
}

function open(field?: ApiInputField, index?: number) {
if (field) currentField.value = cloneDeep(field)
editingIndex.value = index
dialogVisible.value = true
}

function submitField() {
formRef.value
?.validate()
.then(() => emit('submit', cloneDeep(currentField.value), editingIndex.value))
.catch(() => {})
}

function resetData() {
editingIndex.value = undefined
currentField.value = createField()
formRef.value?.clearValidate()
}

function close() {
dialogVisible.value = false
}

defineExpose({ open, close })
</script>

<template>
<MkDialog v-model="dialogVisible" :title="editingIndex === undefined ? '添加参数' : '编辑参数'" align-center @closed="resetData">
<el-form ref="formRef" :model="currentField" label-position="top" require-asterisk-position="right" @submit.prevent>
<el-form-item
label="参数"
prop="variable"
:rules="[
{ required: true, message: '请输入参数', trigger: 'blur' },
{ pattern: /^[a-zA-Z0-9_]+$/, message: '仅支持字母、数字和下划线', trigger: 'blur' },
]"
>
<el-input
v-model="currentField.variable"
maxlength="64"
show-word-limit
@blur="currentField.variable = currentField.variable.trim()"
placeholder="请输入参数"
/>
</el-form-item>
<el-form-item label="描述"><el-input v-model="currentField.desc" maxlength="64" show-word-limit placeholder="请输入描述" /></el-form-item>
<el-form-item label="是否必填"><el-switch v-model="currentField.is_required" size="small" /></el-form-item>
<el-form-item label="默认值" prop="default_value" :rules="{ required: currentField.is_required, message: '请输入默认值', trigger: 'blur' }">
<el-input v-model="currentField.default_value" placeholder="请输入默认值" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitField">{{ editingIndex === undefined ? '添加' : '保存' }}</el-button>
</template>
</MkDialog>
</template>
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<script setup lang="ts">
import { useTemplateRef } from 'vue'
import { cloneDeep } from 'lodash'
import type { FormField } from '@/components/mk-dynamics-form'
import { MsgError } from '@/utils/message'
import type { ApiInputField } from '../../types'
import ApiParameterDialog from './ApiParameterDialog.vue'

const props = defineProps<{ userFields: FormField[] }>()
const inputFields = defineModel<ApiInputField[]>({ required: true })
const fieldDialogRef = useTemplateRef<InstanceType<typeof ApiParameterDialog>>('fieldDialogRef')

// 表格负责增删和重名检查,校验通过后关闭字段弹窗。
function openDialog(field?: ApiInputField, index?: number) {
fieldDialogRef.value?.open(field, index)
}

function saveField(data: ApiInputField, index?: number) {
if (
inputFields.value.some((field, fieldIndex) => field.variable === data.variable && fieldIndex !== index) ||
props.userFields.some(({ field }) => field === data.variable)
) {
MsgError(`参数已存在:${data.variable}`)
return
}
const fields = cloneDeep(inputFields.value)
if (index === undefined) fields.push(cloneDeep(data))
else fields.splice(index, 1, cloneDeep(data))
inputFields.value = fields
fieldDialogRef.value?.close()
}

function deleteField(index: number) {
inputFields.value = cloneDeep(inputFields.value.filter((_, fieldIndex) => fieldIndex !== index))
}
</script>

<template>
<div class="w-full">
<div class="flex-between">
<span>接口传参</span>
<el-button text type="primary" @click="openDialog()"><MkIcon name="icon_add_outlined" /></el-button>
</div>
<MkTable
class="mt-2 border"
v-if="inputFields.length"
:datav-model:data="inputFields"
sortable
size="small"
row-key="variable"
:max-height="undefined"
>
<el-table-column label="参数" prop="variable" min-width="120" show-overflow-tooltip />
<el-table-column label="描述" prop="desc" min-width="120" show-overflow-tooltip />
<el-table-column label="默认值" prop="default_value" min-width="100" show-overflow-tooltip />
<el-table-column label="必填" width="55">
<template #default="{ row }"><el-switch :model-value="Boolean(row.is_required)" disabled size="small" /></template>
</el-table-column>
<el-table-column label="操作" width="80">
<template #default="{ row, $index }">
<el-button text type="primary" aria-label="编辑" @click="openDialog(row, $index)"><MkIcon name="icon_edit_outlined" /></el-button>
<el-button text type="primary" aria-label="删除" @click="deleteField($index)"><MkIcon name="icon_delete-trash_outlined" /></el-button>
</template>
</el-table-column>
</MkTable>
</div>

<ApiParameterDialog ref="fieldDialogRef" @submit="saveField" />
</template>

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<script setup lang="ts">
import { ref, useTemplateRef } from 'vue'
import { cloneDeep } from 'lodash'
import type { FormInstance } from 'element-plus'
import type { ChatInputField } from '../../types'

const emit = defineEmits<{ submit: [data: ChatInputField, index?: number] }>()
const dialogVisible = ref(false)
const editingIndex = ref<number>()
const formRef = useTemplateRef<FormInstance>('formRef')
const currentField = ref<ChatInputField>(createField())

function createField(): ChatInputField {
return { field: '', label: '' }
}

function open(field?: ChatInputField, index?: number) {
if (field) currentField.value = cloneDeep(field)
editingIndex.value = index
dialogVisible.value = true
}

function submitField() {
formRef.value
?.validate()
.then(() => emit('submit', cloneDeep(currentField.value), editingIndex.value))
.catch(() => {})
}

function resetData() {
editingIndex.value = undefined
currentField.value = createField()
formRef.value?.clearValidate()
}
function close() {
dialogVisible.value = false
}

defineExpose({ open, close })
</script>

<template>
<MkDialog v-model="dialogVisible" :title="editingIndex === undefined ? '添加参数' : '编辑参数'" align-center @closed="resetData">
<el-form ref="formRef" :model="currentField" label-position="top" require-asterisk-position="right" @submit.prevent>
<el-form-item
label="参数"
prop="field"
:rules="[
{ required: true, message: '请输入参数', trigger: 'blur' },
{ pattern: /^[a-zA-Z0-9_]+$/, message: '仅支持字母、数字和下划线', trigger: 'blur' },
]"
>
<el-input
v-model="currentField.field"
maxlength="64"
show-word-limit
placeholder="请输入参数"
@blur="currentField.field = currentField.field.trim()"
/>
</el-form-item>
<el-form-item label="显示名称" prop="label" :rules="{ required: true, message: '请输入显示名称', trigger: 'blur' }">
<el-input v-model="currentField.label" maxlength="64" show-word-limit placeholder="请输入显示名称" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submitField">{{ editingIndex === undefined ? '添加' : '保存' }}</el-button>
</template>
</MkDialog>
</template>
Loading
Loading