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: 2 additions & 0 deletions ui/src/workflow-canvas/WORKFLOW_README.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,8 @@ AI 对话、意图识别、问题优化、参数提取、图片理解、视频
#### AI 对话与提示词

LLM 节点的工具声明由 `llm-node/component/ToolsSetting.vue` 维护,导入、导出收在更多菜单。
`ToolImportDialog.vue` 负责批量导入的 JSON 解析,通过 `submit` 返回工具数组;
`ToolsSetting.vue` 按同名覆盖规则合并并写回配置后关闭弹窗,取消不修改配置。
`ToolFunctionDrawer.vue` 编辑工具草稿,支持 JSON 解析和参数表格;参数通过
`ToolParameterDialog.vue` 编辑,抽屉检查重名后更新草稿,工具提交后才写回节点。
工具参数沿用 OpenAI 类型名称,不复用工具输入参数的 `int` / `dict` 协议;取消不修改原配置。
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<script setup lang="ts">
import { ref } from 'vue'
import { normalizeTool, type LlmTool } from '../types'

defineOptions({ name: 'WorkflowLlmToolImportDialog' })

const emit = defineEmits<{ submit: [tools: LlmTool[]] }>()
const visible = ref(false)
const importJson = ref('')
const importError = ref('')

function open() {
visible.value = true
}

function close() {
visible.value = false
}

function handleImport() {
importError.value = ''
try {
const parsed = JSON.parse(importJson.value) as unknown
const imported = (Array.isArray(parsed) ? parsed : [parsed]).map(normalizeTool).filter((tool) => tool !== null)
if (!imported.length) {
importError.value = '未找到有效的工具定义'
return
}
emit('submit', imported)
} catch {
importError.value = 'JSON 解析失败,请检查格式'
}
}

function resetData() {
importJson.value = ''
importError.value = ''
}

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

<template>
<MkDialog v-model="visible" title="导入工具" @closed="resetData">
<el-input
v-model="importJson"
type="textarea"
:rows="12"
@paste.stop
placeholder='粘贴 OpenAI tools 格式 JSON,例如:
[
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取天气",
"parameters": {
"type": "object",
"properties": { "location": { "type": "string", "description": "城市" } },
"required": ["location"]
}
}
}
]'
/>
<div v-if="importError" class="mt-2 text-sm text-danger">{{ importError }}</div>
<template #footer>
<!-- 取消导入 -->
<el-button plain @click="close">取消</el-button>
<!-- 批量导入工具 -->
<el-button type="primary" @click="handleImport">导入</el-button>
</template>
</MkDialog>
</template>
68 changes: 11 additions & 57 deletions ui/src/workflow-canvas/nodes/llm-node/component/ToolsSetting.vue
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
<script setup lang="ts">
import { computed, onBeforeUnmount, ref, useTemplateRef } from 'vue'
import { computed, onBeforeUnmount, useTemplateRef } from 'vue'
import { cloneDeep } from 'lodash'
import NodeCascader from '@/workflow-canvas/component/NodeCascader.vue'
import { createAnchorGuard } from '@/workflow-canvas/core/utils'
import type { WorkflowNodeModel } from '@/workflow-canvas/core/workflow-node'
import { downloadByURL } from '@/utils/common'
import ToolFunctionDrawer from './ToolFunctionDrawer.vue'
import { normalizeTool, type LlmTool, type LlmToolsSetting } from '../types'
import ToolImportDialog from './ToolImportDialog.vue'
import type { LlmTool, LlmToolsSetting } from '../types'

defineOptions({ name: 'WorkflowLlmToolsSetting' })

const props = defineProps<{ nodeModel: WorkflowNodeModel }>()
const setting = defineModel<LlmToolsSetting>({ required: true })

const toolDrawerRef = useTemplateRef<InstanceType<typeof ToolFunctionDrawer>>('toolDrawerRef')
const toolImportDialogRef = useTemplateRef<InstanceType<typeof ToolImportDialog>>('toolImportDialogRef')
const anchorGuard = createAnchorGuard(props.nodeModel)
const existingNames = computed(() => setting.value.tools.map((tool) => tool.function.name))

Expand All @@ -37,36 +39,15 @@ function handleDeleteTool(index: number) {
updateSetting({ tools: setting.value.tools.filter((_tool, i) => i !== index) })
}

// 批量导入
const importVisible = ref(false)
const importJson = ref('')
const importError = ref('')

function handleOpenImport() {
importVisible.value = true
}

function handleImport() {
importError.value = ''
try {
const parsed = JSON.parse(importJson.value) as unknown
const imported = (Array.isArray(parsed) ? parsed : [parsed]).map(normalizeTool).filter((tool) => tool !== null)
if (!imported.length) {
importError.value = '未找到有效的工具定义'
return
}
// 同名函数以导入内容覆盖
const importedNames = new Set(imported.map((tool) => tool.function.name))
updateSetting({ tools: [...setting.value.tools.filter((tool) => !importedNames.has(tool.function.name)), ...imported] })
importVisible.value = false
} catch {
importError.value = 'JSON 解析失败,请检查格式'
}
toolImportDialogRef.value?.open()
}

function resetImport() {
importJson.value = ''
importError.value = ''
function handleImport(imported: LlmTool[]) {
// 同名函数以导入内容覆盖
const importedNames = new Set(imported.map((tool) => tool.function.name))
updateSetting({ tools: [...setting.value.tools.filter((tool) => !importedNames.has(tool.function.name)), ...imported] })
toolImportDialogRef.value?.close()
}

function handleExport() {
Expand Down Expand Up @@ -175,32 +156,5 @@ onBeforeUnmount(() => anchorGuard.reset())

<ToolFunctionDrawer ref="toolDrawerRef" :existing-names="existingNames" @submit="handleToolSubmit" />

<MkDialog v-model="importVisible" title="导入工具" @closed="resetImport">
<el-input
v-model="importJson"
type="textarea"
:rows="12"
@paste.stop
placeholder='粘贴 OpenAI tools 格式 JSON,例如:
[
{
"type": "function",
"function": {
"name": "get_weather",
"description": "获取天气",
"parameters": {
"type": "object",
"properties": { "location": { "type": "string", "description": "城市" } },
"required": ["location"]
}
}
}
]'
/>
<div v-if="importError" class="mt-2 text-sm text-danger">{{ importError }}</div>
<template #footer>
<el-button plain @click="importVisible = false">取消</el-button>
<el-button type="primary" @click="handleImport">导入</el-button>
</template>
</MkDialog>
<ToolImportDialog ref="toolImportDialogRef" @submit="handleImport" />
</template>
Loading