@marktoflow/gui 2.0.0-alpha.5 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.md +48 -180
  2. package/client.log +0 -0
  3. package/dist/client/assets/index-DQeR1ew6.css +1 -0
  4. package/dist/client/assets/index-LbIVPHbD.js +833 -0
  5. package/dist/client/assets/index-LbIVPHbD.js.map +1 -0
  6. package/dist/client/index.html +2 -2
  7. package/dist/client/marktoflow-logo.png +0 -0
  8. package/dist/server/index.js +31 -5
  9. package/dist/server/index.js.map +1 -1
  10. package/dist/server/routes/admin.js +95 -0
  11. package/dist/server/routes/admin.js.map +1 -0
  12. package/dist/server/routes/ai.js +2 -2
  13. package/dist/server/routes/ai.js.map +1 -1
  14. package/dist/server/routes/collaboration.js +104 -0
  15. package/dist/server/routes/collaboration.js.map +1 -0
  16. package/dist/server/routes/execute.js +181 -14
  17. package/dist/server/routes/execute.js.map +1 -1
  18. package/dist/server/routes/form.js +160 -0
  19. package/dist/server/routes/form.js.map +1 -0
  20. package/dist/server/routes/settings.js +90 -0
  21. package/dist/server/routes/settings.js.map +1 -0
  22. package/dist/server/routes/templates.js +106 -0
  23. package/dist/server/routes/templates.js.map +1 -0
  24. package/dist/server/routes/versions.js +101 -0
  25. package/dist/server/routes/versions.js.map +1 -0
  26. package/dist/server/services/AIService.js +85 -2
  27. package/dist/server/services/AIService.js.map +1 -1
  28. package/dist/server/services/ExecutionManager.js +571 -0
  29. package/dist/server/services/ExecutionManager.js.map +1 -0
  30. package/dist/server/services/VersionService.js +65 -0
  31. package/dist/server/services/VersionService.js.map +1 -0
  32. package/dist/server/services/WorkflowService.js +8 -2
  33. package/dist/server/services/WorkflowService.js.map +1 -1
  34. package/dist/server/services/agents/copilot-provider.js +32 -0
  35. package/dist/server/services/agents/copilot-provider.js.map +1 -1
  36. package/dist/server/websocket/index.js +42 -0
  37. package/dist/server/websocket/index.js.map +1 -1
  38. package/dist/shared/constants.js +9 -0
  39. package/dist/shared/constants.js.map +1 -1
  40. package/dist/shared/settings.js +51 -0
  41. package/dist/shared/settings.js.map +1 -0
  42. package/package.json +14 -10
  43. package/public/marktoflow-logo.png +0 -0
  44. package/server.log +0 -0
  45. package/tests/integration/fixtures/test-workflow.md +6 -0
  46. package/.turbo/turbo-build.log +0 -42
  47. package/dist/client/assets/index-CM44OayM.js +0 -704
  48. package/dist/client/assets/index-CM44OayM.js.map +0 -1
  49. package/dist/client/assets/index-Dru63gi6.css +0 -1
  50. package/marktoflow-gui-2.0.0-alpha.5.tgz +0 -0
  51. package/playwright.config.ts +0 -27
  52. package/postcss.config.js +0 -6
  53. package/src/client/App.tsx +0 -520
  54. package/src/client/components/Canvas/Canvas.tsx +0 -425
  55. package/src/client/components/Canvas/ExecutionOverlay.tsx +0 -935
  56. package/src/client/components/Canvas/ForEachNode.tsx +0 -152
  57. package/src/client/components/Canvas/IfElseNode.tsx +0 -141
  58. package/src/client/components/Canvas/NodeContextMenu.tsx +0 -192
  59. package/src/client/components/Canvas/OutputNode.tsx +0 -111
  60. package/src/client/components/Canvas/ParallelNode.tsx +0 -157
  61. package/src/client/components/Canvas/StepNode.tsx +0 -106
  62. package/src/client/components/Canvas/SubWorkflowNode.tsx +0 -141
  63. package/src/client/components/Canvas/SwitchNode.tsx +0 -185
  64. package/src/client/components/Canvas/Toolbar.tsx +0 -227
  65. package/src/client/components/Canvas/TransformNode.tsx +0 -194
  66. package/src/client/components/Canvas/TriggerNode.tsx +0 -128
  67. package/src/client/components/Canvas/TryCatchNode.tsx +0 -164
  68. package/src/client/components/Canvas/WhileNode.tsx +0 -161
  69. package/src/client/components/Canvas/index.ts +0 -24
  70. package/src/client/components/Debug/VariableInspector.tsx +0 -148
  71. package/src/client/components/Editor/InputsEditor.tsx +0 -458
  72. package/src/client/components/Editor/NewStepWizard.tsx +0 -344
  73. package/src/client/components/Editor/StepEditor.tsx +0 -532
  74. package/src/client/components/Editor/YamlEditor.tsx +0 -160
  75. package/src/client/components/Panels/PropertiesPanel.tsx +0 -589
  76. package/src/client/components/Prompt/ChangePreview.tsx +0 -281
  77. package/src/client/components/Prompt/PromptHistoryPanel.tsx +0 -209
  78. package/src/client/components/Prompt/PromptInput.tsx +0 -110
  79. package/src/client/components/Settings/ProviderSwitcher.tsx +0 -228
  80. package/src/client/components/Sidebar/ImportDialog.tsx +0 -257
  81. package/src/client/components/Sidebar/Sidebar.tsx +0 -362
  82. package/src/client/components/common/Breadcrumb.tsx +0 -40
  83. package/src/client/components/common/Button.tsx +0 -68
  84. package/src/client/components/common/ContextMenu.tsx +0 -202
  85. package/src/client/components/common/KeyboardShortcuts.tsx +0 -149
  86. package/src/client/components/common/Modal.tsx +0 -93
  87. package/src/client/components/common/Tabs.tsx +0 -57
  88. package/src/client/components/common/ThemeToggle.tsx +0 -63
  89. package/src/client/components/index.ts +0 -32
  90. package/src/client/hooks/index.ts +0 -4
  91. package/src/client/hooks/useAIPrompt.ts +0 -108
  92. package/src/client/hooks/useCanvas.ts +0 -247
  93. package/src/client/hooks/useWebSocket.ts +0 -164
  94. package/src/client/hooks/useWorkflow.ts +0 -138
  95. package/src/client/main.tsx +0 -10
  96. package/src/client/stores/agentStore.ts +0 -109
  97. package/src/client/stores/canvasStore.ts +0 -348
  98. package/src/client/stores/editorStore.ts +0 -133
  99. package/src/client/stores/executionStore.ts +0 -502
  100. package/src/client/stores/index.ts +0 -4
  101. package/src/client/stores/layoutStore.ts +0 -103
  102. package/src/client/stores/navigationStore.ts +0 -49
  103. package/src/client/stores/promptStore.ts +0 -113
  104. package/src/client/stores/themeStore.ts +0 -75
  105. package/src/client/stores/workflowStore.ts +0 -185
  106. package/src/client/styles/globals.css +0 -452
  107. package/src/client/utils/cn.ts +0 -9
  108. package/src/client/utils/index.ts +0 -4
  109. package/src/client/utils/platform.ts +0 -46
  110. package/src/client/utils/serviceIcons.tsx +0 -97
  111. package/src/client/utils/stepValidation.ts +0 -155
  112. package/src/client/utils/workflowToGraph.ts +0 -523
  113. package/src/server/index.ts +0 -137
  114. package/src/server/routes/ai.ts +0 -91
  115. package/src/server/routes/execute.ts +0 -71
  116. package/src/server/routes/executions.ts +0 -136
  117. package/src/server/routes/tools.ts +0 -970
  118. package/src/server/routes/workflows.ts +0 -147
  119. package/src/server/services/AIService.ts +0 -105
  120. package/src/server/services/FileWatcher.ts +0 -69
  121. package/src/server/services/WorkflowService.ts +0 -601
  122. package/src/server/services/agents/claude-code-provider.ts +0 -320
  123. package/src/server/services/agents/claude-provider.ts +0 -248
  124. package/src/server/services/agents/codex-provider.ts +0 -398
  125. package/src/server/services/agents/copilot-provider.ts +0 -311
  126. package/src/server/services/agents/demo-provider.ts +0 -184
  127. package/src/server/services/agents/index.ts +0 -31
  128. package/src/server/services/agents/ollama-provider.ts +0 -267
  129. package/src/server/services/agents/prompts.ts +0 -509
  130. package/src/server/services/agents/registry.ts +0 -310
  131. package/src/server/services/agents/types.ts +0 -146
  132. package/src/server/websocket/index.ts +0 -117
  133. package/src/shared/constants.ts +0 -180
  134. package/src/shared/types.ts +0 -179
  135. package/tailwind.config.ts +0 -73
  136. package/tests/e2e/app.spec.ts +0 -90
  137. package/tests/e2e/canvas.spec.ts +0 -128
  138. package/tests/e2e/workflow.spec.ts +0 -185
  139. package/tests/integration/api.test.ts +0 -452
  140. package/tests/integration/testApp.ts +0 -31
  141. package/tests/setup.ts +0 -72
  142. package/tests/unit/ForEachNode.test.tsx +0 -308
  143. package/tests/unit/IfElseNode.test.tsx +0 -235
  144. package/tests/unit/ParallelNode.test.tsx +0 -344
  145. package/tests/unit/SwitchNode.test.tsx +0 -327
  146. package/tests/unit/TransformNode.test.tsx +0 -386
  147. package/tests/unit/TryCatchNode.test.tsx +0 -243
  148. package/tests/unit/WhileNode.test.tsx +0 -230
  149. package/tests/unit/agentStore.test.ts +0 -218
  150. package/tests/unit/canvasStore.test.ts +0 -502
  151. package/tests/unit/codexProvider.test.ts +0 -399
  152. package/tests/unit/components.test.tsx +0 -151
  153. package/tests/unit/executionStore.test.ts +0 -567
  154. package/tests/unit/layoutStore.test.ts +0 -194
  155. package/tests/unit/navigationStore.test.ts +0 -152
  156. package/tests/unit/platform.test.ts +0 -118
  157. package/tests/unit/serviceIcons.test.ts +0 -197
  158. package/tests/unit/stepValidation.test.ts +0 -226
  159. package/tests/unit/themeStore.test.ts +0 -141
  160. package/tests/unit/workflowToGraph.test.ts +0 -311
  161. package/tsconfig.json +0 -29
  162. package/tsconfig.server.json +0 -28
  163. package/vite.config.ts +0 -31
  164. package/vitest.config.ts +0 -26
@@ -1,458 +0,0 @@
1
- import { useState, useRef, useEffect } from 'react';
2
- import { Plus, Trash2, Variable } from 'lucide-react';
3
- import { Button } from '../common/Button';
4
-
5
- interface InputsEditorProps {
6
- inputs: Record<string, unknown>;
7
- onChange: (inputs: Record<string, unknown>) => void;
8
- availableVariables: string[];
9
- }
10
-
11
- export function InputsEditor({
12
- inputs,
13
- onChange,
14
- availableVariables,
15
- }: InputsEditorProps) {
16
- const [newKey, setNewKey] = useState('');
17
-
18
- const entries = Object.entries(inputs);
19
-
20
- const updateInput = (key: string, value: unknown) => {
21
- onChange({ ...inputs, [key]: value });
22
- };
23
-
24
- const removeInput = (key: string) => {
25
- const updated = { ...inputs };
26
- delete updated[key];
27
- onChange(updated);
28
- };
29
-
30
- const addInput = () => {
31
- if (newKey && !inputs.hasOwnProperty(newKey)) {
32
- onChange({ ...inputs, [newKey]: '' });
33
- setNewKey('');
34
- }
35
- };
36
-
37
- const renameKey = (oldKey: string, newKeyName: string) => {
38
- if (newKeyName && newKeyName !== oldKey && !inputs.hasOwnProperty(newKeyName)) {
39
- const updated: Record<string, unknown> = {};
40
- for (const [key, value] of Object.entries(inputs)) {
41
- if (key === oldKey) {
42
- updated[newKeyName] = value;
43
- } else {
44
- updated[key] = value;
45
- }
46
- }
47
- onChange(updated);
48
- }
49
- };
50
-
51
- const insertVariable = (key: string, variable: string) => {
52
- const currentValue = String(inputs[key] || '');
53
- updateInput(key, currentValue + `{{ ${variable} }}`);
54
- };
55
-
56
- return (
57
- <div className="space-y-4">
58
- {entries.length === 0 ? (
59
- <div className="text-center py-8">
60
- <p className="text-sm text-gray-500 mb-3">No inputs defined</p>
61
- </div>
62
- ) : (
63
- <div className="space-y-3">
64
- {entries.map(([key, value]) => (
65
- <InputField
66
- key={key}
67
- inputKey={key}
68
- value={value}
69
- onChange={(newValue) => updateInput(key, newValue)}
70
- onRemove={() => removeInput(key)}
71
- onRename={(newName) => renameKey(key, newName)}
72
- availableVariables={availableVariables}
73
- onInsertVariable={(variable) => insertVariable(key, variable)}
74
- />
75
- ))}
76
- </div>
77
- )}
78
-
79
- {/* Add new input */}
80
- <div className="flex gap-2 pt-2 border-t border-node-border">
81
- <input
82
- type="text"
83
- value={newKey}
84
- onChange={(e) => setNewKey(e.target.value)}
85
- placeholder="New input key..."
86
- className="flex-1 px-3 py-2 bg-node-bg border border-node-border rounded-lg text-white text-sm focus:outline-none focus:border-primary"
87
- onKeyDown={(e) => {
88
- if (e.key === 'Enter') {
89
- addInput();
90
- }
91
- }}
92
- />
93
- <Button
94
- variant="secondary"
95
- size="sm"
96
- onClick={addInput}
97
- disabled={!newKey || inputs.hasOwnProperty(newKey)}
98
- icon={<Plus className="w-4 h-4" />}
99
- >
100
- Add
101
- </Button>
102
- </div>
103
-
104
- {/* Variable reference help */}
105
- {availableVariables.length > 0 && (
106
- <div className="p-3 bg-node-bg rounded-lg border border-node-border">
107
- <div className="flex items-center gap-2 mb-2">
108
- <Variable className="w-4 h-4 text-primary" />
109
- <span className="text-xs font-medium text-gray-400 uppercase tracking-wider">
110
- Available Variables
111
- </span>
112
- </div>
113
- <div className="flex flex-wrap gap-1.5">
114
- {availableVariables.map((variable) => (
115
- <code
116
- key={variable}
117
- className="px-2 py-0.5 bg-primary/10 text-primary text-xs rounded"
118
- >
119
- {variable}
120
- </code>
121
- ))}
122
- </div>
123
- <p className="mt-2 text-xs text-gray-500">
124
- Click on an input field, then use the variable button to insert
125
- </p>
126
- </div>
127
- )}
128
- </div>
129
- );
130
- }
131
-
132
- interface InputFieldProps {
133
- inputKey: string;
134
- value: unknown;
135
- onChange: (value: unknown) => void;
136
- onRemove: () => void;
137
- onRename: (newKey: string) => void;
138
- availableVariables: string[];
139
- onInsertVariable: (variable: string) => void;
140
- }
141
-
142
- function InputField({
143
- inputKey,
144
- value,
145
- onChange,
146
- onRemove,
147
- onRename,
148
- availableVariables,
149
- onInsertVariable,
150
- }: InputFieldProps) {
151
- const [isEditingKey, setIsEditingKey] = useState(false);
152
- const [editedKey, setEditedKey] = useState(inputKey);
153
- const [showVariables, setShowVariables] = useState(false);
154
-
155
- const valueType = typeof value;
156
- const isObject = valueType === 'object' && value !== null;
157
- const isArray = Array.isArray(value);
158
-
159
- const handleKeySubmit = () => {
160
- if (editedKey !== inputKey) {
161
- onRename(editedKey);
162
- }
163
- setIsEditingKey(false);
164
- };
165
-
166
- const renderValueInput = () => {
167
- if (isArray || isObject) {
168
- return (
169
- <textarea
170
- value={JSON.stringify(value, null, 2)}
171
- onChange={(e) => {
172
- try {
173
- onChange(JSON.parse(e.target.value));
174
- } catch {
175
- // Keep as string if not valid JSON
176
- onChange(e.target.value);
177
- }
178
- }}
179
- className="flex-1 px-3 py-2 bg-node-bg border border-node-border rounded-lg text-white text-sm font-mono focus:outline-none focus:border-primary resize-none"
180
- rows={3}
181
- />
182
- );
183
- }
184
-
185
- if (valueType === 'boolean') {
186
- return (
187
- <select
188
- value={String(value)}
189
- onChange={(e) => onChange(e.target.value === 'true')}
190
- className="flex-1 px-3 py-2 bg-node-bg border border-node-border rounded-lg text-white text-sm focus:outline-none focus:border-primary"
191
- >
192
- <option value="true">true</option>
193
- <option value="false">false</option>
194
- </select>
195
- );
196
- }
197
-
198
- if (valueType === 'number') {
199
- return (
200
- <input
201
- type="number"
202
- value={String(value)}
203
- onChange={(e) => onChange(parseFloat(e.target.value) || 0)}
204
- className="flex-1 px-3 py-2 bg-node-bg border border-node-border rounded-lg text-white text-sm focus:outline-none focus:border-primary"
205
- />
206
- );
207
- }
208
-
209
- // String input with autocomplete (default)
210
- return (
211
- <VariableAutocompleteInput
212
- value={String(value)}
213
- onChange={(newValue) => onChange(newValue)}
214
- variables={availableVariables}
215
- placeholder="Value or {{ variable }}"
216
- />
217
- );
218
- };
219
-
220
- return (
221
- <div className="p-3 bg-node-bg/50 rounded-lg border border-node-border space-y-2">
222
- {/* Key row */}
223
- <div className="flex items-center gap-2">
224
- {isEditingKey ? (
225
- <input
226
- type="text"
227
- value={editedKey}
228
- onChange={(e) => setEditedKey(e.target.value)}
229
- onBlur={handleKeySubmit}
230
- onKeyDown={(e) => {
231
- if (e.key === 'Enter') handleKeySubmit();
232
- if (e.key === 'Escape') {
233
- setEditedKey(inputKey);
234
- setIsEditingKey(false);
235
- }
236
- }}
237
- className="flex-1 px-2 py-1 bg-node-bg border border-primary rounded text-white text-sm font-mono focus:outline-none"
238
- autoFocus
239
- />
240
- ) : (
241
- <button
242
- onClick={() => setIsEditingKey(true)}
243
- className="flex-1 text-left px-2 py-1 text-sm font-mono text-primary hover:bg-white/5 rounded"
244
- >
245
- {inputKey}
246
- </button>
247
- )}
248
- <span className="text-xs text-gray-500">{valueType}</span>
249
- <Button
250
- variant="ghost"
251
- size="sm"
252
- onClick={onRemove}
253
- className="!p-1 text-gray-400 hover:text-error"
254
- >
255
- <Trash2 className="w-4 h-4" />
256
- </Button>
257
- </div>
258
-
259
- {/* Value row */}
260
- <div className="flex gap-2">
261
- {renderValueInput()}
262
- {availableVariables.length > 0 && !isArray && !isObject && (
263
- <div className="relative">
264
- <Button
265
- variant="ghost"
266
- size="sm"
267
- onClick={() => setShowVariables(!showVariables)}
268
- className="!p-2"
269
- >
270
- <Variable className="w-4 h-4" />
271
- </Button>
272
- {showVariables && (
273
- <div className="absolute right-0 top-full mt-1 z-10 p-2 bg-panel-bg border border-node-border rounded-lg shadow-lg min-w-[150px]">
274
- <div className="text-xs text-gray-400 mb-1">Insert variable</div>
275
- <div className="space-y-1 max-h-40 overflow-y-auto">
276
- {availableVariables.map((variable) => (
277
- <button
278
- key={variable}
279
- onClick={() => {
280
- onInsertVariable(variable);
281
- setShowVariables(false);
282
- }}
283
- className="w-full text-left px-2 py-1 text-xs font-mono text-primary hover:bg-white/10 rounded"
284
- >
285
- {variable}
286
- </button>
287
- ))}
288
- </div>
289
- </div>
290
- )}
291
- </div>
292
- )}
293
- </div>
294
- </div>
295
- );
296
- }
297
-
298
- // Autocomplete input for template variables
299
- interface VariableAutocompleteInputProps {
300
- value: string;
301
- onChange: (value: string) => void;
302
- variables: string[];
303
- placeholder?: string;
304
- }
305
-
306
- function VariableAutocompleteInput({
307
- value,
308
- onChange,
309
- variables,
310
- placeholder,
311
- }: VariableAutocompleteInputProps) {
312
- const [showSuggestions, setShowSuggestions] = useState(false);
313
- const [cursorPosition, setCursorPosition] = useState(0);
314
- const [selectedIndex, setSelectedIndex] = useState(0);
315
- const inputRef = useRef<HTMLInputElement>(null);
316
-
317
- // Find the partial variable being typed at cursor position
318
- const getPartialVariable = (): { text: string; start: number; end: number } | null => {
319
- const beforeCursor = value.substring(0, cursorPosition);
320
- // Look for {{ that hasn't been closed
321
- const openBraceIndex = beforeCursor.lastIndexOf('{{');
322
- if (openBraceIndex === -1) return null;
323
-
324
- // Check if there's a closing }} between {{ and cursor
325
- const afterOpenBrace = beforeCursor.substring(openBraceIndex);
326
- if (afterOpenBrace.includes('}}')) return null;
327
-
328
- // Extract the partial text after {{
329
- const partialText = afterOpenBrace.substring(2).trim();
330
- return {
331
- text: partialText,
332
- start: openBraceIndex,
333
- end: cursorPosition,
334
- };
335
- };
336
-
337
- // Filter variables based on partial input
338
- const partial = getPartialVariable();
339
- const filteredVariables = partial
340
- ? variables.filter((v) =>
341
- v.toLowerCase().includes(partial.text.toLowerCase())
342
- )
343
- : [];
344
-
345
- const shouldShowSuggestions = showSuggestions && partial && filteredVariables.length > 0;
346
-
347
- // Handle input change
348
- const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
349
- onChange(e.target.value);
350
- setCursorPosition(e.target.selectionStart || 0);
351
- setShowSuggestions(true);
352
- setSelectedIndex(0);
353
- };
354
-
355
- // Handle cursor position change
356
- const handleSelect = (e: React.SyntheticEvent<HTMLInputElement>) => {
357
- const target = e.target as HTMLInputElement;
358
- setCursorPosition(target.selectionStart || 0);
359
- };
360
-
361
- // Handle keyboard navigation
362
- const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
363
- if (!shouldShowSuggestions) return;
364
-
365
- switch (e.key) {
366
- case 'ArrowDown':
367
- e.preventDefault();
368
- setSelectedIndex((prev) =>
369
- prev < filteredVariables.length - 1 ? prev + 1 : 0
370
- );
371
- break;
372
- case 'ArrowUp':
373
- e.preventDefault();
374
- setSelectedIndex((prev) =>
375
- prev > 0 ? prev - 1 : filteredVariables.length - 1
376
- );
377
- break;
378
- case 'Enter':
379
- case 'Tab':
380
- e.preventDefault();
381
- insertVariable(filteredVariables[selectedIndex]);
382
- break;
383
- case 'Escape':
384
- setShowSuggestions(false);
385
- break;
386
- }
387
- };
388
-
389
- // Insert selected variable
390
- const insertVariable = (variable: string) => {
391
- if (!partial) return;
392
-
393
- const before = value.substring(0, partial.start);
394
- const after = value.substring(partial.end);
395
- const newValue = `${before}{{ ${variable} }}${after}`;
396
- onChange(newValue);
397
- setShowSuggestions(false);
398
-
399
- // Move cursor after the inserted variable
400
- setTimeout(() => {
401
- if (inputRef.current) {
402
- const newPosition = partial.start + variable.length + 6; // {{ + variable + }} + spaces
403
- inputRef.current.setSelectionRange(newPosition, newPosition);
404
- inputRef.current.focus();
405
- }
406
- }, 0);
407
- };
408
-
409
- // Close suggestions when clicking outside
410
- useEffect(() => {
411
- const handleClickOutside = () => setShowSuggestions(false);
412
- document.addEventListener('click', handleClickOutside);
413
- return () => document.removeEventListener('click', handleClickOutside);
414
- }, []);
415
-
416
- return (
417
- <div className="relative flex-1">
418
- <input
419
- ref={inputRef}
420
- type="text"
421
- value={value}
422
- onChange={handleChange}
423
- onSelect={handleSelect}
424
- onKeyDown={handleKeyDown}
425
- onFocus={() => setShowSuggestions(true)}
426
- className="w-full px-3 py-2 bg-node-bg border border-node-border rounded-lg text-white text-sm focus:outline-none focus:border-primary"
427
- placeholder={placeholder}
428
- />
429
- {shouldShowSuggestions && (
430
- <div
431
- className="absolute left-0 top-full mt-1 z-20 p-1 bg-panel-bg border border-node-border rounded-lg shadow-lg min-w-[200px] max-h-[200px] overflow-y-auto"
432
- onClick={(e) => e.stopPropagation()}
433
- >
434
- <div className="text-xs text-gray-500 px-2 py-1 mb-1">
435
- Variables matching "{partial?.text || ''}"
436
- </div>
437
- {filteredVariables.map((variable, index) => (
438
- <button
439
- key={variable}
440
- onClick={() => insertVariable(variable)}
441
- className={`w-full text-left px-2 py-1.5 text-xs font-mono rounded flex items-center gap-2 ${
442
- index === selectedIndex
443
- ? 'bg-primary/20 text-primary'
444
- : 'text-gray-300 hover:bg-white/5'
445
- }`}
446
- >
447
- <Variable className="w-3 h-3" />
448
- {variable}
449
- </button>
450
- ))}
451
- <div className="text-xs text-gray-600 px-2 pt-1 border-t border-node-border mt-1">
452
- ↑↓ to navigate, Enter to select
453
- </div>
454
- </div>
455
- )}
456
- </div>
457
- );
458
- }