jiaxing.liao 2 дней назад
Родитель
Сommit
b6a41397c2

+ 2 - 0
apps/web/src/features/ChatDrawer/index.vue

@@ -40,6 +40,7 @@ const { t } = useI18n()
 const showForm = ref(false)
 const chatContainerRef = ref<InstanceType<typeof Chat>>()
 const hasVisibleForm = computed(() => props.visibleVariables.length > 0)
+const chatKey = computed(() => props.startNode?.id || 'workflow-chat')
 
 const closeDrawer = () => {
 	handleDrawerUpdate(false)
@@ -154,6 +155,7 @@ defineExpose({
 
 				<div class="chat-drawer__chat">
 					<Chat
+						:key="chatKey"
 						ref="chatContainerRef"
 						:node="startNode || undefined"
 						:workflow-id="workflow.id"

+ 2 - 1
apps/web/src/features/RunWorkflow/utils.ts

@@ -1,4 +1,5 @@
 import type { StartVariable } from '@/nodes/src/start'
+import { cloneDeep } from 'lodash-es'
 
 export const RUN_WORKFLOW_QUERY_VARIABLE = 'query'
 
@@ -75,7 +76,7 @@ export function buildExecuteParams(options: {
 		const fieldName = variable.name
 		if (!fieldName || excluded.has(fieldName)) return
 
-		let value = structuredClone(inputValues[fieldName])
+		let value = cloneDeep(inputValues[fieldName])
 
 		if (variable.formType === 'json_object') {
 			const draft = `${jsonDrafts[fieldName] || ''}`.trim()

+ 45 - 8
apps/web/src/features/toolbar/index.vue

@@ -54,13 +54,36 @@
 		<el-tooltip
 			:content="!props.canChat ? '需要用户输入节点' : t('pages.toolbar.chat')"
 			placement="left"
+			:disabled="props.canChat && props.chatNodes.length > 1"
 		>
-			<IconButton
-				icon="lucide:message-circle"
-				:class="{ 'is-disabled': !props.canChat }"
-				square
-				@click="handleChatClick"
-			/>
+			<el-popover
+				ref="chatPopoverRef"
+				width="240px"
+				trigger="click"
+				:disabled="!props.canChat || props.chatNodes.length <= 1"
+			>
+				<div v-if="props.chatNodes.length > 1" class="run-selector">
+					<div class="run-selector__title">{{ t('pages.toolbar.chatEntry') }}</div>
+					<button
+						v-for="node in props.chatNodes"
+						:key="node.id"
+						type="button"
+						class="run-selector__item"
+						@click="handleSelectChatNode(node.id)"
+					>
+						<span class="run-selector__name">{{ node.name || getNodeDisplayName('start') }}</span>
+						<span class="run-selector__type">{{ getNodeDisplayName('start') }}</span>
+					</button>
+				</div>
+				<template #reference>
+					<IconButton
+						icon="lucide:message-circle"
+						:class="{ 'is-disabled': !props.canChat }"
+						square
+						@click="handleChatClick"
+					/>
+				</template>
+			</el-popover>
 		</el-tooltip>
 
 		<AgentEnvDialog v-model="showEnvDialog" @change="handleEnvChange" :value="envVars" />
@@ -88,6 +111,11 @@ const props = defineProps<{
 		name: string
 		nodeType: string
 	}[]
+	chatNodes: {
+		id: string
+		name?: string
+		nodeType: string
+	}[]
 	canChat?: boolean
 }>()
 
@@ -101,13 +129,14 @@ const emit = defineEmits<{
 		}[]
 	): void
 	(e: 'run', id?: string): void
-	(e: 'chat'): void
+	(e: 'chat', id: string): void
 	(e: 'create:node', value: { type: string } | string): void
 }>()
 
 const { t } = useI18n()
 const showEnvDialog = ref(false)
 const popoverRef = ref<PopoverInstance>()
+const chatPopoverRef = ref<PopoverInstance>()
 
 function handleEnvChange(
 	payload: {
@@ -127,7 +156,15 @@ function handleRunClick() {
 
 function handleChatClick() {
 	if (!props.canChat) return
-	emit('chat')
+	// 多节点时由 popover 处理选择,这里只处理单节点情况
+	if (props.chatNodes.length <= 1 && props.chatNodes[0]) {
+		emit('chat', props.chatNodes[0].id)
+	}
+}
+
+function handleSelectChatNode(id: string) {
+	chatPopoverRef.value?.hide()
+	emit('chat', id)
 }
 
 function handleSelectRunNode(id: string) {

+ 1 - 0
apps/web/src/i18n/locales/en-us.ts

@@ -1660,6 +1660,7 @@ export default {
 			chat: 'Chat',
 			env: 'Environment Variables',
 			runEntry: 'Choose run entry',
+			chatEntry: 'Choose chat entry',
 			envDialog: {
 				title: 'Environment Variables',
 				description:

+ 1 - 0
apps/web/src/i18n/locales/zh-cn.ts

@@ -1561,6 +1561,7 @@ export default {
 			chat: '对话预览',
 			env: '环境变量',
 			runEntry: '选择运行入口',
+			chatEntry: '选择对话入口',
 			envDialog: {
 				title: '环境变量',
 				description: '环境变量用以配置智能体运行时所需的环境变量,例如 API_KEY、SECRET_KEY 等。',

+ 3 - 3
apps/web/src/views/chat/index.vue

@@ -219,7 +219,7 @@
 </template>
 
 <script setup lang="ts">
-import { computed, nextTick, onMounted, reactive, ref, watch } from 'vue'
+import { computed, nextTick, onMounted, reactive, ref, toRaw, watch } from 'vue'
 import { useRoute } from 'vue-router'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { PictureFilled } from '@element-plus/icons-vue'
@@ -547,7 +547,7 @@ const initializeWorkflowFormValues = () => {
 	workflowStartVariables.value.forEach((variable) => {
 		const initialValue =
 			variable.default_value !== undefined
-				? structuredClone(variable.default_value)
+				? structuredClone(toRaw(variable.default_value))
 				: createEmptyValue(variable.formType)
 
 		if (variable.formType === 'json_object') {
@@ -1918,7 +1918,7 @@ const handleCancel = async () => {
 .workflow-form {
 	width: 100%;
 	max-width: 100%;
-	max-height: min(360px, 42vh);
+	max-height: 80vh;
 	margin: 0;
 	padding: 16px;
 	overflow-y: auto;

+ 25 - 12
apps/web/src/views/editor/NodeView.vue

@@ -31,9 +31,10 @@
 			<Toolbar
 				@create:node="handleNodeCreate"
 				@run="handleRunAgent"
-				@chat="handleOpenWorkflowChat"
+				@chat="handleWorkflowChatSelectStartNode"
 				:env-vars="workflow?.env_variables || []"
 				:run-nodes="toolbarRunNodes"
+				:chat-nodes="workflowChatStartNodes"
 				:can-chat="canRunWorkflowChat"
 				@change-env-vars="handleChangeEnvVars"
 			/>
@@ -210,6 +211,7 @@ const runWorkflowInputOnly = ref(false)
 const runWorkflowNodeId = ref('')
 const runWorkflowInitialTab = ref<'input' | 'trigger' | 'result' | 'detail' | 'trace'>('input')
 const workflowChatVisible = ref(false)
+const workflowChatSelectedStartNodeId = ref('')
 const workflowChatInputValues = ref<Record<string, any>>({})
 const workflowChatJsonDrafts = ref<Record<string, string>>({})
 const workflowChatValidationErrors = ref<Record<string, string>>({})
@@ -443,12 +445,17 @@ const getStartVariables = (node?: IWorkflowNode | null): StartVariable[] => {
 	return Array.isArray(variables) ? variables : []
 }
 
+const workflowChatStartNodes = computed<IWorkflowNode[]>(() =>
+	(props.workflow?.nodes || []).filter((node) => {
+		const nodeType = (node as any)?.nodeType || (node as any)?.data?.nodeType
+		return nodeType === 'start'
+	})
+)
+
 const workflowChatStartNode = computed<IWorkflowNode | null>(() => {
 	return (
-		(props.workflow?.nodes || []).find((node) => {
-			const nodeType = (node as any)?.nodeType || (node as any)?.data?.nodeType
-			return nodeType === 'start'
-		}) || null
+		workflowChatStartNodes.value.find((node) => node.id === workflowChatSelectedStartNodeId.value) ||
+		null
 	)
 })
 
@@ -460,7 +467,7 @@ const workflowChatVisibleVariables = computed(() =>
 	workflowChatStartVariables.value.filter((item) => !item.is_hide)
 )
 
-const canRunWorkflowChat = computed(() => !!workflowChatStartNode.value)
+const canRunWorkflowChat = computed(() => workflowChatStartNodes.value.length > 0)
 
 const workflowChatRunning = computed(
 	() => runnerStore.status === 'connecting' || runnerStore.status === 'running'
@@ -756,12 +763,8 @@ const buildWorkflowChatParams = () => {
 	return params
 }
 
-const handleOpenWorkflowChat = () => {
-	if (!workflowChatStartNode.value) {
-		ElMessage.warning(t('pages.nodeView.messages.missingTrigger'))
-		return
-	}
-
+const handleWorkflowChatSelectStartNode = (id: string) => {
+	workflowChatSelectedStartNodeId.value = id
 	initializeWorkflowChatInputValues()
 	resetWorkflowChatValidation()
 	workflowChatBaseParams.value = {}
@@ -841,6 +844,16 @@ watch(
 	{ flush: 'post' }
 )
 
+watch(
+	() => workflowChatStartNodes.value.map((node) => node.id),
+	(ids) => {
+		if (!ids.includes(workflowChatSelectedStartNodeId.value)) {
+			workflowChatSelectedStartNodeId.value = ''
+			workflowChatVisible.value = false
+		}
+	}
+)
+
 /**
  * 创建新节点
  */