Skip to content

Commit 24d99a3

Browse files
committed
feat: [Intelligent Agent] Workflow intelligent agent adds workflow component search/location function
1 parent 7a0caeb commit 24d99a3

File tree

2 files changed

+210
-0
lines changed

2 files changed

+210
-0
lines changed
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
<template>
2+
<div>
3+
<!-- 搜索遮罩层 -->
4+
<Teleport to="body">
5+
<div v-if="showSearch" class="search-mask" @click.self="closeSearch">
6+
<div class="search-container">
7+
<el-input
8+
ref="searchInputRef"
9+
v-model="searchText"
10+
placeholder="搜索..."
11+
:prefix-icon="Search"
12+
clearable
13+
@keyup.enter="handleSearch"
14+
@keyup.esc="closeSearch"
15+
>
16+
<template #append>
17+
<el-button @click="closeSearch">取消</el-button>
18+
</template>
19+
</el-input>
20+
</div>
21+
</div>
22+
</Teleport>
23+
</div>
24+
</template>
25+
26+
<script setup lang="ts">
27+
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
28+
import { Search } from '@element-plus/icons-vue'
29+
30+
// Props定义
31+
interface Props {
32+
onSearch?: (keyword: string) => void // 搜索回调
33+
}
34+
35+
const props = withDefaults(defineProps<Props>(), {
36+
useElementPlus: false,
37+
onSearch: undefined,
38+
})
39+
40+
// 状态
41+
const showSearch = ref(false)
42+
const searchText = ref('')
43+
const searchInputRef = ref<any>(null)
44+
const nativeInputRef = ref<HTMLInputElement | null>(null)
45+
46+
// 快捷键处理
47+
const handleKeyDown = (e: KeyboardEvent) => {
48+
// Ctrl+F 或 Cmd+F (Mac)
49+
if ((e.ctrlKey || e.metaKey) && e.key === 'f') {
50+
e.preventDefault() // 阻止浏览器默认搜索
51+
openSearch()
52+
}
53+
54+
// 按ESC关闭
55+
if (e.key === 'Escape' && showSearch.value) {
56+
closeSearch()
57+
}
58+
}
59+
60+
// 打开搜索
61+
const openSearch = () => {
62+
showSearch.value = true
63+
searchText.value = ''
64+
65+
nextTick(() => {
66+
searchInputRef.value?.focus()
67+
})
68+
}
69+
70+
// 关闭搜索
71+
const closeSearch = () => {
72+
showSearch.value = false
73+
searchText.value = ''
74+
}
75+
76+
// 执行搜索
77+
const handleSearch = () => {
78+
if (searchText.value.trim()) {
79+
props.onSearch?.(searchText.value)
80+
}
81+
}
82+
83+
// 生命周期
84+
onMounted(() => {
85+
window.addEventListener('keydown', handleKeyDown)
86+
})
87+
88+
onUnmounted(() => {
89+
window.removeEventListener('keydown', handleKeyDown)
90+
})
91+
</script>
92+
93+
<style scoped>
94+
.search-mask {
95+
position: fixed;
96+
top: 0;
97+
left: 0;
98+
right: 0;
99+
bottom: 0;
100+
background: rgba(0, 0, 0, 0.3);
101+
display: flex;
102+
justify-content: center;
103+
z-index: 9999;
104+
padding-top: 20vh;
105+
}
106+
107+
.search-container {
108+
width: 500px;
109+
max-width: 90%;
110+
animation: slideDown 0.2s ease;
111+
}
112+
113+
/* 原生输入框样式 */
114+
.native-search {
115+
display: flex;
116+
gap: 8px;
117+
background: white;
118+
padding: 16px;
119+
border-radius: 8px;
120+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
121+
}
122+
123+
.native-search input {
124+
flex: 1;
125+
padding: 10px 12px;
126+
border: 1px solid #dcdfe6;
127+
border-radius: 4px;
128+
font-size: 14px;
129+
outline: none;
130+
}
131+
132+
.native-search input:focus {
133+
border-color: #409eff;
134+
}
135+
136+
.native-search button {
137+
padding: 0 16px;
138+
background: white;
139+
border: 1px solid #dcdfe6;
140+
border-radius: 4px;
141+
cursor: pointer;
142+
transition: all 0.2s;
143+
}
144+
145+
.native-search button:hover {
146+
border-color: #409eff;
147+
color: #409eff;
148+
}
149+
150+
.content {
151+
padding: 20px;
152+
}
153+
154+
.item {
155+
padding: 8px;
156+
border-bottom: 1px solid #eee;
157+
}
158+
159+
@keyframes slideDown {
160+
from {
161+
opacity: 0;
162+
transform: translateY(-20px);
163+
}
164+
to {
165+
opacity: 1;
166+
transform: translateY(0);
167+
}
168+
}
169+
</style>

ui/src/workflow/index.vue

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
<!-- 辅助工具栏 -->
44
<Control class="workflow-control" v-if="lf" :lf="lf"></Control>
55
<TeleportContainer :flow-id="flowId" />
6+
<NodeSearch :on-search="onSearch"></NodeSearch>
67
</template>
78
<script setup lang="ts">
89
import LogicFlow from '@logicflow/core'
@@ -17,6 +18,8 @@ import { initDefaultShortcut } from '@/workflow/common/shortcut'
1718
import Dagre from '@/workflow/plugins/dagre'
1819
import { disconnectAll, getTeleport } from '@/workflow/common/teleport'
1920
import { WorkflowMode } from '@/enums/application'
21+
import { MsgSuccess, MsgWarning } from '@/utils/message'
22+
import NodeSearch from '@/workflow/common/NodeSearch.vue'
2023
const nodes: any = import.meta.glob('./nodes/**/index.ts', { eager: true })
2124
const workflow_mode = inject('workflowMode') || WorkflowMode.Application
2225
const loop_workflow_mode = inject('loopWorkflowMode') || WorkflowMode.ApplicationLoop
@@ -49,6 +52,44 @@ onUnmounted(() => {
4952
const render = (data: any) => {
5053
lf.value.render(data)
5154
}
55+
const searchQueue: Array<string> = []
56+
const selectNode = (node: any) => {
57+
lf.value.graphModel.selectNodeById(node.id)
58+
lf.value.graphModel.transformModel.focusOn(
59+
node.x,
60+
node.y,
61+
lf.value.container.clientWidth,
62+
lf.value.container.clientHeight,
63+
)
64+
searchQueue.push(node.id)
65+
}
66+
const onSearch = (kw: string) => {
67+
const graph_data = lf.value.getGraphData()
68+
for (let index = 0; index < graph_data.nodes.length; index++) {
69+
const node = graph_data.nodes[index]
70+
let firstNode = null
71+
if (node.properties.stepName.includes(kw)) {
72+
if (!firstNode) {
73+
firstNode = node
74+
}
75+
76+
if (!searchQueue.includes(node.id)) {
77+
selectNode(node)
78+
break
79+
}
80+
}
81+
if (index === graph_data.nodes.length - 1) {
82+
searchQueue.length = 0
83+
if (firstNode) {
84+
selectNode(firstNode)
85+
} else {
86+
lf.value.graphModel.clearSelectElements()
87+
MsgWarning('不存在的节点')
88+
}
89+
}
90+
}
91+
}
92+
5293
const renderGraphData = (data?: any) => {
5394
const container: any = document.querySelector('#container')
5495
if (container) {

0 commit comments

Comments
 (0)