1
+ <template >
2
+ <div :style =" data.style" class =" node-container tool-node" >
3
+ <!-- Node label -->
4
+ <div class =" node-label" >
5
+ <input
6
+ v-model =" label"
7
+ @change =" updateNodeData"
8
+ class =" label-input"
9
+ :style =" data.labelStyle"
10
+ />
11
+ </div >
12
+
13
+ <!-- Checkbox to enable/disable updating input from a connected source -->
14
+ <div class =" input-field" >
15
+ <input
16
+ type =" checkbox"
17
+ :id =" `${data.id}-update-from-source`"
18
+ v-model =" updateFromSource"
19
+ @change =" updateNodeData"
20
+ />
21
+ <label :for =" `${data.id}-update-from-source`" class =" input-label" >
22
+ Update Input from Source
23
+ </label >
24
+ </div >
25
+
26
+ <!-- Input for Paths (comma separated) -->
27
+ <div class =" input-field" >
28
+ <label :for =" `${data.id}-paths`" class =" input-label" >
29
+ Paths (comma separated):
30
+ </label >
31
+ <input
32
+ type =" text"
33
+ :id =" `${data.id}-paths`"
34
+ v-model =" paths"
35
+ @change =" updateNodeData"
36
+ class =" input-text"
37
+ />
38
+ </div >
39
+
40
+ <!-- Input for Types (comma separated) -->
41
+ <div class =" input-field" >
42
+ <label :for =" `${data.id}-types`" class =" input-label" >
43
+ Types (comma separated):
44
+ </label >
45
+ <input
46
+ type =" text"
47
+ :id =" `${data.id}-types`"
48
+ v-model =" types"
49
+ @change =" updateNodeData"
50
+ class =" input-text"
51
+ />
52
+ </div >
53
+
54
+ <!-- Checkbox for Recursive -->
55
+ <div class =" input-field" >
56
+ <input
57
+ type =" checkbox"
58
+ :id =" `${data.id}-recursive`"
59
+ v-model =" recursive"
60
+ @change =" updateNodeData"
61
+ />
62
+ <label :for =" `${data.id}-recursive`" class =" input-label" >
63
+ Recursive
64
+ </label >
65
+ </div >
66
+
67
+ <!-- Input for Ignore Pattern -->
68
+ <div class =" input-field" >
69
+ <label :for =" `${data.id}-ignore`" class =" input-label" >
70
+ Ignore Pattern:
71
+ </label >
72
+ <input
73
+ type =" text"
74
+ :id =" `${data.id}-ignore`"
75
+ v-model =" ignorePattern"
76
+ @change =" updateNodeData"
77
+ class =" input-text"
78
+ />
79
+ </div >
80
+
81
+ <!-- Node connection handles -->
82
+ <Handle v-if =" data.hasInputs" type =" target" position =" left" id =" input" />
83
+ <Handle v-if =" data.hasOutputs" type =" source" position =" right" id =" output" />
84
+ </div >
85
+ </template >
86
+
87
+ <script setup>
88
+ import { ref , computed , onMounted } from ' vue'
89
+ import { Handle , useVueFlow } from ' @vue-flow/core'
90
+
91
+ const { getEdges , findNode } = useVueFlow ()
92
+
93
+ // Define the component props; note that we set default node data values for our inputs.
94
+ const props = defineProps ({
95
+ id: {
96
+ type: String ,
97
+ required: false ,
98
+ default: ' RepoConcat_0' ,
99
+ },
100
+ data: {
101
+ type: Object ,
102
+ required: false ,
103
+ default : () => ({
104
+ style: {},
105
+ labelStyle: {},
106
+ type: ' RepoConcatNode' ,
107
+ inputs: {
108
+ // Defaults: a single path and a comma‐separated list of file types
109
+ paths: " ." ,
110
+ types: " .go, .md, .vue, .js, .css, .html" ,
111
+ recursive: false ,
112
+ ignorePattern: " "
113
+ },
114
+ outputs: {},
115
+ hasInputs: true ,
116
+ hasOutputs: true ,
117
+ updateFromSource: true ,
118
+ }),
119
+ },
120
+ })
121
+
122
+ const emit = defineEmits ([' update:data' ])
123
+
124
+ // Local reactive variable for the update-from-source checkbox
125
+ const updateFromSource = ref (props .data .updateFromSource )
126
+
127
+ // Mount the run method on the node data so that VueFlow can invoke it.
128
+ onMounted (() => {
129
+ if (! props .data .run ) {
130
+ props .data .run = run
131
+ }
132
+ })
133
+
134
+ /**
135
+ * run() gathers input parameters either from connected nodes (if any and enabled)
136
+ * or from this node’s own inputs, sends a POST request to /api/repoconcat,
137
+ * and then updates the node’s outputs with the returned concatenated result.
138
+ */
139
+ async function run () {
140
+ try {
141
+ // Clear previous output
142
+ props .data .outputs .result = ' '
143
+
144
+ // Check for connected source nodes (to optionally update input parameters)
145
+ const connectedSources = getEdges .value
146
+ .filter (edge => edge .target === props .id )
147
+ .map (edge => edge .source )
148
+
149
+ let payload
150
+ if (connectedSources .length > 0 && updateFromSource .value ) {
151
+ const sourceData = findNode (connectedSources[0 ]).data .outputs .result .output
152
+ console .log (' Connected source data:' , sourceData)
153
+ try {
154
+ payload = JSON .parse (sourceData)
155
+ } catch (err) {
156
+ console .error (' Error parsing JSON from connected node:' , err)
157
+ props .data .outputs .result = { error: ' Invalid JSON from connected node' }
158
+ return { error: ' Invalid JSON from connected node' }
159
+ }
160
+ } else {
161
+ // Use the values entered in this node's input fields.
162
+ const pathsInput = props .data .inputs .paths
163
+ const typesInput = props .data .inputs .types
164
+ const recursiveValue = props .data .inputs .recursive
165
+ const ignorePatternValue = props .data .inputs .ignorePattern
166
+
167
+ // Convert comma-separated strings into arrays.
168
+ const pathsArray = pathsInput .split (' ,' ).map (s => s .trim ()).filter (s => s)
169
+ const typesArray = typesInput .split (' ,' ).map (s => s .trim ()).filter (s => s)
170
+
171
+ payload = {
172
+ paths: pathsArray,
173
+ types: typesArray,
174
+ recursive: recursiveValue,
175
+ ignorePattern: ignorePatternValue,
176
+ }
177
+ }
178
+
179
+ // POST the parameters to the /api/repoconcat endpoint.
180
+ const response = await fetch (' http://localhost:8080/api/repoconcat' , {
181
+ method: ' POST' ,
182
+ headers: { ' Content-Type' : ' application/json' },
183
+ body: JSON .stringify (payload),
184
+ })
185
+
186
+ if (! response .ok ) {
187
+ const errorMsg = await response .text ()
188
+ console .error (' Error response from server:' , errorMsg)
189
+ props .data .outputs .result = { error: errorMsg }
190
+ return { error: errorMsg }
191
+ }
192
+
193
+ // The API returns plain text – the concatenated output.
194
+ const result = await response .text ()
195
+ console .log (' RepoConcat run result:' , result)
196
+
197
+ props .data .outputs = {
198
+ result: {
199
+ output: result,
200
+ },
201
+ }
202
+
203
+ updateNodeData ()
204
+ return { response, result }
205
+ } catch (error) {
206
+ console .error (' Error in run():' , error)
207
+ props .data .outputs .result = { error: error .message }
208
+ return { error }
209
+ }
210
+ }
211
+
212
+ // Computed property for the node label.
213
+ const label = computed ({
214
+ get : () => props .data .type ,
215
+ set : (value ) => {
216
+ props .data .type = value
217
+ updateNodeData ()
218
+ },
219
+ })
220
+
221
+ // Computed property for the "paths" input.
222
+ const paths = computed ({
223
+ get : () => props .data .inputs ? .paths || ' ' ,
224
+ set: (value ) => {
225
+ props .data .inputs .paths = value
226
+ updateNodeData ()
227
+ },
228
+ })
229
+
230
+ // Computed property for the "types" input.
231
+ const types = computed ({
232
+ get : () => props .data .inputs ? .types || ' ' ,
233
+ set: (value ) => {
234
+ props .data .inputs .types = value
235
+ updateNodeData ()
236
+ },
237
+ })
238
+
239
+ // Computed property for the "recursive" checkbox.
240
+ const recursive = computed ({
241
+ get : () => props .data .inputs ? .recursive || false ,
242
+ set: (value ) => {
243
+ props .data .inputs .recursive = value
244
+ updateNodeData ()
245
+ },
246
+ })
247
+
248
+ // Computed property for the "ignorePattern" input.
249
+ const ignorePattern = computed ({
250
+ get : () => props .data .inputs ? .ignorePattern || ' ' ,
251
+ set: (value ) => {
252
+ props .data .inputs .ignorePattern = value
253
+ updateNodeData ()
254
+ },
255
+ })
256
+
257
+ // Emit updated node data to VueFlow.
258
+ function updateNodeData () {
259
+ const updatedData = {
260
+ ... props .data ,
261
+ inputs: {
262
+ paths: paths .value ,
263
+ types: types .value ,
264
+ recursive: recursive .value ,
265
+ ignorePattern: ignorePattern .value ,
266
+ },
267
+ outputs: props .data .outputs ,
268
+ updateFromSource: updateFromSource .value ,
269
+ }
270
+ emit (' update:data' , { id: props .id , data: updatedData })
271
+ }
272
+ </script >
273
+
274
+ <style scoped>
275
+ .tool-node {
276
+ --node-border-color : #777 !important ;
277
+ --node-bg-color : #1e1e1e !important ;
278
+ --node-text-color : #eee ;
279
+ }
280
+
281
+ .node-label {
282
+ color : var (--node-text-color );
283
+ font-size : 16px ;
284
+ text-align : center ;
285
+ margin-bottom : 10px ;
286
+ font-weight : bold ;
287
+ }
288
+
289
+ .input-field {
290
+ margin-bottom : 8px ;
291
+ }
292
+
293
+ /* Styling for standard text inputs */
294
+ .input-text {
295
+ background-color : #333 ;
296
+ border : 1px solid #666 ;
297
+ color : #eee ;
298
+ padding : 4px ;
299
+ font-size : 12px ;
300
+ width : calc (100% - 8px );
301
+ box-sizing : border-box ;
302
+ }
303
+
304
+ /* You can keep using your existing label-input styling from your other components */
305
+ .label-input {
306
+ background-color : #333 ;
307
+ border : 1px solid #666 ;
308
+ color : #eee ;
309
+ padding : 4px ;
310
+ font-size : 16px ;
311
+ width : 100% ;
312
+ box-sizing : border-box ;
313
+ }
314
+ </style >
315
+
0 commit comments