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
3 changes: 3 additions & 0 deletions ui/src/assets/workflow/icon_parameter_extraction.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions ui/src/assets/workflow/icon_variable-aggregation.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions ui/src/assets/workflow/icon_variable-assign.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions ui/src/assets/workflow/icon_variable-splitting.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 26 additions & 4 deletions ui/src/components/COMPONENT_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -768,17 +768,39 @@ const code = defineModel<string>({ required: true })
### JsonInput

JSON 专用输入框,通过 `v-model` 接收并回传解析后的 JSON 值,内置 JSON 语法诊断、格式化和全屏
编辑。组件暴露 `validateRules`,可直接接入 Element Plus 表单自定义校验器;无法解析的输入不会
覆盖最后一次有效的 `v-model` 值。
编辑。与 `MdEditorMagnify` 一样,通过 `useFormItem()` 触发外层表单项校验,不在组件内部创建
表单项。内容变化时触发 `change`,失焦和全屏确认后触发 `blur`;`validateEvent` 默认为 `true`,
设为 `false` 可关闭自动触发。无效输入也会触发校验,不能仅监听解析后的 `v-model`。

组件暴露 `format()` 和 `validateRules()`。JSON 语法规则统一由 `validateRules` 校验编辑器原始
文本,空白或无效 JSON 提示“请输入正确的 JSON 格式”;外层 `rules` 接入该方法后,表单提交
也会检查语法。无法解析的输入不会覆盖最后一次有效的 `v-model` 值,不要直接对已经解析的
`value` 再调用 `JSON.parse(value)`。必填等业务规则继续放在外层表单项。

```vue
<script setup lang="ts">
import { reactive, useTemplateRef } from 'vue'
import JsonInput from '@/components/codemirror-editor/Json.vue'

const config = defineModel<unknown>({ required: true })
const form = reactive<{ config: unknown }>({ config: {} })
const jsonInputRef = useTemplateRef<InstanceType<typeof JsonInput>>('jsonInputRef')

function validateJson(rule: unknown, value: unknown, callback: (error?: Error) => void) {
if (!jsonInputRef.value) {
callback(new Error('请输入配置'))
return
}
jsonInputRef.value.validateRules(rule, value, callback)
}
</script>

<JsonInput v-model="config" title="配置(JSON)" />
<template>
<el-form :model="form">
<el-form-item prop="config" :rules="{ validator: validateJson, trigger: ['change', 'blur'] }">
<JsonInput ref="jsonInputRef" v-model="form.config" title="配置(JSON)" />
</el-form-item>
</el-form>
</template>
```

### MkSourceCard
Expand Down
51 changes: 28 additions & 23 deletions ui/src/components/business/model-select/index.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<script setup lang="ts">
<script setup lang="ts" generic="Multiple extends boolean = false">
import { computed, ref, useTemplateRef } from 'vue'
import type { SelectInstance } from 'element-plus'
import { MODEL_STATUS } from '@/api/enums'
Expand All @@ -18,18 +18,20 @@ interface ModelOptionGroup {
provider: string
}

type ModelSelectValue = Multiple extends true ? string[] : string

const props = withDefaults(
defineProps<{
modelValue: string
modelValue: ModelSelectValue
options: ModelItem[]
providerOptions: ModelProviderItem[]
canEditParams?: boolean
canAdd?: boolean
modelParams?: Record<string, unknown>
disabled?: boolean
multiple?: Multiple
}>(),
{
modelValue: '',
options: () => [],
providerOptions: () => [],
canEditParams: false,
Expand All @@ -40,9 +42,9 @@ const props = withDefaults(
)

const emit = defineEmits<{
change: [modelId: string]
change: [modelValue: ModelSelectValue]
refresh: []
'update:modelValue': [modelId: string]
'update:modelValue': [modelValue: ModelSelectValue]
'update:modelParams': [settings: Record<string, unknown>]
}>()

Expand All @@ -60,24 +62,26 @@ const modelOptionGroups = computed<ModelOptionGroup[]>(() => {
})
})

const selectedProviderIcon = computed(
() => modelOptionGroups.value.find(({ models }) => models.some(({ id }) => id === selectedModelId.value))?.icon ?? '',
)
const _options = computed(() => {
return groupBy(props.options, 'provider')
})

const loading = ref(false)
const canEditModelParams = computed(() => props.canEditParams && !props.multiple)

const selectedModelId = computed({
const selectedModelValue = computed<ModelSelectValue>({
get: () => props.modelValue,
set: (modelId) => {
emit('update:modelValue', modelId)
emit('change', modelId)
resetModelParams(modelId)
set: (modelValue) => {
emit('update:modelValue', modelValue)
emit('change', modelValue)
if (typeof modelValue === 'string') resetModelParams(modelValue)
},
})

function getProviderIcon(modelId: unknown) {
return modelOptionGroups.value.find(({ models }) => models.some(({ id }) => id === modelId))?.icon ?? ''
}

// 创建模型:根据当前资源范围传入完整 API,创建后由调用方刷新选项。
const selectRef = useTemplateRef<SelectInstance>('selectRef')
const allModelProvider: ModelProviderItem = { icon: '', name: '全部模型', provider: 'all' }
Expand All @@ -97,33 +101,34 @@ function handleOpenCreateModel(open: () => void) {
const modelParamsDialogRef = useTemplateRef<InstanceType<typeof ModelParamsDialog>>('modelParamsDialogRef')

function resetModelParams(modelId: string) {
if (!props.canEditParams) return
if (!canEditModelParams.value) return
emit('update:modelParams', {})
if (!modelId) return
modelParamsDialogRef.value?.resetDefault(modelId).then((settings) => {
if (props.modelValue !== modelId || !props.canEditParams) return
if (props.modelValue !== modelId || !canEditModelParams.value) return
emit('update:modelParams', settings)
})
}

function openModelParams() {
if (!props.modelValue || props.disabled) return
if (typeof props.modelValue !== 'string' || !props.modelValue || props.disabled) return
modelParamsDialogRef.value?.open(props.modelValue, props.modelParams)
}
</script>

<template>
<div class="relative w-full" :class="{ 'model-select--with-params': canEditParams }">
<div class="relative w-full" :class="{ 'model-select--with-params': canEditModelParams }">
<el-select
ref="selectRef"
v-model="selectedModelId"
v-model="selectedModelValue"
placeholder="请选择模型"
v-bind="$attrs"
class="w-full"
:disabled="disabled"
clearable
filterable
:loading="loading"
:multiple="multiple"
:teleported="false"
>
<el-option-group v-for="group in modelOptionGroups" :key="group.provider" :label="group.name">
Expand All @@ -143,9 +148,9 @@ function openModelParams() {
</el-option>
</el-option-group>

<template #label="{ label }">
<div class="flex items-center gap-2">
<span class="h-5 w-5 shrink-0" v-html="selectedProviderIcon" />
<template #label="{ label, value }">
<div class="flex items-center" :class="multiple ? 'gap-1' : 'gap-2'">
<span v-if="getProviderIcon(value)" class="shrink-0" :class="multiple ? 'h-4 w-4' : 'h-5 w-5'" v-html="getProviderIcon(value)" />
<span class="truncate" :title="label">{{ label }}</span>
</div>
</template>
Expand All @@ -163,7 +168,7 @@ function openModelParams() {
</slot>
</template>
</el-select>
<div v-if="canEditParams" class="absolute inset-y-px right-3 flex items-center gap-2">
<div v-if="canEditModelParams" class="absolute inset-y-px right-3 flex items-center gap-2">
<el-divider direction="vertical" />
<el-tooltip content="模型参数设置" placement="top" :disabled="disabled || !modelValue">
<el-button text class="-mr-1" :disabled="disabled || !modelValue" @click.stop="openModelParams">
Expand All @@ -172,7 +177,7 @@ function openModelParams() {
</el-tooltip>
</div>
</div>
<ModelParamsDialog v-if="canEditParams" ref="modelParamsDialogRef" @submit="emit('update:modelParams', $event)" />
<ModelParamsDialog v-if="canEditModelParams" ref="modelParamsDialogRef" @submit="emit('update:modelParams', $event)" />
</template>

<style scoped lang="scss">
Expand Down
20 changes: 16 additions & 4 deletions ui/src/components/codemirror-editor/Json.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import { nextTick, ref, watch } from 'vue'
import { useFormItem } from 'element-plus'
import { DocumentChecked } from '@element-plus/icons-vue'
import { json, jsonParseLinter } from '@codemirror/lang-json'
import { linter } from '@codemirror/lint'
Expand All @@ -8,10 +9,17 @@ import { Codemirror } from 'vue-codemirror'
defineOptions({ name: 'JsonInput', inheritAttrs: false })

const modelValue = defineModel<unknown>({ required: true })
const props = withDefaults(defineProps<{ title?: string }>(), { title: 'JSON' })
const props = withDefaults(defineProps<{ title?: string; validateEvent?: boolean }>(), { title: 'JSON', validateEvent: true })

const emit = defineEmits<{ submitDialog: [value: unknown] }>()

const { formItem } = useFormItem()
// 与 MdEditorMagnify 一致,由外层 FormItem 管理规则和错误提示。
function validateFormItem(trigger: 'change' | 'blur') {
if (!props.validateEvent || !formItem?.propString) return
formItem.validate(trigger).catch(() => {})
}

const extensions = [json(), linter(jsonParseLinter())]

function stringifyJson(value: unknown) {
Expand Down Expand Up @@ -44,6 +52,7 @@ function handleContentChange(content: string) {
} catch {
// 保留无法解析的编辑内容,交由 CodeMirror 和表单校验提示。
}
void nextTick(() => validateFormItem('change'))
}

function format() {
Expand All @@ -67,20 +76,22 @@ function closeEditorDialog() {
dialogVisible.value = false
}

function submitEditorDialog() {
async function submitEditorDialog() {
try {
const value = parseJson(dialogContent.value)
handleContentChange(dialogContent.value)
emit('submitDialog', value)
closeEditorDialog()
await nextTick()
validateFormItem('blur')
} catch {
// JSON 不合法时保留弹窗和编辑内容,等待用户修正。
}
}

function validateRules(_rule: unknown, _value: unknown, callback: (error?: Error) => void) {
try {
parseJson(editorContent.value)
JSON.parse(editorContent.value)
callback()
} catch {
callback(new Error('请输入正确的 JSON 格式'))
Expand All @@ -100,6 +111,7 @@ defineExpose({ format, validateRules })
:style="{ height: '210px' }"
v-bind="$attrs"
@update:model-value="handleContentChange"
@blur="validateFormItem('blur')"
/>
<el-button class="absolute right-2 top-2" text type="info" @click="format">
<MkIcon :icon="DocumentChecked" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ onMounted(() => {
</el-button>
</div>

<div class="w-full mk-gray-card py-3!">
<div class="w-full mk-gray-card">
<MkFormList v-model="formValue.option_list" :default-item="{ label: '', value: '' }" :show-add-button="false" @remove="handleOptionRemove">
<template #default="{ index, item: option }">
<el-form-item
Expand Down
Loading
Loading