@invect/ui 0.0.7 → 0.0.9

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 (41) hide show
  1. package/dist/{Invect-BndIqK9R.js → Invect-DKeWRdqo.js} +30004 -29859
  2. package/dist/api/client.d.ts +5 -0
  3. package/dist/api/node-data.api.d.ts +1 -1
  4. package/dist/assets/provider-icons/index.d.ts +3 -4
  5. package/dist/{babel-C9OtljFZ.js → babel-BAnO5AO4.js} +5 -5
  6. package/dist/demo.js +31 -31
  7. package/dist/{estree-ClbRfS-1.js → estree-CKaJvoWR.js} +3 -3
  8. package/dist/index.css +1 -1
  9. package/dist/index.js +194 -194
  10. package/dist/stores/uiStore.d.ts +4 -0
  11. package/dist/svg-raw.d.ts +4 -0
  12. package/package.json +5 -5
  13. package/src/api/client.ts +11 -5
  14. package/src/api/use-flow-run-stream.ts +1 -5
  15. package/src/assets/provider-icons/cloudwatch.svg +14 -0
  16. package/src/assets/provider-icons/dropbox.svg +10 -0
  17. package/src/assets/provider-icons/facebook.svg +8 -0
  18. package/src/assets/provider-icons/google_analytics.svg +7 -0
  19. package/src/assets/provider-icons/index.ts +46 -43
  20. package/src/assets/provider-icons/jira.svg +18 -0
  21. package/src/assets/provider-icons/microsoft_teams.svg +27 -0
  22. package/src/assets/provider-icons/salesforce.svg +16 -0
  23. package/src/assets/provider-icons/shopify.svg +8 -0
  24. package/src/assets/provider-icons/trello.svg +16 -0
  25. package/src/assets/provider-icons/twitter.svg +7 -0
  26. package/src/components/chat/ChatMessageList.tsx +3 -27
  27. package/src/components/chat/ChatPanel.tsx +3 -3
  28. package/src/components/chat/InlineCredentialSetup.tsx +32 -19
  29. package/src/components/chat/use-chat.ts +1 -0
  30. package/src/components/credentials/CreateCredentialModal.tsx +89 -2
  31. package/src/components/credentials/EditCredentialModal.tsx +82 -2
  32. package/src/components/flow-editor/FlowHeader.tsx +1 -22
  33. package/src/components/flow-editor/FlowLayout.tsx +6 -27
  34. package/src/components/flow-editor/NodeSidebar.tsx +5 -12
  35. package/src/components/flow-editor/RunControls.tsx +12 -12
  36. package/src/components/flow-editor/node-config-panel/ConfigFieldWithTemplate.tsx +1 -2
  37. package/src/components/flow-editor/node-config-panel/panels/ConfigurationPanel.tsx +1 -12
  38. package/src/components/graph/LayoutSelector.tsx +2 -2
  39. package/src/components/shared/ProviderIcon.tsx +35 -1
  40. package/src/stores/uiStore.ts +23 -0
  41. package/src/svg-raw.d.ts +4 -0
@@ -45,7 +45,7 @@ type LlmProvider = (typeof LLM_PROVIDERS)[number]['value'];
45
45
 
46
46
  function getDefaultName(provider: LlmProvider): string {
47
47
  const label = LLM_PROVIDERS.find((p) => p.value === provider)?.label ?? provider;
48
- return `${label} (Chat)`;
48
+ return `${label}-assistant`;
49
49
  }
50
50
 
51
51
  function getBaseUrl(provider: LlmProvider): string | undefined {
@@ -228,24 +228,25 @@ function CreateCredentialForm({ onBack }: { onBack?: () => void }) {
228
228
  return (
229
229
  <form
230
230
  onSubmit={handleSubmit}
231
- className="flex flex-col gap-3.5 rounded-xl border bg-muted/20 p-4 mx-2 w-full max-w-sm"
231
+ autoComplete="off"
232
+ className="flex flex-col gap-4 rounded-xl border border-border/60 bg-card p-5 mx-2 w-full max-w-sm shadow-sm"
232
233
  >
233
234
  {/* Header */}
234
- <div className="flex flex-col items-center gap-2 pb-1">
235
- <div className="flex size-9 items-center justify-center rounded-full bg-primary/10">
236
- <KeyRound className="size-4 text-primary" />
235
+ <div className="flex flex-col gap-2.5 pb-0.5">
236
+ <div className="flex size-10 items-center justify-center rounded-full bg-primary/10">
237
+ <KeyRound className="size-5 text-primary" />
237
238
  </div>
238
239
  <div className="text-center">
239
240
  <p className="text-sm font-semibold text-foreground">Connect an LLM provider</p>
240
- <p className="mt-0.5 text-[11px] text-muted-foreground">
241
+ <p className="mt-1 text-xs text-muted-foreground">
241
242
  Add an API key to start chatting with the assistant.
242
243
  </p>
243
244
  </div>
244
245
  </div>
245
246
 
246
247
  {/* Provider */}
247
- <div className="space-y-1">
248
- <label className="text-[11px] font-medium text-muted-foreground">Provider</label>
248
+ <div className="space-y-1.5 text-left">
249
+ <label className="text-xs font-medium text-muted-foreground">Provider</label>
249
250
  <Select
250
251
  value={provider}
251
252
  onValueChange={(v) => {
@@ -255,7 +256,7 @@ function CreateCredentialForm({ onBack }: { onBack?: () => void }) {
255
256
  }
256
257
  }}
257
258
  >
258
- <SelectTrigger className="h-8 text-xs">
259
+ <SelectTrigger className="h-9 text-xs">
259
260
  <SelectValue />
260
261
  </SelectTrigger>
261
262
  <SelectContent>
@@ -269,33 +270,45 @@ function CreateCredentialForm({ onBack }: { onBack?: () => void }) {
269
270
  </div>
270
271
 
271
272
  {/* Name */}
272
- <div className="space-y-1">
273
- <label className="text-[11px] font-medium text-muted-foreground">Name</label>
273
+ <div className="space-y-1.5">
274
+ <label className="text-xs font-medium text-muted-foreground">Name</label>
274
275
  <Input
275
276
  value={name}
276
277
  onChange={(e) => setName(e.target.value)}
277
278
  placeholder={getDefaultName(provider)}
278
- className="h-8 text-xs"
279
+ className="h-9 text-xs"
280
+ autoComplete="off"
281
+ autoCorrect="off"
282
+ autoCapitalize="off"
283
+ spellCheck={false}
284
+ data-1p-ignore
285
+ data-lpignore="true"
286
+ data-form-type="other"
279
287
  />
280
288
  </div>
281
289
 
282
290
  {/* API Key */}
283
- <div className="space-y-1">
284
- <label className="text-[11px] font-medium text-muted-foreground">API Key</label>
291
+ <div className="space-y-1.5">
292
+ <label className="text-xs font-medium text-muted-foreground">API Key</label>
285
293
  <div className="relative">
286
294
  <Input
287
295
  type={showKey ? 'text' : 'password'}
288
296
  value={apiKey}
289
297
  onChange={(e) => setApiKey(e.target.value)}
290
298
  placeholder={LLM_PROVIDERS.find((p) => p.value === provider)?.placeholder ?? 'sk-...'}
291
- className="h-8 pr-8 text-xs font-mono"
292
- autoComplete="off"
299
+ className="h-9 pr-8 text-xs font-mono"
300
+ autoComplete="new-password"
301
+ autoCorrect="off"
302
+ autoCapitalize="off"
293
303
  spellCheck={false}
304
+ data-1p-ignore
305
+ data-lpignore="true"
306
+ data-form-type="other"
294
307
  />
295
308
  <button
296
309
  type="button"
297
310
  onClick={() => setShowKey((s) => !s)}
298
- className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground/60 hover:text-muted-foreground transition-colors"
311
+ className="absolute right-2.5 top-1/2 -translate-y-1/2 text-muted-foreground/50 hover:text-muted-foreground transition-colors"
299
312
  tabIndex={-1}
300
313
  >
301
314
  {showKey ? <EyeOff className="size-3.5" /> : <Eye className="size-3.5" />}
@@ -312,7 +325,7 @@ function CreateCredentialForm({ onBack }: { onBack?: () => void }) {
312
325
  )}
313
326
 
314
327
  {/* Submit */}
315
- <Button type="submit" size="sm" className="w-full gap-1.5 text-xs" disabled={!canSubmit}>
328
+ <Button type="submit" size="sm" className="mt-0.5 w-full gap-1.5" disabled={!canSubmit}>
316
329
  {formState === 'creating' ? (
317
330
  <>
318
331
  <Loader2 className="size-3.5 animate-spin" />
@@ -333,7 +346,7 @@ function CreateCredentialForm({ onBack }: { onBack?: () => void }) {
333
346
  <button
334
347
  type="button"
335
348
  onClick={onBack}
336
- className="text-[11px] text-muted-foreground hover:text-foreground transition-colors"
349
+ className="text-[11px] text-muted-foreground hover:text-foreground transition-colors text-center"
337
350
  >
338
351
  ← Back to existing providers
339
352
  </button>
@@ -114,6 +114,7 @@ export function useChat(options: UseChatOptions = {}) {
114
114
  const body = JSON.stringify({ messages: state.getSerializableMessages() });
115
115
  fetch(url, {
116
116
  method: 'PUT',
117
+ credentials: 'include',
117
118
  headers: { 'Content-Type': 'application/json' },
118
119
  body,
119
120
  keepalive: true,
@@ -12,7 +12,7 @@ import {
12
12
  DialogHeader,
13
13
  DialogTitle,
14
14
  } from '../ui/dialog';
15
- import { CheckCircle2, XCircle, Loader2 } from 'lucide-react';
15
+ import { CheckCircle2, XCircle, Loader2, Plus, Trash2 } from 'lucide-react';
16
16
  import type { CreateCredentialInput, CredentialAuthType, CredentialType } from '../../api/types';
17
17
  import { useTestCredentialRequest } from '../../api/credentials.api';
18
18
 
@@ -116,6 +116,13 @@ export const CreateCredentialModal: React.FC<CreateCredentialModalProps> = ({
116
116
  ) {
117
117
  const encoded = btoa(`${formData.config.username}:${formData.config.password}`);
118
118
  headers['Authorization'] = `Basic ${encoded}`;
119
+ } else if (formData.authType === 'custom' && formData.config.headers) {
120
+ const customHeaders = formData.config.headers as Record<string, string>;
121
+ for (const [key, value] of Object.entries(customHeaders)) {
122
+ if (key.trim()) {
123
+ headers[key] = value;
124
+ }
125
+ }
119
126
  }
120
127
 
121
128
  if (testBody && ['POST', 'PUT', 'PATCH'].includes(testMethod)) {
@@ -193,7 +200,7 @@ export const CreateCredentialModal: React.FC<CreateCredentialModalProps> = ({
193
200
  ref={firstFieldRef}
194
201
  value={formData.name}
195
202
  onChange={(e) => setFormData({ ...formData, name: e.target.value })}
196
- placeholder="My Stripe Production"
203
+ placeholder="Acme API"
197
204
  required
198
205
  autoComplete="one-time-code"
199
206
  data-1p-ignore
@@ -497,6 +504,86 @@ export const CreateCredentialModal: React.FC<CreateCredentialModalProps> = ({
497
504
  </div>
498
505
  </>
499
506
  )}
507
+
508
+ {formData.authType === 'custom' &&
509
+ (() => {
510
+ const headers = (formData.config.headers as Record<string, string>) || {};
511
+ const entries = Object.entries(headers);
512
+ const updateHeaders = (newHeaders: Record<string, string>) => {
513
+ setFormData((prev) => ({
514
+ ...prev,
515
+ config: { ...prev.config, headers: newHeaders },
516
+ }));
517
+ };
518
+ return (
519
+ <div className="space-y-2">
520
+ <div className="flex items-center justify-between">
521
+ <Label className="text-xs">Headers*</Label>
522
+ <Button
523
+ type="button"
524
+ variant="ghost"
525
+ size="sm"
526
+ className="h-6 px-2 text-xs"
527
+ onClick={() => {
528
+ updateHeaders({ ...headers, '': '' });
529
+ }}
530
+ >
531
+ <Plus className="w-3 h-3 mr-1" />
532
+ Add Header
533
+ </Button>
534
+ </div>
535
+ {entries.length === 0 && (
536
+ <p className="text-xs text-muted-foreground">
537
+ No headers added yet. Click "Add Header" to start.
538
+ </p>
539
+ )}
540
+ {entries.map(([key, value], index) => (
541
+ <div key={index} className="flex gap-2 items-start">
542
+ <Input
543
+ value={key}
544
+ onChange={(e) => {
545
+ const newEntries = [...entries];
546
+ newEntries[index] = [e.target.value, value];
547
+ updateHeaders(Object.fromEntries(newEntries));
548
+ }}
549
+ placeholder="Header name"
550
+ autoComplete="one-time-code"
551
+ data-1p-ignore
552
+ data-lpignore="true"
553
+ className="h-8 text-xs flex-1"
554
+ />
555
+ <Input
556
+ value={value}
557
+ onChange={(e) => {
558
+ const newEntries = [...entries];
559
+ newEntries[index] = [key, e.target.value];
560
+ updateHeaders(Object.fromEntries(newEntries));
561
+ }}
562
+ type="text"
563
+ style={{ WebkitTextSecurity: 'disc' }}
564
+ placeholder="Header value"
565
+ autoComplete="one-time-code"
566
+ data-1p-ignore
567
+ data-lpignore="true"
568
+ className="h-8 text-xs flex-1"
569
+ />
570
+ <Button
571
+ type="button"
572
+ variant="ghost"
573
+ size="sm"
574
+ className="h-8 w-8 p-0 text-muted-foreground hover:text-destructive"
575
+ onClick={() => {
576
+ const newEntries = entries.filter((_, i) => i !== index);
577
+ updateHeaders(Object.fromEntries(newEntries));
578
+ }}
579
+ >
580
+ <Trash2 className="w-3.5 h-3.5" />
581
+ </Button>
582
+ </div>
583
+ ))}
584
+ </div>
585
+ );
586
+ })()}
500
587
  </div>
501
588
 
502
589
  {/* Test Section (Always visible for HTTP APIs and LLM providers) */}
@@ -1,5 +1,5 @@
1
1
  import React, { useState, useEffect } from 'react';
2
- import { X } from 'lucide-react';
2
+ import { X, Plus, Trash2 } from 'lucide-react';
3
3
  import { Button } from '../ui/button';
4
4
  import { Input } from '../ui/input';
5
5
  import { Label } from '../ui/label';
@@ -115,7 +115,7 @@ export const EditCredentialModal: React.FC<EditCredentialModalProps> = ({
115
115
  id="name"
116
116
  value={formData.name}
117
117
  onChange={(e) => setFormData({ ...formData, name: e.target.value })}
118
- placeholder="My Stripe Production"
118
+ placeholder="Acme API"
119
119
  required
120
120
  />
121
121
  </div>
@@ -369,6 +369,86 @@ export const EditCredentialModal: React.FC<EditCredentialModalProps> = ({
369
369
  </div>
370
370
  </>
371
371
  )}
372
+
373
+ {formData.authType === 'custom' &&
374
+ (() => {
375
+ const headers = (formData.config?.headers as Record<string, string>) || {};
376
+ const entries = Object.entries(headers);
377
+ const updateHeaders = (newHeaders: Record<string, string>) => {
378
+ setFormData((prev) => ({
379
+ ...prev,
380
+ config: { ...prev.config, headers: newHeaders },
381
+ }));
382
+ };
383
+ return (
384
+ <div className="space-y-2">
385
+ <div className="flex items-center justify-between">
386
+ <Label className="text-sm">Headers</Label>
387
+ <Button
388
+ type="button"
389
+ variant="ghost"
390
+ size="sm"
391
+ className="h-7 px-2 text-xs"
392
+ onClick={() => {
393
+ updateHeaders({ ...headers, '': '' });
394
+ }}
395
+ >
396
+ <Plus className="w-3 h-3 mr-1" />
397
+ Add Header
398
+ </Button>
399
+ </div>
400
+ {entries.length === 0 && (
401
+ <p className="text-xs text-muted-foreground">
402
+ No headers added yet. Click "Add Header" to start.
403
+ </p>
404
+ )}
405
+ {entries.map(([key, value], index) => (
406
+ <div key={index} className="flex gap-2 items-start">
407
+ <Input
408
+ value={key}
409
+ onChange={(e) => {
410
+ const newEntries = [...entries];
411
+ newEntries[index] = [e.target.value, value];
412
+ updateHeaders(Object.fromEntries(newEntries));
413
+ }}
414
+ placeholder="Header name"
415
+ autoComplete="one-time-code"
416
+ data-1p-ignore
417
+ data-lpignore="true"
418
+ className="flex-1"
419
+ />
420
+ <Input
421
+ value={value}
422
+ onChange={(e) => {
423
+ const newEntries = [...entries];
424
+ newEntries[index] = [key, e.target.value];
425
+ updateHeaders(Object.fromEntries(newEntries));
426
+ }}
427
+ type="text"
428
+ style={{ WebkitTextSecurity: 'disc' }}
429
+ placeholder="Header value"
430
+ autoComplete="one-time-code"
431
+ data-1p-ignore
432
+ data-lpignore="true"
433
+ className="flex-1"
434
+ />
435
+ <Button
436
+ type="button"
437
+ variant="ghost"
438
+ size="sm"
439
+ className="h-10 w-10 p-0 text-muted-foreground hover:text-destructive"
440
+ onClick={() => {
441
+ const newEntries = entries.filter((_, i) => i !== index);
442
+ updateHeaders(Object.fromEntries(newEntries));
443
+ }}
444
+ >
445
+ <Trash2 className="w-3.5 h-3.5" />
446
+ </Button>
447
+ </div>
448
+ ))}
449
+ </div>
450
+ );
451
+ })()}
372
452
  </div>
373
453
 
374
454
  {/* Description */}
@@ -1,10 +1,8 @@
1
1
  import { Button } from '../ui/button';
2
2
  import { Badge } from '../ui/badge';
3
- import { Save, Settings, Share2, Loader2 } from 'lucide-react';
3
+ import { Save, Loader2 } from 'lucide-react';
4
4
  import { InlineEdit } from './inline-edit';
5
5
  import { cn } from '../../lib/utils';
6
- import { usePluginRegistry } from '../../contexts/PluginRegistryContext';
7
- import { useParams } from 'react-router';
8
6
 
9
7
  interface FlowHeaderProps {
10
8
  flowName: string;
@@ -21,10 +19,6 @@ export function FlowHeader({
21
19
  onSave,
22
20
  isSaving = false,
23
21
  }: FlowHeaderProps) {
24
- const { flowId } = useParams();
25
- const registry = usePluginRegistry();
26
- const flowHeaderActions = registry.headerActions['flowHeader'] ?? [];
27
-
28
22
  return (
29
23
  <header className="flex items-center justify-between px-6 border-b h-14 border-border bg-imp-background text-card-foreground">
30
24
  <div className="flex items-center gap-4">
@@ -50,21 +44,6 @@ export function FlowHeader({
50
44
  )}
51
45
  </div>
52
46
  <div className="flex items-center gap-2">
53
- <Button variant="ghost" size="icon-sm" title="Settings" aria-label="Settings">
54
- <Settings className="w-4 h-4" />
55
- </Button>
56
- {/* Plugin-contributed header actions (e.g. Share button from RBAC) */}
57
- {flowHeaderActions
58
- .filter((a) => !a.permission || registry.checkPermission(a.permission, { flowId }))
59
- .map((action, i) => (
60
- <action.component key={`plugin-action-${i}`} flowId={flowId} basePath="" />
61
- ))}
62
- {/* Fallback Share button when no plugin provides one */}
63
- {flowHeaderActions.length === 0 && (
64
- <Button variant="ghost" size="icon-sm" title="Share" aria-label="Share">
65
- <Share2 className="w-4 h-4" />
66
- </Button>
67
- )}
68
47
  <Button
69
48
  variant="outline"
70
49
  size="sm"
@@ -1,7 +1,6 @@
1
1
  import type React from 'react';
2
- import { Plus, PanelLeftClose, PanelLeftOpen } from 'lucide-react';
2
+ import { Plus } from 'lucide-react';
3
3
  import { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from '../ui/tooltip';
4
- import { useUIStore } from '../../stores/uiStore';
5
4
  import { ToolbarCollapsedProvider } from './toolbar-context';
6
5
 
7
6
  interface FlowLayoutProps {
@@ -40,8 +39,7 @@ export function FlowLayout({
40
39
  sidebarOpen = true,
41
40
  onToggleSidebar,
42
41
  }: FlowLayoutProps) {
43
- const collapsed = useUIStore((s) => s.toolbarCollapsed);
44
- const toggleCollapsed = useUIStore((s) => s.toggleToolbarCollapsed);
42
+ const collapsed = true;
45
43
 
46
44
  return (
47
45
  <div className="flex flex-1 min-h-0">
@@ -60,35 +58,16 @@ export function FlowLayout({
60
58
  {(onToggleSidebar || layoutSelector || chatToggle || toolbarExtra) && (
61
59
  <TooltipProvider>
62
60
  <ToolbarCollapsedProvider value={collapsed}>
63
- <div className="absolute bottom-4 left-1/2 -translate-x-1/2 z-10 flex items-center gap-1 rounded-lg border border-border bg-card/90 backdrop-blur-sm shadow-md p-1">
64
- {/* Collapse / expand toggle */}
65
- <Tooltip>
66
- <TooltipTrigger asChild>
67
- <button
68
- onClick={toggleCollapsed}
69
- className="flex items-center justify-center w-7 h-7 rounded-md text-muted-foreground hover:bg-accent hover:text-accent-foreground transition-colors"
70
- >
71
- {collapsed ? (
72
- <PanelLeftOpen className="w-3.5 h-3.5" />
73
- ) : (
74
- <PanelLeftClose className="w-3.5 h-3.5" />
75
- )}
76
- </button>
77
- </TooltipTrigger>
78
- <TooltipContent side="top">
79
- {collapsed ? 'Expand toolbar' : 'Collapse toolbar'}
80
- </TooltipContent>
81
- </Tooltip>
82
-
61
+ <div className="absolute bottom-4 left-1/2 -translate-x-1/2 z-10 flex items-center gap-1.5 rounded-xl border border-border bg-card/90 backdrop-blur-sm shadow-md p-1.5">
83
62
  {onToggleSidebar && (
84
63
  <Tooltip>
85
64
  <TooltipTrigger asChild>
86
65
  <button
87
66
  onClick={onToggleSidebar}
88
- className="flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium rounded-md text-card-foreground hover:bg-accent hover:text-accent-foreground transition-colors"
67
+ className="flex items-center gap-1.5 px-3.5 py-2 text-sm font-medium rounded-md text-card-foreground hover:bg-accent hover:text-accent-foreground transition-colors"
89
68
  title={sidebarOpen ? 'Close node panel' : 'Open node panel'}
90
69
  >
91
- <Plus className="w-3.5 h-3.5" />
70
+ <Plus className="w-4 h-4" />
92
71
  {!collapsed && 'Add nodes'}
93
72
  </button>
94
73
  </TooltipTrigger>
@@ -99,7 +78,7 @@ export function FlowLayout({
99
78
  {chatToggle}
100
79
  {toolbarExtra && (
101
80
  <>
102
- <div className="w-px h-4 bg-border mx-0.5" />
81
+ <div className="w-px h-5 bg-border mx-1" />
103
82
  {toolbarExtra}
104
83
  </>
105
84
  )}
@@ -7,6 +7,7 @@ import type { NodeDefinition } from '../../types/node-definition.types';
7
7
  import type { ToolDefinition, AddedToolInstance } from '../nodes/ToolSelectorModal';
8
8
  import { ProviderIcon } from '../shared/ProviderIcon';
9
9
  import { useFlowEditorStore } from './flow-editor.store';
10
+ import { useUIStore } from '../../stores/uiStore';
10
11
  import { ActionsSidebar } from './ActionsSidebar';
11
12
  import { Search, Plus, X, ChevronRight, PanelLeftClose } from 'lucide-react';
12
13
  import { cn } from '../../lib/utils';
@@ -65,22 +66,14 @@ function NodesSidebar({
65
66
  onCollapse?: () => void;
66
67
  }) {
67
68
  const [search, setSearch] = useState('');
68
- // Tracks which groups the user has manually expanded (all start collapsed)
69
- const [expandedGroups, setExpandedGroups] = useState<Set<string>>(new Set());
70
69
  const { isLoading, nodeDefinitions } = useNodeRegistry();
70
+ const expandedGroups = useUIStore((s) => s.nodeSidebarExpandedGroups);
71
+ const toggleNodeSidebarGroup = useUIStore((s) => s.toggleNodeSidebarGroup);
71
72
 
72
73
  const isSearching = search.trim().length > 0;
73
74
 
74
75
  const toggleGroup = (providerId: string) => {
75
- setExpandedGroups((prev) => {
76
- const next = new Set(prev);
77
- if (next.has(providerId)) {
78
- next.delete(providerId);
79
- } else {
80
- next.add(providerId);
81
- }
82
- return next;
83
- });
76
+ toggleNodeSidebarGroup(providerId);
84
77
  };
85
78
 
86
79
  const totalVisible = useMemo(
@@ -225,7 +218,7 @@ function NodesSidebar({
225
218
  <div className="p-3 space-y-4">
226
219
  {sortedProviderIds.map((providerId) => {
227
220
  const group = providerGroups[providerId];
228
- const isCollapsed = isSearching ? false : !expandedGroups.has(providerId);
221
+ const isCollapsed = isSearching ? false : !expandedGroups.includes(providerId);
229
222
  return (
230
223
  <div key={providerId}>
231
224
  <button
@@ -31,17 +31,17 @@ export function RunControls({
31
31
  onClick={onExecute}
32
32
  disabled={isExecuting || !onExecute}
33
33
  className={cn(
34
- 'flex items-center gap-1.5 py-1.5 text-xs font-medium rounded-md bg-foreground text-background hover:bg-foreground/85 transition-colors disabled:opacity-50 disabled:pointer-events-none',
35
- collapsed ? 'px-4' : 'px-3',
34
+ 'flex items-center gap-1.5 py-2 text-sm font-medium rounded-md bg-foreground text-background hover:bg-foreground/85 transition-colors disabled:opacity-50 disabled:pointer-events-none',
35
+ collapsed ? 'px-5' : 'px-4',
36
36
  )}
37
37
  title={collapsed ? undefined : 'Run flow'}
38
38
  >
39
39
  {isExecuting ? (
40
- <Loader2 className="w-3.5 h-3.5 animate-spin" />
40
+ <Loader2 className="w-4 h-4 animate-spin" />
41
41
  ) : (
42
- <Play className="w-3.5 h-3.5" />
42
+ <Play className="w-4 h-4" />
43
43
  )}
44
- {!collapsed && 'Run'}
44
+ Run
45
45
  </button>
46
46
  </TooltipTrigger>
47
47
  {collapsed && <TooltipContent side="top">Run flow</TooltipContent>}
@@ -49,7 +49,7 @@ export function RunControls({
49
49
 
50
50
  {/* Active / Inactive segmented toggle */}
51
51
  {isActive !== undefined && onToggleActive && (
52
- <div className="inline-flex items-center rounded-md border border-border bg-muted/40 p-0.5">
52
+ <div className="inline-flex items-center rounded-md border border-border bg-muted/40 p-1">
53
53
  <Tooltip>
54
54
  <TooltipTrigger asChild>
55
55
  <button
@@ -60,16 +60,16 @@ export function RunControls({
60
60
  }}
61
61
  disabled={isTogglingActive}
62
62
  className={cn(
63
- 'flex items-center gap-1 px-2 py-1 text-xs font-medium rounded-sm transition-colors disabled:opacity-50',
63
+ 'flex items-center gap-1.5 px-2.5 py-1.5 text-sm font-medium rounded-sm transition-colors disabled:opacity-50',
64
64
  isActive
65
65
  ? 'bg-card text-foreground shadow-sm'
66
66
  : 'text-muted-foreground hover:text-foreground',
67
67
  )}
68
68
  >
69
69
  {isTogglingActive && !isActive ? (
70
- <Loader2 className="w-3.5 h-3.5 animate-spin" />
70
+ <Loader2 className="w-4.5 h-4.5 animate-spin" />
71
71
  ) : (
72
- <Power className="w-3 h-3" />
72
+ <Power className={cn('w-4.5 h-4.5', isActive && 'text-emerald-500')} />
73
73
  )}
74
74
  {!collapsed && 'Active'}
75
75
  </button>
@@ -86,16 +86,16 @@ export function RunControls({
86
86
  }}
87
87
  disabled={isTogglingActive}
88
88
  className={cn(
89
- 'flex items-center gap-1 px-2 py-1 text-xs font-medium rounded-sm transition-colors disabled:opacity-50',
89
+ 'flex items-center gap-1.5 px-2.5 py-1.5 text-sm font-medium rounded-sm transition-colors disabled:opacity-50',
90
90
  !isActive
91
91
  ? 'bg-card text-foreground shadow-sm'
92
92
  : 'text-muted-foreground hover:text-foreground',
93
93
  )}
94
94
  >
95
95
  {isTogglingActive && isActive ? (
96
- <Loader2 className="w-3.5 h-3.5 animate-spin" />
96
+ <Loader2 className="w-4.5 h-4.5 animate-spin" />
97
97
  ) : (
98
- <PowerOff className="w-3 h-3" />
98
+ <PowerOff className={cn('w-4.5 h-4.5', !isActive && 'text-red-400')} />
99
99
  )}
100
100
  {!collapsed && 'Inactive'}
101
101
  </button>
@@ -3,7 +3,7 @@ import { Input } from '../../ui/input';
3
3
  import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../../ui/select';
4
4
  import { Switch } from '../../ui/switch';
5
5
  import { Button } from '../../ui/button';
6
- import { Code2, Type, AlertCircle } from 'lucide-react';
6
+ import { Code2, Type } from 'lucide-react';
7
7
  import type { NodeParamField } from '../../../types/node-definition.types';
8
8
  import { cn } from '../../../lib/utils';
9
9
  import { DroppableInput } from './DroppableInput';
@@ -68,7 +68,6 @@ export const ConfigFieldWithTemplate = ({
68
68
 
69
69
  const fieldError = error ? (
70
70
  <div className="flex items-center gap-1 text-[11px] text-destructive mt-0.5">
71
- <AlertCircle className="w-3 h-3 shrink-0" />
72
71
  <span>{error}</span>
73
72
  </div>
74
73
  ) : null;
@@ -357,18 +357,7 @@ export function ConfigurationPanel({
357
357
  )}
358
358
 
359
359
  {/* Run error */}
360
- {runError && fieldErrors && Object.keys(fieldErrors).length > 0 ? (
361
- <div className="flex items-start gap-2 p-2.5 rounded-md bg-destructive/5 border border-destructive/20">
362
- <AlertCircle className="w-3.5 h-3.5 text-destructive mt-0.5 shrink-0" />
363
- <div className="text-xs text-destructive">
364
- <strong>Validation failed:</strong> {Object.keys(fieldErrors).length} field
365
- {Object.keys(fieldErrors).length > 1 ? 's have' : ' has'} errors — see
366
- highlighted fields above
367
- </div>
368
- </div>
369
- ) : runError ? (
370
- <ExecutionErrorDisplay error={runError} />
371
- ) : null}
360
+ {runError ? <ExecutionErrorDisplay error={runError} /> : null}
372
361
 
373
362
  {/* Model status message */}
374
363
  {nodeType === GraphNodeType.MODEL && modelStatusMessage && (
@@ -28,9 +28,9 @@ export const LayoutSelector: React.FC<LayoutSelectorProps> = ({ onLayoutChange,
28
28
  variant="ghost"
29
29
  size="sm"
30
30
  onClick={handleRealign}
31
- className="h-8 gap-1.5 rounded-md px-2.5 text-muted-foreground hover:bg-accent hover:text-foreground"
31
+ className="h-9 gap-1.5 rounded-md px-3 text-muted-foreground hover:bg-accent hover:text-foreground"
32
32
  >
33
- <AlignHorizontalDistributeCenter className="w-3.5 h-3.5" />
33
+ <AlignHorizontalDistributeCenter className="w-4.5 h-4.5" />
34
34
  {!collapsed && 'Realign'}
35
35
  </Button>
36
36
  </TooltipTrigger>