Bladeren bron

feat: 调整节点问题

jiaxing.liao 1 week geleden
bovenliggende
commit
c06d6c18c9

+ 8 - 0
apps/web/components.d.ts

@@ -12,6 +12,7 @@ export {}
 /* prettier-ignore */
 declare module 'vue' {
   export interface GlobalComponents {
+    AutoFocusPlugin: typeof import('./src/components/PromptEditor/plugins/AutoFocusPlugin.vue')['default']
     BranchCard: typeof import('./src/components/SetterCommon/condition/BranchCard.vue')['default']
     CodeEditor: typeof import('./src/components/SetterCommon/Code/CodeEditor.vue')['default']
     CodeSetter: typeof import('./src/components/setter/CodeSetter.vue')['default']
@@ -66,7 +67,9 @@ declare module 'vue' {
     ExecutionChart: typeof import('./src/components/Chart/ExecutionChart.vue')['default']
     HttpSetter: typeof import('./src/components/setter/HttpSetter.vue')['default']
     InputVariables: typeof import('./src/components/SetterCommon/Code/InputVariables.vue')['default']
+    OnChangePlugin: typeof import('./src/components/PromptEditor/plugins/OnChangePlugin.vue')['default']
     OutputVariables: typeof import('./src/components/SetterCommon/Code/OutputVariables.vue')['default']
+    PromptEditor: typeof import('./src/components/PromptEditor/index.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
     RunWorkflow: typeof import('./src/components/RunWorkflow/index.vue')['default']
@@ -74,6 +77,7 @@ declare module 'vue' {
     Setter: typeof import('./src/components/setter/index.vue')['default']
     Sidebar: typeof import('./src/components/Sidebar/index.vue')['default']
     SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
+    TagPlugin: typeof import('./src/components/PromptEditor/plugins/TagPlugin.vue')['default']
     TemplateModal: typeof import('./src/components/TemplateModal/index.vue')['default']
     TestConfig: typeof import('./src/components/SetterCommon/Code/TestConfig.vue')['default']
   }
@@ -84,6 +88,7 @@ declare module 'vue' {
 
 // For TSX support
 declare global {
+  const AutoFocusPlugin: typeof import('./src/components/PromptEditor/plugins/AutoFocusPlugin.vue')['default']
   const BranchCard: typeof import('./src/components/SetterCommon/condition/BranchCard.vue')['default']
   const CodeEditor: typeof import('./src/components/SetterCommon/Code/CodeEditor.vue')['default']
   const CodeSetter: typeof import('./src/components/setter/CodeSetter.vue')['default']
@@ -138,7 +143,9 @@ declare global {
   const ExecutionChart: typeof import('./src/components/Chart/ExecutionChart.vue')['default']
   const HttpSetter: typeof import('./src/components/setter/HttpSetter.vue')['default']
   const InputVariables: typeof import('./src/components/SetterCommon/Code/InputVariables.vue')['default']
+  const OnChangePlugin: typeof import('./src/components/PromptEditor/plugins/OnChangePlugin.vue')['default']
   const OutputVariables: typeof import('./src/components/SetterCommon/Code/OutputVariables.vue')['default']
+  const PromptEditor: typeof import('./src/components/PromptEditor/index.vue')['default']
   const RouterLink: typeof import('vue-router')['RouterLink']
   const RouterView: typeof import('vue-router')['RouterView']
   const RunWorkflow: typeof import('./src/components/RunWorkflow/index.vue')['default']
@@ -146,6 +153,7 @@ declare global {
   const Setter: typeof import('./src/components/setter/index.vue')['default']
   const Sidebar: typeof import('./src/components/Sidebar/index.vue')['default']
   const SvgIcon: typeof import('./src/components/SvgIcon/index.vue')['default']
+  const TagPlugin: typeof import('./src/components/PromptEditor/plugins/TagPlugin.vue')['default']
   const TemplateModal: typeof import('./src/components/TemplateModal/index.vue')['default']
   const TestConfig: typeof import('./src/components/SetterCommon/Code/TestConfig.vue')['default']
 }

+ 1 - 2
apps/web/package.json

@@ -10,14 +10,13 @@
   },
   "dependencies": {
     "@element-plus/icons-vue": "^2.3.2",
-    "@lexical/text": "^0.41.0",
-    "@lexical/utils": "^0.41.0",
     "@vitejs/plugin-vue-jsx": "^5.1.3",
     "@vueuse/core": "^14.2.0",
     "axios": "^1.13.2",
     "echarts": "^6.0.0",
     "element-plus": "^2.13.1",
     "lexical": "^0.41.0",
+    "lexical-vue": "^0.14.1",
     "lodash-es": "^4.17.21",
     "monaco-editor": "^0.55.1",
     "normalize.css": "^8.0.1",

+ 18 - 0
apps/web/src/components/PromptEditor/editorConfig.ts

@@ -0,0 +1,18 @@
+import { type InitialConfigType } from 'lexical-vue'
+
+const editorConfig: InitialConfigType = {
+	namespace: 'PromptEditor',
+	theme: {
+		ltr: 'ltr',
+		rtl: 'rtl',
+		placeholder: 'editor-placeholder',
+		paragraph: 'editor-paragraph'
+	},
+	editable: true,
+	onError(error: Error) {
+		throw error
+	},
+	nodes: []
+}
+
+export default editorConfig

+ 71 - 0
apps/web/src/components/PromptEditor/index.vue

@@ -0,0 +1,71 @@
+<script setup lang="ts">
+import { LexicalComposer } from 'lexical-vue/LexicalComposer'
+import { ContentEditable } from 'lexical-vue/LexicalContentEditable'
+import { HistoryPlugin } from 'lexical-vue/LexicalHistoryPlugin'
+import { PlainTextPlugin } from 'lexical-vue/LexicalPlainTextPlugin'
+import editorConfig from './editorConfig'
+
+const props = defineProps<{
+	modelValue?: string
+	placeholder?: string
+}>()
+</script>
+
+<template>
+	<LexicalComposer :initial-config="editorConfig">
+		<div class="el-input__wrapper">
+			<PlainTextPlugin>
+				<template #contentEditable>
+					<ContentEditable class="el-input__inner">
+						<template #placeholder>
+							<div class="editor-placeholder">{{ placeholder }}</div>
+						</template>
+					</ContentEditable>
+				</template>
+			</PlainTextPlugin>
+		</div>
+		<HistoryPlugin />
+	</LexicalComposer>
+</template>
+
+<style lang="less" scoped>
+.ltr {
+	text-align: left;
+}
+
+.rtl {
+	text-align: right;
+}
+
+.el-input__wrapper {
+	width: 100%;
+}
+:deep(.el-input__inner) {
+	text-align: left;
+	min-height: 32px;
+	resize: none;
+	font-size: 15px;
+	caret-color: rgb(5, 5, 5);
+	position: relative;
+	tab-size: 1;
+	outline: 0;
+	caret-color: #444;
+	p {
+		margin: 0;
+		line-height: 32px;
+	}
+}
+
+.editor-placeholder {
+	color: #999;
+	overflow: hidden;
+	position: absolute;
+	text-overflow: ellipsis;
+	top: 7px;
+	left: 10px;
+	font-size: 15px;
+	user-select: none;
+	display: inline-block;
+	pointer-events: none;
+}
+</style>

+ 17 - 0
apps/web/src/components/PromptEditor/plugins/AutoFocusPlugin.vue

@@ -0,0 +1,17 @@
+<script setup lang="ts">
+import { useLexicalComposer } from 'lexical-vue'
+import { onMounted } from 'vue'
+
+// Lexical Vue plugins are Vue components, which makes them
+// highly composable. Furthermore, you can lazy load plugins if
+// desired, so you don't pay the cost for plugins until you
+// actually use them.
+const editor = useLexicalComposer()
+
+onMounted(() => {
+	// Focus the editor when the effect fires!
+	editor.focus()
+})
+</script>
+
+<template />

+ 42 - 0
apps/web/src/components/PromptEditor/plugins/TagPlugin.ts

@@ -0,0 +1,42 @@
+import { TextNode } from 'lexical'
+
+export class TagNode extends TextNode {
+	static getType() {
+		return 'tag'
+	}
+
+	static clone(node) {
+		return new TagNode(node.__className, node.__text, node.__key)
+	}
+
+	constructor(className: string, text: string, key?: string) {
+		super(text, key)
+		this.__className = className
+	}
+
+	createDOM(config) {
+		const dom = document.createElement('span')
+		const inner = super.createDOM(config)
+		dom.className = this.__className
+		inner.className = 'emoji-inner'
+		dom.appendChild(inner)
+		return dom
+	}
+
+	updateDOM(prevNode, dom, config) {
+		const inner = dom.firstChild
+		if (inner === null) {
+			return true
+		}
+		super.updateDOM(prevNode, inner, config)
+		return false
+	}
+}
+
+export function $isTagNode(node) {
+	return node instanceof TagNode
+}
+
+export function $createTagNode(className: string, tagText: string) {
+	return new TagNode(className, tagText).setMode('token')
+}

+ 22 - 20
apps/web/src/features/nodeLibary/index.vue

@@ -33,29 +33,31 @@ const { onDragStart } = useDragAndDrop()
 const materials = computed<NodeGroup[]>(() => {
 	const groupMap = new Map<string, NodeGroup>()
 
-	nodes.forEach((node) => {
-		const groupName = node.group || '其他'
-
-		if (!groupMap.has(groupName)) {
-			groupMap.set(groupName, {
-				id: groupName,
-				label: groupName,
-				nodes: []
-			})
-		}
+	nodes
+		.filter((node) => !node.name?.includes('custom-'))
+		.forEach((node) => {
+			const groupName = node.group || '其他'
+
+			if (!groupMap.has(groupName)) {
+				groupMap.set(groupName, {
+					id: groupName,
+					label: groupName,
+					nodes: []
+				})
+			}
 
-		const group = groupMap.get(groupName)!
+			const group = groupMap.get(groupName)!
 
-		group.nodes.push({
-			id: node.name,
-			type: node.schema.nodeType,
-			name: node.displayName,
-			description: node.description,
-			icon: node.icon,
-			iconColor: node.iconColor,
-			raw: node
+			group.nodes.push({
+				id: node.name,
+				type: node.schema.nodeType,
+				name: node.displayName,
+				description: node.description,
+				icon: node.icon,
+				iconColor: node.iconColor,
+				raw: node
+			})
 		})
-	})
 
 	// 保持分组顺序稳定
 	return Array.from(groupMap.values())

+ 4 - 0
apps/web/src/nodes/Interface.ts

@@ -121,6 +121,10 @@ export interface INodeType {
 	 * 输出端口列表
 	 */
 	outputs: EndpointType[] | ((params: any) => EndpointType[])
+	/**
+	 * 隐藏显示工具栏
+	 */
+	hideToolBar?: boolean
 	/**
 	 * 输出端口名称
 	 */

+ 4 - 447
apps/web/src/nodes/_base/VarInput.vue

@@ -1,454 +1,11 @@
 <template>
-	<div class="var-input">
-		<el-popover
-			:visible="visible"
-			width="320"
-			popper-class="var-input__popover"
-			placement="bottom-start"
-		>
-			<!-- 整体输入区域 -->
-			<template #reference>
-				<div class="var-input__wrapper" @click="focusInput">
-					<!-- 变量 tag 区域 -->
-					<el-tag v-if="variableExpression">
-						<div>
-							<div v-if="valueInfo.type === 'env'" class="flex gap-1 items-center truncate">
-								<span class="var-input__item-prefix env text-6px">
-									<span>ENV</span>
-								</span>
-								<span class="text-gray-600">{{ valueInfo.value }}</span>
-							</div>
-							<div v-else class="truncate flex items-center gap-1">
-								<span
-									class="var-input__item-prefix env text-10px"
-									:style="{ background: nodeMap[valueInfo?.nodeType ?? '']?.iconColor ?? '' }"
-								>
-									<Icon :icon="nodeMap[valueInfo?.nodeType!]?.icon!" :size="18" />
-								</span>
-								<span>{{ valueInfo.nodeName }}</span>
-								<span class="mx-2px text-gray-400">/</span>
-								<span class="text-gray-600">{{ valueInfo.value }}</span>
-							</div>
-						</div>
-					</el-tag>
-
-					<!-- 文本输入区域 -->
-					<el-input
-						ref="textInputRef"
-						v-model="textValue"
-						class="var-input__text"
-						v-bind="$attrs"
-						@keydown="handleKeydown"
-					/>
-
-					<!-- 字数统计(可选) -->
-					<div v-if="showCounter" class="var-input__counter">
-						{{ textValue.length }}/{{ maxLength || 0 }}
-					</div>
-				</div>
-			</template>
-
-			<!-- 变量选择面板 -->
-			<div class="var-input__panel">
-				<el-input
-					v-model="keyword"
-					clearable
-					size="small"
-					placeholder="搜索变量"
-					class="var-input__search"
-				/>
-
-				<div class="var-input__list">
-					<div v-for="group in filteredVars" :key="group.id" class="var-input__group">
-						<div class="var-input__group-title">{{ group.label }}</div>
-						<div
-							v-for="item in group.list"
-							:key="`${group.label}-${item.name}`"
-							class="var-input__item"
-							@click="
-								handleSelect({
-									value: `${group.id}.${item.name}`,
-									type: item.type
-								})
-							"
-						>
-							<span
-								class="var-input__item-prefix env text-10px"
-								:style="{ background: nodeMap[group?.nodeType ?? '']?.iconColor ?? '' }"
-							>
-								<span v-if="group.id === 'env'" class="text-6px">ENV</span>
-								<Icon v-if="group?.nodeType" :icon="nodeMap[group?.nodeType]?.icon!" :size="18" />
-							</span>
-							<span class="var-input__item-name" :title="item.name">{{ item.name }}</span>
-							<span class="var-input__item-type">{{ item.typeLabel }}</span>
-						</div>
-					</div>
-
-					<div v-if="!filteredVars.length" class="var-input__empty">暂无匹配的变量</div>
-				</div>
-			</div>
-		</el-popover>
+	<div>
+		<PromptEditor v-model="modelValue" v-bind="$attrs" />
 	</div>
 </template>
 
 <script setup lang="ts">
-import { computed, ref, watch, inject, type Ref } from 'vue'
-import type { InputInstance } from 'element-plus'
-import { Icon } from '@repo/ui'
-import { nodeMap } from '@/nodes'
-
-type EnvVarType = 'string' | 'number' | 'boolean' | 'object' | 'array' | string
-
-interface Props {
-	/** 对外值,如:#{xxx.var1}xxxx */
-	modelValue: string
-	/** 是否显示右侧计数器(可选) */
-	showCounter?: boolean
-	/** 最大长度,仅用于显示计数,不做硬校验 */
-	maxLength?: number
-}
-
-const props = withDefaults(defineProps<Props>(), {
-	modelValue: '',
-	showCounter: false,
-	maxLength: 0
-})
-
-const emit = defineEmits<{
-	(e: 'update:modelValue', value: string): void
-	(e: 'change', value: { value: string; type: string }): void
-}>()
-
-const nodeVars =
-	inject<
-		Ref<{ type: 'env' | 'output'; list: any[]; name: string; nodeType: string; id: string }[]>
-	>('nodeVars')
-
-const visible = ref(false)
-const keyword = ref('')
-
-// 变量占位部分(如 #{xxx.var1})
-const variableExpression = ref<string>('')
-// 文本部分
-const textValue = ref<string>('')
-
-const textInputRef = ref<InputInstance>()
-
-const showCounter = computed(() => props.showCounter && !!props.maxLength)
-const maxLength = computed(() => props.maxLength)
-
-/** 把外部的 modelValue 拆成「变量  文本」 */
-const parseModelValue = (value: string) => {
-	if (value?.startsWith('#{')) {
-		const endIndex = value.indexOf('}')
-		if (endIndex > 1) {
-			variableExpression.value = value.slice(0, endIndex + 1)
-			textValue.value = value.slice(endIndex + 1)
-			return
-		}
-	}
-	variableExpression.value = ''
-	textValue.value = value || ''
-}
-
-// 初始化
-parseModelValue(props.modelValue)
-
-watch(
-	() => props.modelValue,
-	(val) => {
-		const combined = variableExpression.value + textValue.value
-		if (val !== combined) {
-			parseModelValue(val)
-		}
-	}
-)
-
-// 组合内部值并回传
-const syncToOuter = () => {
-	const combined = `${variableExpression.value}${textValue.value}`
-	emit('update:modelValue', combined)
-}
-
-watch(
-	() => textValue.value,
-	() => {
-		syncToOuter()
-	}
-)
-
-const valueInfo = computed(() => {
-	const exprValue = variableExpression.value
-	if (!exprValue?.startsWith('#{') || !exprValue?.endsWith('}')) {
-		return {
-			type: 'env',
-			name: exprValue || '',
-			value: exprValue || ''
-		}
-	}
-
-	const expr = exprValue.slice(2, -1)
-	const [prefix, ...rest] = expr.split('.')
-	const varName = rest.join('.')
-
-	if (!prefix || !varName) {
-		return {
-			type: 'env',
-			name: exprValue || '',
-			value: exprValue || ''
-		}
-	}
-
-	if (prefix === 'env') {
-		return {
-			type: 'env',
-			name: varName,
-			value: varName
-		}
-	} else if (prefix.startsWith('sys')) {
-		return {
-			type: 'system',
-			name: exprValue,
-			value: varName
-		}
-	} else {
-		if (nodeVars && Array.isArray(nodeVars.value)) {
-			const node = nodeVars.value.find((item) => item.type === 'output' && item.id === prefix)
-			return {
-				type: 'node',
-				nodeType: node?.nodeType || '',
-				nodeName: node?.name || prefix,
-				name: varName,
-				value: varName
-			}
-		} else {
-			return {
-				type: 'node',
-				nodeType: '',
-				nodeName: prefix,
-				name: varName,
-				value: varName
-			}
-		}
-	}
-})
-
-const normalizeTypeLabel = (type?: EnvVarType) => {
-	if (!type) return ''
-	const t = String(type).toLowerCase()
-	if (t === 'string') return 'String'
-	if (t === 'number') return 'Number'
-	if (t === 'boolean') return 'Boolean'
-	if (t === 'object') return 'Object'
-	if (t === 'array') return 'Array'
-	return type as string
-}
-
-/** 过滤变量列表 */
-const filteredVars = computed(() => {
-	const kw = keyword.value.trim().toLowerCase()
-
-	let list: {
-		label: string
-		id: string
-		nodeType?: string
-		list: {
-			name: any
-			type: any
-			typeLabel: string
-		}[]
-	}[] = []
-
-	const nodeOutputs = nodeVars?.value?.filter(
-		(item) => item.type === 'output' && item.list.find((i) => i.name.includes(kw))
-	)
+import PromptEditor from '@/components/PromptEditor/index.vue'
 
-	nodeOutputs?.forEach((item) => {
-		list.push({
-			label: item.name,
-			nodeType: item.nodeType,
-			id: item.id,
-			list: item.list
-				.filter((i) => i.name.includes(kw))
-				.map((i) => ({
-					name: i.name,
-					type: i.type,
-					typeLabel: normalizeTypeLabel(i.type)
-				}))
-		})
-	})
-
-	const envVars = nodeVars?.value?.find((item) => item.type === 'env')?.list
-
-	if (envVars) {
-		list.push({
-			label: 'ENVIRONMENT',
-			id: 'env',
-			list: envVars.map((item) => ({
-				name: item.name,
-				type: item.type,
-				typeLabel: normalizeTypeLabel(item.type)
-			}))
-		})
-	}
-
-	return list
-})
-
-const handleSelect = (variable: { value: string; type: string }) => {
-	// 只更新变量部分,文本不变
-	variableExpression.value = `#{${variable.value}}`
-	syncToOuter()
-	visible.value = false
-	keyword.value = ''
-	emit('change', variable)
-}
-
-const handleKeydown = (event: KeyboardEvent) => {
-	// 输入 / 时弹出变量选择
-	if (event.key === '/') {
-		event.preventDefault()
-		visible.value = true
-		keyword.value = ''
-	}
-}
-
-const focusInput = () => {
-	textInputRef.value?.focus()
-	visible.value = true
-}
+const modelValue = defineModel<string>('modelValue')
 </script>
-
-<style scoped lang="less">
-.var-input {
-	width: 100%;
-}
-
-.var-input__wrapper {
-	width: 100%;
-	display: inline-flex;
-	align-items: center;
-	gap: 4px;
-	border: 1px solid var(--el-input-border-color);
-	border-radius: 8px;
-	padding: 2px 8px;
-	background-color: #fff;
-	cursor: text;
-
-	&:hover {
-		border-color: var(--el-input-hover-border-color);
-	}
-}
-
-.var-input__tag {
-	max-width: 50%;
-	overflow: hidden;
-	text-overflow: ellipsis;
-	white-space: nowrap;
-}
-
-.var-input__text {
-	flex: 1;
-
-	:deep(.el-input__wrapper) {
-		box-shadow: none;
-		border: none;
-		padding-left: 0;
-	}
-}
-
-.var-input__counter {
-	font-size: 10px;
-	color: #999;
-	margin-left: 4px;
-	white-space: nowrap;
-}
-
-.var-input__popover {
-	padding: 8px !important;
-}
-
-.var-input__panel {
-	display: flex;
-	flex-direction: column;
-	gap: 8px;
-}
-
-.var-input__search {
-	:deep(.el-input__wrapper) {
-		box-shadow: none;
-		border-radius: 8px;
-		background-color: #f5f5f5;
-	}
-}
-
-.var-input__list {
-	max-height: 260px;
-	overflow-y: auto;
-}
-
-.var-input__group .var-input__group {
-	margin-top: 8px;
-	border-top: 1px solid #f2f2f2;
-	padding-top: 8px;
-}
-
-.var-input__group-title {
-	font-size: 12px;
-	color: #999;
-	margin-bottom: 4px;
-}
-
-.var-input__item {
-	display: flex;
-	align-items: center;
-	gap: 6px;
-	padding: 4px 6px;
-	border-radius: 6px;
-	cursor: pointer;
-	font-size: 12px;
-	color: #333;
-
-	&:hover {
-		background-color: #f5f7ff;
-	}
-}
-
-.var-input__item-prefix {
-	display: inline-flex;
-	align-items: center;
-	justify-content: center;
-	width: 20px;
-	height: 20px;
-	border-radius: 6px;
-	color: #fff;
-	flex-shrink: 0;
-
-	&.env {
-		background: #6366f1;
-	}
-
-	&.sys {
-		background: #f97316;
-	}
-}
-
-.var-input__item-name {
-	flex: 1;
-	overflow: hidden;
-	text-overflow: ellipsis;
-	white-space: nowrap;
-}
-
-.var-input__item-type {
-	color: #999;
-	flex-shrink: 0;
-}
-
-.var-input__empty {
-	padding: 12px 4px;
-	font-size: 12px;
-	color: #999;
-	text-align: center;
-}
-</style>

+ 1 - 1
apps/web/src/nodes/_base/condition/ConditionItem.vue

@@ -6,7 +6,7 @@
 		<!-- 条件行 -->
 		<div class="flex items-center gap-1">
 			<!-- 左侧变量选择 -->
-			<VarSelect v-model="modelValue.left_value" class="flex-1" />
+			<VarSelect v-model="modelValue.left_value" class="flex-1" placeholder="{x} 设置变量值" />
 			<!-- 运算符选择 -->
 			<el-select
 				v-model="modelValue.comparison_operator"

+ 1 - 0
apps/web/src/nodes/src/condition/setter.vue

@@ -4,6 +4,7 @@
 			v-for="(caseItem, index) in cases"
 			:key="caseItem.id"
 			class="border border-b-0.5px border-b-solid border-gray-200 relative"
+			:style="{ paddingLeft: cases[index]!.conditions?.length < 2 ? '60px' : '0' }"
 		>
 			<div class="absolute text-16px font-bold top-0 left-0">{{ index === 0 ? 'IF' : 'ELIF' }}</div>
 			<Condition

+ 2 - 0
apps/web/src/nodes/src/index.ts

@@ -21,6 +21,7 @@ const loopStartNode = {
 	name: 'custom-loop-start',
 	nodeType: 'custom-loop-start',
 	displayName: '循环开始',
+	hideToolBar: true,
 	schema: {
 		...startNode.schema,
 		nodeType: 'custom-loop-start'
@@ -32,6 +33,7 @@ const iterationStartNode = {
 	name: 'custom-iteration-start',
 	nodeType: 'custom-iteration-start',
 	displayName: '迭代开始',
+	hideToolBar: true,
 	schema: {
 		...startNode.schema,
 		nodeType: 'custom-iteration-start'

+ 4 - 2
packages/workflow/src/components/elements/nodes/CanvasNode.vue

@@ -92,6 +92,8 @@ const outputs = computed(() => {
 	)
 })
 
+const hideToolBar = computed(() => nodeMap?.[props.node?.data.nodeType]?.hideToolBar)
+
 const onUpdate = (prop: Record<string, unknown>) => {
 	emit('update', props.id, prop)
 }
@@ -115,7 +117,7 @@ provide('canvas-node-data', {
 </script>
 
 <template>
-	<div class="relative">
+	<div class="relative w-full h-full">
 		<NodeRenderer v-bind="$attrs" @update="onUpdate" @add-inner-node="emit('add-inner-node')" />
 
 		<template v-for="target in inputs" :key="'handle-inputs-port' + target.index">
@@ -126,6 +128,6 @@ provide('canvas-node-data', {
 			<CanvasHandle v-bind="source" type="source" />
 		</template>
 
-		<CanvasNodeToolBar @delete="onDelete" @run="onRun" :hovered="hovered" />
+		<CanvasNodeToolBar v-if="!hideToolBar" @delete="onDelete" @run="onRun" :hovered="hovered" />
 	</div>
 </template>

+ 0 - 1
packages/workflow/src/components/elements/nodes/CanvasNodeToolBar.vue

@@ -29,7 +29,6 @@ const more = () => {
 	barState.value = !barState.value
 }
 const BarHandleClick = (state: string) => {
-	console.log(state)
 	barState.value = false
 	if (state === 'node-run') {
 		emit('run')

+ 1 - 1
packages/workflow/src/components/elements/nodes/render-types/NodeDefault.vue

@@ -44,7 +44,7 @@ const warningInfo = computed(() => {
 <template>
 	<div
 		:class="nodeClass"
-		class="default-node bg-#fff box-border border-2 border-solid border-#dcdcdc rounded-8px relative"
+		class="default-node w-full h-full bg-#fff box-border border-2 border-solid border-#dcdcdc rounded-8px relative"
 	>
 		<div className="w-full h-full relative flex items-center justify-center">
 			<div

+ 60 - 0
packages/workflow/src/components/elements/nodes/render-types/NodeIcon.vue

@@ -0,0 +1,60 @@
+<script setup lang="ts">
+import { inject, computed, type Ref } from 'vue'
+import { Icon } from '@repo/ui'
+
+import type { NodeProps } from '@vue-flow/core'
+import type { IWorkflowNode, CanvasConnectionPort } from '../../../../Interface'
+
+const node = inject<{
+	props?: NodeProps<IWorkflowNode['data']> & {
+		readOnly?: boolean
+		hovered?: boolean
+		node: IWorkflowNode
+	}
+	inputs?: Ref<CanvasConnectionPort[]>
+	outputs?: Ref<CanvasConnectionPort[]>
+}>('canvas-node-data')
+
+const nodeMap = inject<{ nodeMap: Record<string, any> }>('vueflow')?.nodeMap
+
+const nodeClass = computed(() => {
+	let classes: string[] = []
+	if (node?.props?.selected) {
+		classes.push('ring-6px', 'ring-#e0e2e7')
+	}
+	if (node?.inputs?.value?.length === 0) {
+		classes.push('rounded-l-36px')
+	}
+	if (node?.outputs?.value?.length === 0) {
+		classes.push('rounded-r-36px')
+	}
+
+	return classes
+})
+const nodeData = computed(() => node?.props?.data)
+
+const nodeType = computed(() => nodeMap?.[nodeData.value?.nodeType!])
+</script>
+
+<template>
+	<el-tooltip :content="nodeType?.displayName || '节点标题'">
+		<div :class="nodeClass" class="bg-#fff rounded-8px relative">
+			<div className="w-full h-full relative flex items-center justify-center">
+				<div
+					class="relative flex-shrink-0 flex items-center justify-center w-10 h-10 rounded-lg"
+					:style="{ background: nodeType?.iconColor }"
+				>
+					<div
+						class="absolute inset-0 bg-gradient-to-br from-white/30 to-transparent rounded-lg"
+					></div>
+					<Icon
+						:icon="nodeType?.icon ?? 'lucide:cloud'"
+						color="#ffffff"
+						class="relative z-10"
+						:size="20"
+					/>
+				</div>
+			</div>
+		</div>
+	</el-tooltip>
+</template>

+ 0 - 1
packages/workflow/src/components/elements/nodes/render-types/NodeLoop.vue

@@ -90,7 +90,6 @@ function onAddNode() {
 	<div
 		:class="nodeClass"
 		class="node-loop rounded-12px border-2 border-solid border-#dcdcdc bg-#fafafa overflow-hidden flex flex-col"
-		:style="{ width: width + 'px', height: height + 'px' }"
 	>
 		<!-- 标题栏 -->
 		<div

+ 2 - 0
packages/workflow/src/components/elements/nodes/render-types/NodeRenderer.vue

@@ -3,6 +3,7 @@ import { inject, computed, type Ref } from 'vue'
 import NodeDefault from './NodeDefault.vue'
 import NodeStickyNote from './NodeStickyNote.vue'
 import NodeLoop from './NodeLoop.vue'
+import NodeIcon from './NodeIcon.vue'
 
 import type { NodeProps } from '@vue-flow/core'
 import type { IWorkflowNode, CanvasConnectionPort } from '../../../../Interface'
@@ -27,5 +28,6 @@ defineEmits<{
 		v-bind="$attrs"
 		@add-inner-node="$emit('add-inner-node')"
 	/>
+	<NodeIcon v-else-if="nodeType?.includes('-start')" v-bind="$attrs" />
 	<NodeDefault v-else v-bind="$attrs"> </NodeDefault>
 </template>

+ 412 - 19
pnpm-lock.yaml

@@ -139,12 +139,6 @@ importers:
       '@element-plus/icons-vue':
         specifier: ^2.3.2
         version: 2.3.2(vue@3.5.27(typescript@5.9.3))
-      '@lexical/text':
-        specifier: ^0.41.0
-        version: 0.41.0
-      '@lexical/utils':
-        specifier: ^0.41.0
-        version: 0.41.0
       '@vitejs/plugin-vue-jsx':
         specifier: ^5.1.3
         version: 5.1.3(rolldown-vite@7.2.5(@types/node@25.1.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(yaml@1.10.2))(vue@3.5.27(typescript@5.9.3))
@@ -163,6 +157,9 @@ importers:
       lexical:
         specifier: ^0.41.0
         version: 0.41.0
+      lexical-vue:
+        specifier: ^0.14.1
+        version: 0.14.1(@rsbuild/core@1.7.2)(@rspack/core@1.7.3(@swc/helpers@0.5.18))(rolldown-vite@7.2.5(@types/node@25.1.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(yaml@1.10.2))(vue@3.5.27(typescript@5.9.3))(yjs@13.6.30)
       lodash-es:
         specifier: ^4.17.21
         version: 4.17.23
@@ -2022,14 +2019,67 @@ packages:
     peerDependencies:
       '@langchain/core': '>=0.3.58 <0.4.0'
 
-  '@lexical/selection@0.41.0':
-    resolution: {integrity: sha512-1s7/kNyRzcv5uaTwsUL28NpiisqTf5xZ1zNukLsCN1xY+TWbv9RE9OxIv+748wMm4pxNczQe/UbIBODkbeknLw==}
+  '@lexical/clipboard@0.38.1':
+    resolution: {integrity: sha512-H8NUS4rDzr1eci5UjpN10cmnmcl4RL/XJZCbJos4Z4hSYZMFdQ1taw6EalsTzkyV1I4Ihm6tSDh0rYckaCo5oQ==}
+
+  '@lexical/code@0.38.1':
+    resolution: {integrity: sha512-fzZgX/zzDH4mfuEyhbXG3Pt7CWyqYlOkdLOfDWjg6+n06L6riNbxUOSqVt4fo+jgv4U+izCEOnWgTtiTSc6VHA==}
+
+  '@lexical/dragon@0.38.1':
+    resolution: {integrity: sha512-IVeRm5eYpB1l2PB/8MnsIVdnN14kRFHZOsAU+607emOsVlEpqNdxEj6sraZFl4NbwIdM7sHVzQalIsC7FIbLaw==}
+
+  '@lexical/extension@0.38.1':
+    resolution: {integrity: sha512-26C5dIgtKAr1+IRySINCI9WuD/xIDNz8970IThqy1oAwvmyW8uL3/Q310BfMC/DsICY/xoN9jVG4sPFvN5evBQ==}
+
+  '@lexical/hashtag@0.38.1':
+    resolution: {integrity: sha512-fvTscouFBhq9+OncjoE2eXUuWST6c9EQwuYotTkZuVHkCK91UYAJXncx8zl77iQpdmX4uL88eObBOa/c8AYiSQ==}
+
+  '@lexical/history@0.38.1':
+    resolution: {integrity: sha512-032ngvSQYNMZrj1dc5iExkFW+Z7rgvyy7/bd7Qz4YDTP18lAKAuCMdwLnJtAXmV4+QS1CA/MKYRayG8+rp7mMQ==}
+
+  '@lexical/html@0.38.1':
+    resolution: {integrity: sha512-jzEI3sTeXkLx5A2FIYo5pWyQy2uTRwHYNNmqt+RqBDIDBpGhbJqVJBaGi3F5KKiLSlz//jyrjaDSoelBqUTBDw==}
+
+  '@lexical/link@0.38.1':
+    resolution: {integrity: sha512-somh0aJXbDFGX8tlw06aCTdJRpRckbq7zDEeRzyAvzM8EEj2vVwqTaOnGFO/NQcfECx7rJ952htwAvaSoeJrxA==}
+
+  '@lexical/list@0.38.1':
+    resolution: {integrity: sha512-r5YyViPTD3hx95fikiHMU4ex7krAmOVQcBzSxOvorpAClQW1Ne2zbVovKolNpfgh81p9cT9OiASGDuw1/pfBXQ==}
+
+  '@lexical/mark@0.38.1':
+    resolution: {integrity: sha512-JfyLJ8VQ1uwqAjUOJkT5Vyda1RIhKUVP9kVr1Tb5OsV7VwlNfteF6WBgZcHTAigzB84faOF8JExKc6fR8ATezw==}
+
+  '@lexical/markdown@0.38.1':
+    resolution: {integrity: sha512-yfbIH5f6N8u6LRRYNpbggSKck9GsghqDz6vuATIjuscQtV7/ub46dKjDc+YrQnjrO7ZElP5aAz4Bgf2aoZdSOQ==}
+
+  '@lexical/offset@0.38.1':
+    resolution: {integrity: sha512-4CWGevkFNyZXV8Myekllhw+NQnV+AJsF3gMZfaEbaKQ0A6L4glmEbLe4hCAAXBTtFut/P/MF3XH+a+GLSzbSNQ==}
+
+  '@lexical/overflow@0.38.1':
+    resolution: {integrity: sha512-jGVoo5Prl66PL6dKXV5m3cVJkuQxskOcxQI/eYvbP6nGbJ7kYBNqyk+AOU4nWSNAdFf+B4m1ED8h3Nj7DN/Xww==}
+
+  '@lexical/plain-text@0.38.1':
+    resolution: {integrity: sha512-/jiBI+Tv+NDi7ZzlQJSowG2jia56eAx9wK1gWlVhZjyW6HwBlhWZEqgn8BOyoYVe9gvCHQGT++RP40dGyh4WHA==}
+
+  '@lexical/rich-text@0.38.1':
+    resolution: {integrity: sha512-XwbXDnfsr8Q2JZTbyS4Rc7NW0mhiWWwYSXmqDk8V0swqIneJXcHz5K0GVNDtmCm2qW9mBrGvfYsN4C242it0Sw==}
+
+  '@lexical/selection@0.38.1':
+    resolution: {integrity: sha512-uSsxgkXX1skmmk65fSoKydRZHtpzKjS+AHSO2Lff6QyMmGDVwqZRJoaCR9P0Z7kqaaJfMj3sNSDH1FX3wv+eXA==}
+
+  '@lexical/table@0.38.1':
+    resolution: {integrity: sha512-MLdk9FNicSABNKO2NOQibBePtV9t0WYsrsf2N4+bUH7tD5b4jviVMUBlkbz0k/OZVe7G8Kbp5t4FR+V+iUdxNw==}
 
-  '@lexical/text@0.41.0':
-    resolution: {integrity: sha512-gGA+Anc7ck110EXo4KVKtq6Ui3M7Vz3OpGJ4QE6zJHWW8nV5h273koUGSutAMeoZgRVb6t01Izh3ORoFt/j1CA==}
+  '@lexical/text@0.38.1':
+    resolution: {integrity: sha512-QESpGp+4yeJZei5zkB6i0vgXOcqPiAqVD4DcykluMgH4ThMZ5/6TmQG0B74Gfy4y2c9z9gRRzSHrIC5GQ+fvAA==}
 
-  '@lexical/utils@0.41.0':
-    resolution: {integrity: sha512-Wlsokr5NQCq83D+7kxZ9qs5yQ3dU3Qaf2M+uXxLRoPoDaXqW8xTWZq1+ZFoEzsHzx06QoPa4Vu/40BZR91uQPg==}
+  '@lexical/utils@0.38.1':
+    resolution: {integrity: sha512-3QGEe/3OGAvJptJVlOnMcCrIxOyXw9YutA7hpX9KMEbcJl1NQcH/Qk+hjhgwwxqVof2gTczfLbCVoCPZIZxTXg==}
+
+  '@lexical/yjs@0.38.1':
+    resolution: {integrity: sha512-bB0xQ/PgSYZgIuKhtPwaVMOllRq+y8bHvICBRfubzFli6VKWJQC2kB4psYYim6VxTgDDIDeUSuKZZsCL4GBa8Q==}
+    peerDependencies:
+      yjs: '>=13.5.22'
 
   '@lezer/common@1.5.0':
     resolution: {integrity: sha512-PNGcolp9hr4PJdXR4ix7XtixDrClScvtSCYW3rQG106oVMOOI+jFb+0+J3mbeL/53g1Zd6s0kJzaw6Ri68GmAA==}
@@ -2149,6 +2199,9 @@ packages:
   '@polka/url@1.0.0-next.29':
     resolution: {integrity: sha512-wwQAWhWSuHaag8c4q/KN/vCoeOJYshAIvMQwD4GpSb3OiZklFfvAgmj0VCBBImRpuF/aFgIRzllXlVX93Jevww==}
 
+  '@preact/signals-core@1.14.0':
+    resolution: {integrity: sha512-AowtCcCU/33lFlh1zRFf/u+12rfrhtNakj7UpaGEsmMwUKpKWMVvcktOGcwBBNiB4lWrZWc01LhiyyzVklJyaQ==}
+
   '@quansync/fs@1.0.0':
     resolution: {integrity: sha512-4TJ3DFtlf1L5LDMaM6CanJ/0lckGNtJcMjQ1NAV6zDmA0tEHKZtxNKin8EgPaVX1YzljbxckyT2tJrpQKAtngQ==}
 
@@ -2459,6 +2512,9 @@ packages:
     resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==}
     engines: {node: '>=10.13.0'}
 
+  '@ts-morph/common@0.27.0':
+    resolution: {integrity: sha512-Wf29UqxWDpc+i61k3oIOzcUfQt79PIT9y/MWfAGlrkjg6lBC1hwDECLXPVJAhWjiGbfBCxZd65F/LIZF3+jeJQ==}
+
   '@tsconfig/node10@1.0.12':
     resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==}
 
@@ -3006,6 +3062,24 @@ packages:
       '@vue-flow/core': ^1.23.0
       vue: ^3.3.0
 
+  '@vue-vine/compiler@1.7.27':
+    resolution: {integrity: sha512-aWLc1TaCq1m379hMGAUdgZRe6OR7fTqQllSb3BR9oz3SA3hiIZbcvcH89lFr0Vj48CPj2RC16p8bAVCdZDrXAg==}
+
+  '@vue-vine/rsbuild-plugin@1.7.27':
+    resolution: {integrity: sha512-rO401ayAg8YA+uij2Q2u8ckHz1MQ7gtWNfPEQfQhDhasZI2Q9ypZgm6QdAvZIs7OjUPzKerIJsBBTNyU1vPKAw==}
+    peerDependencies:
+      '@rsbuild/core': '>=1.0.0'
+
+  '@vue-vine/rspack-loader@1.7.27':
+    resolution: {integrity: sha512-5Ls0qxtazPtKzy0q1mnw//Xhtp7uIYjXZZEoY20g1p31ni7JjE7lPn/J7zn1rpscmySBzzaLj/8eZfq4bCf9kg==}
+    peerDependencies:
+      '@rspack/core': ^1.5.8
+
+  '@vue-vine/vite-plugin@1.7.27':
+    resolution: {integrity: sha512-xAKlPNUR2NJ88KiowpjDjBxJ6ro0Z9BQCP5kZMGyhFSRe3fTmr9loeDAemlKSny0LVI8wnvsKGqfIgTbfgi1gQ==}
+    peerDependencies:
+      vite: ^7.0.2
+
   '@vue/babel-helper-vue-transform-on@2.0.1':
     resolution: {integrity: sha512-uZ66EaFbnnZSYqYEyplWvn46GhZ1KuYSThdT68p+am7MgBNbQ3hphTL9L+xSIsWkdktwhPYLwPgVWqo96jDdRA==}
 
@@ -3289,6 +3363,10 @@ packages:
   balanced-match@1.0.2:
     resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
 
+  balanced-match@4.0.4:
+    resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
+    engines: {node: 18 || 20 || >=22}
+
   base64-js@1.5.1:
     resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
 
@@ -3327,6 +3405,10 @@ packages:
   brace-expansion@2.0.2:
     resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==}
 
+  brace-expansion@5.0.4:
+    resolution: {integrity: sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==}
+    engines: {node: 18 || 20 || >=22}
+
   braces@2.3.2:
     resolution: {integrity: sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==}
     engines: {node: '>=0.10.0'}
@@ -3438,6 +3520,9 @@ packages:
     resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
     engines: {node: '>=6'}
 
+  code-block-writer@13.0.3:
+    resolution: {integrity: sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==}
+
   codemirror-shiki@0.2.5:
     resolution: {integrity: sha512-EFtjSYtT0osdjM4HLek5juVu8QVmG7E58Dib91yOO/vhGj+i3b63PyD8UF+PWXRTRaZa6q5Z6J02Iq0M/u0fHQ==}
     peerDependencies:
@@ -3593,6 +3678,11 @@ packages:
     resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==}
     engines: {node: '>= 6'}
 
+  cssesc@3.0.0:
+    resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==}
+    engines: {node: '>=4'}
+    hasBin: true
+
   cssfilter@0.0.10:
     resolution: {integrity: sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==}
 
@@ -4345,6 +4435,10 @@ packages:
     resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==}
     engines: {node: '>=8.6.0'}
 
+  fast-glob@3.3.3:
+    resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==}
+    engines: {node: '>=8.6.0'}
+
   fast-json-stable-stringify@2.1.0:
     resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
 
@@ -4922,6 +5016,9 @@ packages:
     resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==}
     engines: {node: '>=0.10.0'}
 
+  isomorphic.js@0.2.5:
+    resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==}
+
   iterator.prototype@1.1.5:
     resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
     engines: {node: '>= 0.4'}
@@ -5093,9 +5190,22 @@ packages:
     resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
     engines: {node: '>= 0.8.0'}
 
+  lexical-vue@0.14.1:
+    resolution: {integrity: sha512-PGuej4Uaw9vKnu95L8MLQsD5jmAT2LWo73/pwO/CNZTR8H3bk1xvrdX80QD/i0bb2C4xKfl0FWiInmNhfb8CUw==}
+    peerDependencies:
+      vue: ^3.5.0
+
+  lexical@0.38.1:
+    resolution: {integrity: sha512-T2/2pnAZ160kA9nlmkfSpfzke7SuNo1DhMYynTWd36vvZWKEOY0PL3SMSeGSI3TAx2vbO3aHcj/FNrYKAoUiRg==}
+
   lexical@0.41.0:
     resolution: {integrity: sha512-pNIm5+n+hVnJHB9gYPDYsIO5Y59dNaDU9rJmPPsfqQhP2ojKFnUoPbcRnrI9FJLXB14sSumcY8LUw7Sq70TZqA==}
 
+  lib0@0.2.117:
+    resolution: {integrity: sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==}
+    engines: {node: '>=16'}
+    hasBin: true
+
   lightningcss-android-arm64@1.31.1:
     resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==}
     engines: {node: '>= 12.0.0'}
@@ -5170,6 +5280,9 @@ packages:
     resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==}
     engines: {node: '>= 12.0.0'}
 
+  line-column@1.0.2:
+    resolution: {integrity: sha512-Ktrjk5noGYlHsVnYWh62FLVs4hTb8A3e+vucNZMgPeAOITdshMSgv4cCZQeRDjm7+goqmo6+liZwTXo+U3sVww==}
+
   lines-and-columns@1.2.4:
     resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
 
@@ -5220,6 +5333,10 @@ packages:
   lottie-web@5.13.0:
     resolution: {integrity: sha512-+gfBXl6sxXMPe8tKQm7qzLnUy5DUPJPKIyRHwtpCpyUEYjHYRJC/5gjUvdkuO2c3JllrPtHXH5UJJK8LRYl5yQ==}
 
+  lru-cache@11.2.7:
+    resolution: {integrity: sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==}
+    engines: {node: 20 || >=22}
+
   lru-cache@5.1.1:
     resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
 
@@ -5373,6 +5490,9 @@ packages:
     resolution: {integrity: sha512-iuPV41VWKWBIOpBsjoxjDZw8/GbSfZ2mk7N1453bwMrfzdrIk7EzBd+8UVR6rkw67th7xnk9Dytl3J+lHPdxvg==}
     engines: {node: '>=4'}
 
+  merge-source-map@1.1.0:
+    resolution: {integrity: sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==}
+
   merge2@1.4.1:
     resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
     engines: {node: '>= 8'}
@@ -5509,6 +5629,10 @@ packages:
     engines: {node: '>=4'}
     hasBin: true
 
+  minimatch@10.2.4:
+    resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==}
+    engines: {node: 18 || 20 || >=22}
+
   minimatch@3.1.2:
     resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
 
@@ -5878,6 +6002,10 @@ packages:
     peerDependencies:
       postcss: '>4 <9'
 
+  postcss-selector-parser@7.1.1:
+    resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==}
+    engines: {node: '>=4'}
+
   postcss-value-parser@4.2.0:
     resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
 
@@ -6624,6 +6752,9 @@ packages:
     resolution: {integrity: sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==}
     engines: {node: '>=0.12'}
 
+  tiny-invariant@1.3.3:
+    resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
+
   tiny-pinyin@1.3.2:
     resolution: {integrity: sha512-uHNGu4evFt/8eNLldazeAM1M8JrMc1jshhJJfVRARTN3yT8HEEibofeQ7QETWQ5ISBjd6fKtTVBCC/+mGS6FpA==}
 
@@ -6682,6 +6813,9 @@ packages:
     resolution: {integrity: sha512-yF35FCoEOFBzOclSkMNEUbFQZuv89KEQ+5Xz03HrMSGUGB1+r+El+JiGOFwsP4p9RFNzwlrydYoTLvPOuICl9w==}
     engines: {node: '>=18'}
 
+  ts-morph@26.0.0:
+    resolution: {integrity: sha512-ztMO++owQnz8c/gIENcM9XfCEzgoGphTv+nKpYNM1bgsdOVC/jRZuEBf6N+mLLDNg68Kl+GgUZfOySaRiG1/Ug==}
+
   ts-node@10.9.2:
     resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
     hasBin: true
@@ -7065,6 +7199,11 @@ packages:
     peerDependencies:
       typescript: '>=5.0.0'
 
+  vue-vine@1.7.27:
+    resolution: {integrity: sha512-tmkOt9xveCmZQMQXWCNztiKLJvu07tTAcWmYLH44RrVAhVSOHPETn4PieSGPt7qOA721q/KF86vIVzpagz+eNg==}
+    peerDependencies:
+      vue: '>=3.2'
+
   vue@3.5.27:
     resolution: {integrity: sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==}
     peerDependencies:
@@ -7144,6 +7283,10 @@ packages:
     resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
     engines: {node: '>=12'}
 
+  yjs@13.6.30:
+    resolution: {integrity: sha512-vv/9h42eCMC81ZHDFswuu/MKzkl/vyq1BhaNGfHyOonwlG4CJbQF4oiBBJPvfdeCt/PlVDWh7Nov9D34YY09uQ==}
+    engines: {node: '>=16.0.0', npm: '>=8.0.0'}
+
   yn@3.1.1:
     resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
     engines: {node: '>=6'}
@@ -9572,18 +9715,129 @@ snapshots:
     transitivePeerDependencies:
       - ws
 
-  '@lexical/selection@0.41.0':
+  '@lexical/clipboard@0.38.1':
+    dependencies:
+      '@lexical/html': 0.38.1
+      '@lexical/list': 0.38.1
+      '@lexical/selection': 0.38.1
+      '@lexical/utils': 0.38.1
+      lexical: 0.38.1
+
+  '@lexical/code@0.38.1':
     dependencies:
-      lexical: 0.41.0
+      '@lexical/utils': 0.38.1
+      lexical: 0.38.1
+      prismjs: 1.30.0
+
+  '@lexical/dragon@0.38.1':
+    dependencies:
+      '@lexical/extension': 0.38.1
+      lexical: 0.38.1
+
+  '@lexical/extension@0.38.1':
+    dependencies:
+      '@lexical/utils': 0.38.1
+      '@preact/signals-core': 1.14.0
+      lexical: 0.38.1
+
+  '@lexical/hashtag@0.38.1':
+    dependencies:
+      '@lexical/text': 0.38.1
+      '@lexical/utils': 0.38.1
+      lexical: 0.38.1
+
+  '@lexical/history@0.38.1':
+    dependencies:
+      '@lexical/extension': 0.38.1
+      '@lexical/utils': 0.38.1
+      lexical: 0.38.1
+
+  '@lexical/html@0.38.1':
+    dependencies:
+      '@lexical/selection': 0.38.1
+      '@lexical/utils': 0.38.1
+      lexical: 0.38.1
+
+  '@lexical/link@0.38.1':
+    dependencies:
+      '@lexical/extension': 0.38.1
+      '@lexical/utils': 0.38.1
+      lexical: 0.38.1
+
+  '@lexical/list@0.38.1':
+    dependencies:
+      '@lexical/extension': 0.38.1
+      '@lexical/selection': 0.38.1
+      '@lexical/utils': 0.38.1
+      lexical: 0.38.1
 
-  '@lexical/text@0.41.0':
+  '@lexical/mark@0.38.1':
     dependencies:
-      lexical: 0.41.0
+      '@lexical/utils': 0.38.1
+      lexical: 0.38.1
 
-  '@lexical/utils@0.41.0':
+  '@lexical/markdown@0.38.1':
     dependencies:
-      '@lexical/selection': 0.41.0
-      lexical: 0.41.0
+      '@lexical/code': 0.38.1
+      '@lexical/link': 0.38.1
+      '@lexical/list': 0.38.1
+      '@lexical/rich-text': 0.38.1
+      '@lexical/text': 0.38.1
+      '@lexical/utils': 0.38.1
+      lexical: 0.38.1
+
+  '@lexical/offset@0.38.1':
+    dependencies:
+      lexical: 0.38.1
+
+  '@lexical/overflow@0.38.1':
+    dependencies:
+      lexical: 0.38.1
+
+  '@lexical/plain-text@0.38.1':
+    dependencies:
+      '@lexical/clipboard': 0.38.1
+      '@lexical/dragon': 0.38.1
+      '@lexical/selection': 0.38.1
+      '@lexical/utils': 0.38.1
+      lexical: 0.38.1
+
+  '@lexical/rich-text@0.38.1':
+    dependencies:
+      '@lexical/clipboard': 0.38.1
+      '@lexical/dragon': 0.38.1
+      '@lexical/selection': 0.38.1
+      '@lexical/utils': 0.38.1
+      lexical: 0.38.1
+
+  '@lexical/selection@0.38.1':
+    dependencies:
+      lexical: 0.38.1
+
+  '@lexical/table@0.38.1':
+    dependencies:
+      '@lexical/clipboard': 0.38.1
+      '@lexical/extension': 0.38.1
+      '@lexical/utils': 0.38.1
+      lexical: 0.38.1
+
+  '@lexical/text@0.38.1':
+    dependencies:
+      lexical: 0.38.1
+
+  '@lexical/utils@0.38.1':
+    dependencies:
+      '@lexical/list': 0.38.1
+      '@lexical/selection': 0.38.1
+      '@lexical/table': 0.38.1
+      lexical: 0.38.1
+
+  '@lexical/yjs@0.38.1(yjs@13.6.30)':
+    dependencies:
+      '@lexical/offset': 0.38.1
+      '@lexical/selection': 0.38.1
+      lexical: 0.38.1
+      yjs: 13.6.30
 
   '@lezer/common@1.5.0': {}
 
@@ -9762,6 +10016,8 @@ snapshots:
 
   '@polka/url@1.0.0-next.29': {}
 
+  '@preact/signals-core@1.14.0': {}
+
   '@quansync/fs@1.0.0':
     dependencies:
       quansync: 1.0.0
@@ -10050,6 +10306,12 @@ snapshots:
 
   '@trysound/sax@0.2.0': {}
 
+  '@ts-morph/common@0.27.0':
+    dependencies:
+      fast-glob: 3.3.3
+      minimatch: 10.2.4
+      path-browserify: 1.0.1
+
   '@tsconfig/node10@1.0.12': {}
 
   '@tsconfig/node12@1.0.11': {}
@@ -10809,6 +11071,41 @@ snapshots:
       '@vue-flow/core': 1.48.1(vue@3.5.27(typescript@5.9.3))
       vue: 3.5.27(typescript@5.9.3)
 
+  '@vue-vine/compiler@1.7.27':
+    dependencies:
+      '@babel/parser': 7.28.6
+      '@babel/types': 7.28.6
+      '@vue/compiler-dom': 3.5.27
+      '@vue/compiler-ssr': 3.5.27
+      '@vue/shared': 3.5.27
+      get-tsconfig: 4.13.0
+      hash-sum: 2.0.0
+      line-column: 1.0.2
+      lru-cache: 11.2.7
+      magic-string: 0.30.21
+      merge-source-map: 1.1.0
+      postcss: 8.5.6
+      postcss-selector-parser: 7.1.1
+      ts-morph: 26.0.0
+
+  '@vue-vine/rsbuild-plugin@1.7.27(@rsbuild/core@1.7.2)(@rspack/core@1.7.3(@swc/helpers@0.5.18))':
+    dependencies:
+      '@rsbuild/core': 1.7.2
+      '@vue-vine/compiler': 1.7.27
+      '@vue-vine/rspack-loader': 1.7.27(@rspack/core@1.7.3(@swc/helpers@0.5.18))
+    transitivePeerDependencies:
+      - '@rspack/core'
+
+  '@vue-vine/rspack-loader@1.7.27(@rspack/core@1.7.3(@swc/helpers@0.5.18))':
+    dependencies:
+      '@rspack/core': 1.7.3(@swc/helpers@0.5.18)
+      '@vue-vine/compiler': 1.7.27
+
+  '@vue-vine/vite-plugin@1.7.27(rolldown-vite@7.2.5(@types/node@25.1.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(yaml@1.10.2))':
+    dependencies:
+      '@vue-vine/compiler': 1.7.27
+      vite: rolldown-vite@7.2.5(@types/node@25.1.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(yaml@1.10.2)
+
   '@vue/babel-helper-vue-transform-on@2.0.1': {}
 
   '@vue/babel-plugin-jsx@2.0.1(@babel/core@7.28.6)':
@@ -11187,6 +11484,8 @@ snapshots:
 
   balanced-match@1.0.2: {}
 
+  balanced-match@4.0.4: {}
+
   base64-js@1.5.1: {}
 
   base@0.11.2:
@@ -11224,6 +11523,10 @@ snapshots:
     dependencies:
       balanced-match: 1.0.2
 
+  brace-expansion@5.0.4:
+    dependencies:
+      balanced-match: 4.0.4
+
   braces@2.3.2:
     dependencies:
       arr-flatten: 1.1.0
@@ -11356,6 +11659,8 @@ snapshots:
 
   clsx@2.1.1: {}
 
+  code-block-writer@13.0.3: {}
+
   codemirror-shiki@0.2.5(@codemirror/state@6.5.4)(@codemirror/view@6.38.8)(shiki@3.21.0):
     dependencies:
       '@codemirror/state': 6.5.4
@@ -11495,6 +11800,8 @@ snapshots:
 
   css-what@6.2.2: {}
 
+  cssesc@3.0.0: {}
+
   cssfilter@0.0.10: {}
 
   csso@4.2.0:
@@ -12507,6 +12814,14 @@ snapshots:
       merge2: 1.4.1
       micromatch: 4.0.8
 
+  fast-glob@3.3.3:
+    dependencies:
+      '@nodelib/fs.stat': 2.0.5
+      '@nodelib/fs.walk': 1.2.8
+      glob-parent: 5.1.2
+      merge2: 1.4.1
+      micromatch: 4.0.8
+
   fast-json-stable-stringify@2.1.0: {}
 
   fast-levenshtein@2.0.6: {}
@@ -13149,6 +13464,8 @@ snapshots:
 
   isobject@3.0.1: {}
 
+  isomorphic.js@0.2.5: {}
+
   iterator.prototype@1.1.5:
     dependencies:
       define-data-property: 1.1.4
@@ -13296,8 +13613,42 @@ snapshots:
       prelude-ls: 1.2.1
       type-check: 0.4.0
 
+  lexical-vue@0.14.1(@rsbuild/core@1.7.2)(@rspack/core@1.7.3(@swc/helpers@0.5.18))(rolldown-vite@7.2.5(@types/node@25.1.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(yaml@1.10.2))(vue@3.5.27(typescript@5.9.3))(yjs@13.6.30):
+    dependencies:
+      '@lexical/dragon': 0.38.1
+      '@lexical/extension': 0.38.1
+      '@lexical/hashtag': 0.38.1
+      '@lexical/history': 0.38.1
+      '@lexical/html': 0.38.1
+      '@lexical/link': 0.38.1
+      '@lexical/list': 0.38.1
+      '@lexical/mark': 0.38.1
+      '@lexical/markdown': 0.38.1
+      '@lexical/overflow': 0.38.1
+      '@lexical/plain-text': 0.38.1
+      '@lexical/rich-text': 0.38.1
+      '@lexical/table': 0.38.1
+      '@lexical/text': 0.38.1
+      '@lexical/utils': 0.38.1
+      '@lexical/yjs': 0.38.1(yjs@13.6.30)
+      lexical: 0.38.1
+      tiny-invariant: 1.3.3
+      vue: 3.5.27(typescript@5.9.3)
+      vue-vine: 1.7.27(@rsbuild/core@1.7.2)(@rspack/core@1.7.3(@swc/helpers@0.5.18))(rolldown-vite@7.2.5(@types/node@25.1.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(yaml@1.10.2))(vue@3.5.27(typescript@5.9.3))
+    transitivePeerDependencies:
+      - '@rsbuild/core'
+      - '@rspack/core'
+      - vite
+      - yjs
+
+  lexical@0.38.1: {}
+
   lexical@0.41.0: {}
 
+  lib0@0.2.117:
+    dependencies:
+      isomorphic.js: 0.2.5
+
   lightningcss-android-arm64@1.31.1:
     optional: true
 
@@ -13347,6 +13698,11 @@ snapshots:
       lightningcss-win32-arm64-msvc: 1.31.1
       lightningcss-win32-x64-msvc: 1.31.1
 
+  line-column@1.0.2:
+    dependencies:
+      isarray: 1.0.0
+      isobject: 2.1.0
+
   lines-and-columns@1.2.4: {}
 
   linkify-it@5.0.0:
@@ -13393,6 +13749,8 @@ snapshots:
 
   lottie-web@5.13.0: {}
 
+  lru-cache@11.2.7: {}
+
   lru-cache@5.1.1:
     dependencies:
       yallist: 3.1.1
@@ -13660,6 +14018,10 @@ snapshots:
     dependencies:
       is-plain-obj: 1.1.0
 
+  merge-source-map@1.1.0:
+    dependencies:
+      source-map: 0.6.1
+
   merge2@1.4.1: {}
 
   mermaid@11.12.0:
@@ -13991,6 +14353,10 @@ snapshots:
   mime@1.6.0:
     optional: true
 
+  minimatch@10.2.4:
+    dependencies:
+      brace-expansion: 5.0.4
+
   minimatch@3.1.2:
     dependencies:
       brace-expansion: 1.1.12
@@ -14366,6 +14732,11 @@ snapshots:
     dependencies:
       postcss: 5.2.18
 
+  postcss-selector-parser@7.1.1:
+    dependencies:
+      cssesc: 3.0.0
+      util-deprecate: 1.0.2
+
   postcss-value-parser@4.2.0: {}
 
   postcss@5.2.18:
@@ -15327,6 +15698,8 @@ snapshots:
       es5-ext: 0.10.64
       next-tick: 1.1.0
 
+  tiny-invariant@1.3.3: {}
+
   tiny-pinyin@1.3.2: {}
 
   tinyexec@1.0.2: {}
@@ -15382,6 +15755,11 @@ snapshots:
 
   ts-md5@2.0.1: {}
 
+  ts-morph@26.0.0:
+    dependencies:
+      '@ts-morph/common': 0.27.0
+      code-block-writer: 13.0.3
+
   ts-node@10.9.2(@types/node@18.19.130)(typescript@5.9.3):
     dependencies:
       '@cspotcode/source-map-support': 0.8.1
@@ -15905,6 +16283,17 @@ snapshots:
       '@vue/language-core': 3.2.2
       typescript: 5.9.3
 
+  vue-vine@1.7.27(@rsbuild/core@1.7.2)(@rspack/core@1.7.3(@swc/helpers@0.5.18))(rolldown-vite@7.2.5(@types/node@25.1.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(yaml@1.10.2))(vue@3.5.27(typescript@5.9.3)):
+    dependencies:
+      '@vue-vine/rsbuild-plugin': 1.7.27(@rsbuild/core@1.7.2)(@rspack/core@1.7.3(@swc/helpers@0.5.18))
+      '@vue-vine/rspack-loader': 1.7.27(@rspack/core@1.7.3(@swc/helpers@0.5.18))
+      '@vue-vine/vite-plugin': 1.7.27(rolldown-vite@7.2.5(@types/node@25.1.0)(esbuild@0.27.2)(jiti@2.6.1)(less@4.5.1)(yaml@1.10.2))
+      vue: 3.5.27(typescript@5.9.3)
+    transitivePeerDependencies:
+      - '@rsbuild/core'
+      - '@rspack/core'
+      - vite
+
   vue@3.5.27(typescript@5.9.2):
     dependencies:
       '@vue/compiler-dom': 3.5.27
@@ -16016,6 +16405,10 @@ snapshots:
       y18n: 5.0.8
       yargs-parser: 21.1.1
 
+  yjs@13.6.30:
+    dependencies:
+      lib0: 0.2.117
+
   yn@3.1.1: {}
 
   yocto-queue@0.1.0: {}