@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.
- package/dist/{Invect-BndIqK9R.js → Invect-DKeWRdqo.js} +30004 -29859
- package/dist/api/client.d.ts +5 -0
- package/dist/api/node-data.api.d.ts +1 -1
- package/dist/assets/provider-icons/index.d.ts +3 -4
- package/dist/{babel-C9OtljFZ.js → babel-BAnO5AO4.js} +5 -5
- package/dist/demo.js +31 -31
- package/dist/{estree-ClbRfS-1.js → estree-CKaJvoWR.js} +3 -3
- package/dist/index.css +1 -1
- package/dist/index.js +194 -194
- package/dist/stores/uiStore.d.ts +4 -0
- package/dist/svg-raw.d.ts +4 -0
- package/package.json +5 -5
- package/src/api/client.ts +11 -5
- package/src/api/use-flow-run-stream.ts +1 -5
- package/src/assets/provider-icons/cloudwatch.svg +14 -0
- package/src/assets/provider-icons/dropbox.svg +10 -0
- package/src/assets/provider-icons/facebook.svg +8 -0
- package/src/assets/provider-icons/google_analytics.svg +7 -0
- package/src/assets/provider-icons/index.ts +46 -43
- package/src/assets/provider-icons/jira.svg +18 -0
- package/src/assets/provider-icons/microsoft_teams.svg +27 -0
- package/src/assets/provider-icons/salesforce.svg +16 -0
- package/src/assets/provider-icons/shopify.svg +8 -0
- package/src/assets/provider-icons/trello.svg +16 -0
- package/src/assets/provider-icons/twitter.svg +7 -0
- package/src/components/chat/ChatMessageList.tsx +3 -27
- package/src/components/chat/ChatPanel.tsx +3 -3
- package/src/components/chat/InlineCredentialSetup.tsx +32 -19
- package/src/components/chat/use-chat.ts +1 -0
- package/src/components/credentials/CreateCredentialModal.tsx +89 -2
- package/src/components/credentials/EditCredentialModal.tsx +82 -2
- package/src/components/flow-editor/FlowHeader.tsx +1 -22
- package/src/components/flow-editor/FlowLayout.tsx +6 -27
- package/src/components/flow-editor/NodeSidebar.tsx +5 -12
- package/src/components/flow-editor/RunControls.tsx +12 -12
- package/src/components/flow-editor/node-config-panel/ConfigFieldWithTemplate.tsx +1 -2
- package/src/components/flow-editor/node-config-panel/panels/ConfigurationPanel.tsx +1 -12
- package/src/components/graph/LayoutSelector.tsx +2 -2
- package/src/components/shared/ProviderIcon.tsx +35 -1
- package/src/stores/uiStore.ts +23 -0
- 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}
|
|
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
|
-
|
|
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
|
|
235
|
-
<div className="flex size-
|
|
236
|
-
<KeyRound className="size-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
292
|
-
autoComplete="
|
|
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/
|
|
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
|
|
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="
|
|
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="
|
|
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,
|
|
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
|
|
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 =
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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.
|
|
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-
|
|
35
|
-
collapsed ? 'px-
|
|
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-
|
|
40
|
+
<Loader2 className="w-4 h-4 animate-spin" />
|
|
41
41
|
) : (
|
|
42
|
-
<Play className="w-
|
|
42
|
+
<Play className="w-4 h-4" />
|
|
43
43
|
)}
|
|
44
|
-
|
|
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-
|
|
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-
|
|
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-
|
|
70
|
+
<Loader2 className="w-4.5 h-4.5 animate-spin" />
|
|
71
71
|
) : (
|
|
72
|
-
<Power className=
|
|
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-
|
|
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-
|
|
96
|
+
<Loader2 className="w-4.5 h-4.5 animate-spin" />
|
|
97
97
|
) : (
|
|
98
|
-
<PowerOff className=
|
|
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
|
|
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
|
|
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-
|
|
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-
|
|
33
|
+
<AlignHorizontalDistributeCenter className="w-4.5 h-4.5" />
|
|
34
34
|
{!collapsed && 'Realign'}
|
|
35
35
|
</Button>
|
|
36
36
|
</TooltipTrigger>
|