@foresthubai/workflow-builder 0.3.0 → 0.4.0
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/LICENSE +661 -661
- package/NOTICE +16 -16
- package/README.md +110 -93
- package/dist/components/ui/command.d.ts +2 -2
- package/dist/components/ui/input.d.ts +1 -1
- package/dist/components/ui/resizable.d.ts +1 -1
- package/dist/components/ui/textarea.d.ts +1 -1
- package/dist/graph/BaseNode.js +10 -10
- package/dist/graph/reactFlowRegistry.d.ts.map +1 -1
- package/dist/lib/utils.d.ts +3 -0
- package/dist/lib/utils.d.ts.map +1 -0
- package/dist/lib/utils.js +6 -0
- package/dist/lib/utils.js.map +1 -0
- package/dist/toolbars/CanvasTabsToolbar.d.ts +11 -0
- package/dist/toolbars/CanvasTabsToolbar.d.ts.map +1 -0
- package/dist/toolbars/CanvasTabsToolbar.js +101 -0
- package/dist/toolbars/CanvasTabsToolbar.js.map +1 -0
- package/package.json +2 -2
- package/src/BuilderLayout.tsx +345 -345
- package/src/Canvas.tsx +261 -261
- package/src/CanvasEditor.tsx +142 -142
- package/src/CanvasTabsToolbar.tsx +176 -176
- package/src/RightConfigPanel.tsx +266 -266
- package/src/WorkflowBuilder.tsx +412 -412
- package/src/cn.ts +6 -6
- package/src/components/ui/add-button.tsx +39 -39
- package/src/components/ui/alert-dialog.tsx +141 -141
- package/src/components/ui/alert.tsx +59 -59
- package/src/components/ui/badge.tsx +36 -36
- package/src/components/ui/button.tsx +85 -85
- package/src/components/ui/card.tsx +79 -79
- package/src/components/ui/checkbox.tsx +28 -28
- package/src/components/ui/collapsible.tsx +9 -9
- package/src/components/ui/command.tsx +153 -153
- package/src/components/ui/delete-button.tsx +23 -23
- package/src/components/ui/dialog.tsx +125 -125
- package/src/components/ui/dropdown-menu.tsx +198 -198
- package/src/components/ui/input.tsx +55 -55
- package/src/components/ui/label.tsx +24 -24
- package/src/components/ui/readonly-banner.tsx +15 -15
- package/src/components/ui/resizable.tsx +43 -43
- package/src/components/ui/scroll-area.tsx +102 -102
- package/src/components/ui/select.tsx +160 -160
- package/src/components/ui/separator.tsx +29 -29
- package/src/components/ui/switch.tsx +27 -27
- package/src/components/ui/textarea.tsx +51 -51
- package/src/components/ui/toast.tsx +127 -127
- package/src/components/ui/toaster.tsx +33 -33
- package/src/components/ui/toggle-group.tsx +59 -59
- package/src/components/ui/toggle.tsx +43 -43
- package/src/components/ui/tooltip.tsx +32 -32
- package/src/dialogs/NodePickerDialog.tsx +84 -84
- package/src/dialogs/ValidationDialog.tsx +184 -184
- package/src/graph/BaseNode.tsx +557 -557
- package/src/graph/CustomEdge.tsx +185 -185
- package/src/graph/CustomNode.tsx +16 -16
- package/src/graph/FunctionCallNode.tsx +30 -30
- package/src/graph/PortHandle.tsx +189 -189
- package/src/graph/reactFlowRegistry.ts +26 -26
- package/src/hooks/use-toast.ts +125 -125
- package/src/hooks/useAvailableVariables.ts +20 -20
- package/src/hooks/useCanvasHistory.ts +22 -22
- package/src/hooks/useCanvasTabs.ts +168 -168
- package/src/hooks/useFunctionDiagnosticsSync.ts +40 -40
- package/src/hooks/useFunctionRegistry.ts +26 -26
- package/src/hooks/useFunctions.ts +44 -44
- package/src/hooks/useGraph.ts +161 -161
- package/src/hooks/useNodeDefinitions.ts +82 -82
- package/src/hooks/useParamErrors.ts +26 -26
- package/src/hooks/useResolvedTheme.ts +30 -30
- package/src/hooks/useResourceDiagnosticsSync.ts +58 -58
- package/src/hooks/useSuppressThemeTransition.ts +79 -79
- package/src/hooks/useWorkflowSerialization.ts +127 -127
- package/src/i18n/index.ts +53 -53
- package/src/i18n/locales/de.json +501 -501
- package/src/i18n/locales/en.json +557 -557
- package/src/index.ts +27 -27
- package/src/inputs/ExpressionInput.tsx +297 -297
- package/src/inputs/ParameterEditor.tsx +515 -515
- package/src/inputs/PortSection.tsx +144 -144
- package/src/panels/BuilderSidebar.tsx +301 -301
- package/src/panels/ChannelConfigPanel.tsx +49 -49
- package/src/panels/ChannelsPanel.tsx +28 -28
- package/src/panels/DebugConsolePanel.tsx +73 -73
- package/src/panels/DebugContextPanel.tsx +77 -77
- package/src/panels/DebugExternalIOPanel.tsx +180 -180
- package/src/panels/DiagnosticsPanel.tsx +170 -170
- package/src/panels/EdgeConfigPanel.tsx +104 -104
- package/src/panels/FunctionConfigPanel.tsx +179 -179
- package/src/panels/FunctionListPanel.tsx +45 -45
- package/src/panels/MemoryConfigPanel.tsx +55 -55
- package/src/panels/MemoryPanel.tsx +40 -40
- package/src/panels/ModelConfigPanel.tsx +41 -41
- package/src/panels/ModelsPanel.tsx +36 -36
- package/src/panels/NodeConfigPanel.tsx +630 -630
- package/src/panels/NodeLibrary.tsx +288 -288
- package/src/panels/ResourceConfigPanel.tsx +132 -132
- package/src/panels/ResourceListPanel.tsx +113 -113
- package/src/panels/VariableConfigPanel.tsx +161 -161
- package/src/panels/VariablesPanel.tsx +145 -145
- package/src/stores/canvasStore.test.ts +44 -44
- package/src/stores/canvasStore.ts +245 -245
- package/src/stores/debugStore.ts +74 -74
- package/src/stores/diagnosticsStore.ts +130 -130
- package/src/stores/editorStore.ts +202 -202
- package/src/styles/index.css +526 -526
- package/src/utils/categoryConstants.ts +26 -26
- package/src/utils/channelOperations.ts +86 -86
- package/src/utils/connectionRules.ts +137 -137
- package/src/utils/functionOperations.ts +179 -179
- package/src/utils/graphOperations.ts +550 -550
- package/src/utils/history.ts +207 -207
- package/src/utils/memoryOperations.ts +57 -57
- package/src/utils/migrateFunctionNodes.ts +107 -107
- package/src/utils/modelOperations.ts +55 -55
- package/src/utils/paramDisplay.ts +71 -71
- package/src/utils/resourceHelpers.ts +32 -32
- package/src/utils/translation.ts +28 -28
- package/src/utils/variableOperations.ts +75 -75
- package/tailwind-preset.ts +166 -166
|
@@ -1,184 +1,184 @@
|
|
|
1
|
-
import { useState } from "react";
|
|
2
|
-
import { useTranslation } from "react-i18next";
|
|
3
|
-
import { AlertCircle, AlertTriangle, ChevronDown } from "lucide-react";
|
|
4
|
-
import { Badge } from "../components/ui/badge";
|
|
5
|
-
import { Button } from "../components/ui/button";
|
|
6
|
-
import {
|
|
7
|
-
Dialog,
|
|
8
|
-
DialogContent,
|
|
9
|
-
DialogDescription,
|
|
10
|
-
DialogFooter,
|
|
11
|
-
DialogHeader,
|
|
12
|
-
DialogTitle,
|
|
13
|
-
} from "../components/ui/dialog";
|
|
14
|
-
import { cn } from "../cn";
|
|
15
|
-
import type { Diagnostic, ValidationResult } from "@foresthubai/workflow-core/diagnostics";
|
|
16
|
-
|
|
17
|
-
interface ValidationDialogProps {
|
|
18
|
-
open: boolean;
|
|
19
|
-
onOpenChange: (open: boolean) => void;
|
|
20
|
-
validation: ValidationResult;
|
|
21
|
-
/**
|
|
22
|
-
* Export-gating flow only: when provided and there are no errors, the footer
|
|
23
|
-
* offers "Continue with Warnings". Omit for a plain informational dialog (just
|
|
24
|
-
* a Close button) — e.g. the standalone Validate action.
|
|
25
|
-
*/
|
|
26
|
-
onContinue?: () => void;
|
|
27
|
-
/** Clicking a diagnostic navigates to its target (canvas/node/edge/resource). */
|
|
28
|
-
onSelectDiagnostic?: (diagnostic: Diagnostic) => void;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export default function ValidationDialog({
|
|
32
|
-
open,
|
|
33
|
-
onOpenChange,
|
|
34
|
-
validation,
|
|
35
|
-
onContinue,
|
|
36
|
-
onSelectDiagnostic,
|
|
37
|
-
}: ValidationDialogProps) {
|
|
38
|
-
const { t } = useTranslation();
|
|
39
|
-
const hasErrors = validation.totalErrors > 0;
|
|
40
|
-
|
|
41
|
-
return (
|
|
42
|
-
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
43
|
-
<DialogContent className="max-w-lg max-h-[80vh] flex flex-col">
|
|
44
|
-
<DialogHeader>
|
|
45
|
-
<DialogTitle className="flex items-center gap-2">
|
|
46
|
-
{hasErrors ? (
|
|
47
|
-
<AlertTriangle className="w-5 h-5 text-destructive" />
|
|
48
|
-
) : (
|
|
49
|
-
<AlertCircle className="w-5 h-5 text-warning" />
|
|
50
|
-
)}
|
|
51
|
-
{t("validationIssues")}
|
|
52
|
-
</DialogTitle>
|
|
53
|
-
<DialogDescription>
|
|
54
|
-
{hasErrors
|
|
55
|
-
? t("validationErrorsDesc", {
|
|
56
|
-
errorCount: validation.totalErrors,
|
|
57
|
-
warningCount: validation.totalWarnings,
|
|
58
|
-
})
|
|
59
|
-
: t("validationWarningsDesc", {
|
|
60
|
-
warningCount: validation.totalWarnings,
|
|
61
|
-
})}
|
|
62
|
-
</DialogDescription>
|
|
63
|
-
</DialogHeader>
|
|
64
|
-
|
|
65
|
-
<div className="flex-1 overflow-y-auto -mx-6 px-6">
|
|
66
|
-
<div className="flex flex-col gap-2">
|
|
67
|
-
{validation.canvases.map((canvas) => (
|
|
68
|
-
<Section
|
|
69
|
-
key={canvas.canvasId}
|
|
70
|
-
label={canvas.canvasLabel}
|
|
71
|
-
diagnostics={canvas.diagnostics}
|
|
72
|
-
onSelectDiagnostic={onSelectDiagnostic}
|
|
73
|
-
/>
|
|
74
|
-
))}
|
|
75
|
-
{/* Project-scoped diagnostics (no canvasId) live in their own buckets. */}
|
|
76
|
-
{validation.channelDiagnostics.length > 0 && (
|
|
77
|
-
<Section
|
|
78
|
-
label={t("channels")}
|
|
79
|
-
diagnostics={validation.channelDiagnostics}
|
|
80
|
-
onSelectDiagnostic={onSelectDiagnostic}
|
|
81
|
-
/>
|
|
82
|
-
)}
|
|
83
|
-
{validation.memoryDiagnostics.length > 0 && (
|
|
84
|
-
<Section
|
|
85
|
-
label={t("memoryFiles")}
|
|
86
|
-
diagnostics={validation.memoryDiagnostics}
|
|
87
|
-
onSelectDiagnostic={onSelectDiagnostic}
|
|
88
|
-
/>
|
|
89
|
-
)}
|
|
90
|
-
{validation.modelDiagnostics.length > 0 && (
|
|
91
|
-
<Section
|
|
92
|
-
label={t("models")}
|
|
93
|
-
diagnostics={validation.modelDiagnostics}
|
|
94
|
-
onSelectDiagnostic={onSelectDiagnostic}
|
|
95
|
-
/>
|
|
96
|
-
)}
|
|
97
|
-
</div>
|
|
98
|
-
</div>
|
|
99
|
-
|
|
100
|
-
<DialogFooter>
|
|
101
|
-
{hasErrors || !onContinue ? (
|
|
102
|
-
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
103
|
-
{t("close")}
|
|
104
|
-
</Button>
|
|
105
|
-
) : (
|
|
106
|
-
<>
|
|
107
|
-
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
108
|
-
{t("cancel")}
|
|
109
|
-
</Button>
|
|
110
|
-
<Button onClick={onContinue}>{t("continueWithWarnings")}</Button>
|
|
111
|
-
</>
|
|
112
|
-
)}
|
|
113
|
-
</DialogFooter>
|
|
114
|
-
</DialogContent>
|
|
115
|
-
</Dialog>
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// One collapsible group of diagnostics — used for both canvases and the
|
|
120
|
-
// project-scoped buckets (channels/memory/models). Counts are derived here.
|
|
121
|
-
function Section({
|
|
122
|
-
label,
|
|
123
|
-
diagnostics,
|
|
124
|
-
onSelectDiagnostic,
|
|
125
|
-
}: {
|
|
126
|
-
label: string;
|
|
127
|
-
diagnostics: Diagnostic[];
|
|
128
|
-
onSelectDiagnostic?: (diagnostic: Diagnostic) => void;
|
|
129
|
-
}) {
|
|
130
|
-
const [open, setOpen] = useState(true);
|
|
131
|
-
const errorCount = diagnostics.filter((d) => d.severity === "error").length;
|
|
132
|
-
const warningCount = diagnostics.length - errorCount;
|
|
133
|
-
|
|
134
|
-
return (
|
|
135
|
-
<div className="rounded-md border border-border/60">
|
|
136
|
-
<button
|
|
137
|
-
type="button"
|
|
138
|
-
onClick={() => setOpen((v) => !v)}
|
|
139
|
-
className="flex items-center gap-2 w-full px-3 py-2 text-left text-sm hover:bg-accent/50 rounded-t-md"
|
|
140
|
-
>
|
|
141
|
-
<ChevronDown className={cn("w-3.5 h-3.5 shrink-0 transition-transform", !open && "-rotate-90")} />
|
|
142
|
-
<span className="truncate flex-1 font-medium">{label}</span>
|
|
143
|
-
<div className="flex items-center gap-1">
|
|
144
|
-
{errorCount > 0 && (
|
|
145
|
-
<Badge
|
|
146
|
-
variant="outline"
|
|
147
|
-
className="text-[10px] px-1.5 py-0 h-4 leading-4 border-destructive/40 text-destructive bg-card"
|
|
148
|
-
>
|
|
149
|
-
{errorCount}
|
|
150
|
-
</Badge>
|
|
151
|
-
)}
|
|
152
|
-
{warningCount > 0 && (
|
|
153
|
-
<Badge
|
|
154
|
-
variant="outline"
|
|
155
|
-
className="text-[10px] px-1.5 py-0 h-4 leading-4 border-warning/40 text-warning bg-card"
|
|
156
|
-
>
|
|
157
|
-
{warningCount}
|
|
158
|
-
</Badge>
|
|
159
|
-
)}
|
|
160
|
-
</div>
|
|
161
|
-
</button>
|
|
162
|
-
{open && (
|
|
163
|
-
<div className="border-t border-border/40 px-3 py-1.5 flex flex-col gap-0.5">
|
|
164
|
-
{diagnostics.map((diag, i) => (
|
|
165
|
-
<button
|
|
166
|
-
key={i}
|
|
167
|
-
type="button"
|
|
168
|
-
onClick={() => onSelectDiagnostic?.(diag)}
|
|
169
|
-
disabled={!onSelectDiagnostic}
|
|
170
|
-
className="flex items-start gap-1.5 px-1 py-1 text-xs text-left rounded transition-colors enabled:hover:bg-accent/50 enabled:cursor-pointer disabled:cursor-default"
|
|
171
|
-
>
|
|
172
|
-
{diag.severity === "error" ? (
|
|
173
|
-
<AlertTriangle className="w-3.5 h-3.5 text-destructive shrink-0 mt-0.5" />
|
|
174
|
-
) : (
|
|
175
|
-
<AlertCircle className="w-3.5 h-3.5 text-warning shrink-0 mt-0.5" />
|
|
176
|
-
)}
|
|
177
|
-
<span className="text-muted-foreground">{diag.message}</span>
|
|
178
|
-
</button>
|
|
179
|
-
))}
|
|
180
|
-
</div>
|
|
181
|
-
)}
|
|
182
|
-
</div>
|
|
183
|
-
);
|
|
184
|
-
}
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import { useTranslation } from "react-i18next";
|
|
3
|
+
import { AlertCircle, AlertTriangle, ChevronDown } from "lucide-react";
|
|
4
|
+
import { Badge } from "../components/ui/badge";
|
|
5
|
+
import { Button } from "../components/ui/button";
|
|
6
|
+
import {
|
|
7
|
+
Dialog,
|
|
8
|
+
DialogContent,
|
|
9
|
+
DialogDescription,
|
|
10
|
+
DialogFooter,
|
|
11
|
+
DialogHeader,
|
|
12
|
+
DialogTitle,
|
|
13
|
+
} from "../components/ui/dialog";
|
|
14
|
+
import { cn } from "../cn";
|
|
15
|
+
import type { Diagnostic, ValidationResult } from "@foresthubai/workflow-core/diagnostics";
|
|
16
|
+
|
|
17
|
+
interface ValidationDialogProps {
|
|
18
|
+
open: boolean;
|
|
19
|
+
onOpenChange: (open: boolean) => void;
|
|
20
|
+
validation: ValidationResult;
|
|
21
|
+
/**
|
|
22
|
+
* Export-gating flow only: when provided and there are no errors, the footer
|
|
23
|
+
* offers "Continue with Warnings". Omit for a plain informational dialog (just
|
|
24
|
+
* a Close button) — e.g. the standalone Validate action.
|
|
25
|
+
*/
|
|
26
|
+
onContinue?: () => void;
|
|
27
|
+
/** Clicking a diagnostic navigates to its target (canvas/node/edge/resource). */
|
|
28
|
+
onSelectDiagnostic?: (diagnostic: Diagnostic) => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export default function ValidationDialog({
|
|
32
|
+
open,
|
|
33
|
+
onOpenChange,
|
|
34
|
+
validation,
|
|
35
|
+
onContinue,
|
|
36
|
+
onSelectDiagnostic,
|
|
37
|
+
}: ValidationDialogProps) {
|
|
38
|
+
const { t } = useTranslation();
|
|
39
|
+
const hasErrors = validation.totalErrors > 0;
|
|
40
|
+
|
|
41
|
+
return (
|
|
42
|
+
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
43
|
+
<DialogContent className="max-w-lg max-h-[80vh] flex flex-col">
|
|
44
|
+
<DialogHeader>
|
|
45
|
+
<DialogTitle className="flex items-center gap-2">
|
|
46
|
+
{hasErrors ? (
|
|
47
|
+
<AlertTriangle className="w-5 h-5 text-destructive" />
|
|
48
|
+
) : (
|
|
49
|
+
<AlertCircle className="w-5 h-5 text-warning" />
|
|
50
|
+
)}
|
|
51
|
+
{t("validationIssues")}
|
|
52
|
+
</DialogTitle>
|
|
53
|
+
<DialogDescription>
|
|
54
|
+
{hasErrors
|
|
55
|
+
? t("validationErrorsDesc", {
|
|
56
|
+
errorCount: validation.totalErrors,
|
|
57
|
+
warningCount: validation.totalWarnings,
|
|
58
|
+
})
|
|
59
|
+
: t("validationWarningsDesc", {
|
|
60
|
+
warningCount: validation.totalWarnings,
|
|
61
|
+
})}
|
|
62
|
+
</DialogDescription>
|
|
63
|
+
</DialogHeader>
|
|
64
|
+
|
|
65
|
+
<div className="flex-1 overflow-y-auto -mx-6 px-6">
|
|
66
|
+
<div className="flex flex-col gap-2">
|
|
67
|
+
{validation.canvases.map((canvas) => (
|
|
68
|
+
<Section
|
|
69
|
+
key={canvas.canvasId}
|
|
70
|
+
label={canvas.canvasLabel}
|
|
71
|
+
diagnostics={canvas.diagnostics}
|
|
72
|
+
onSelectDiagnostic={onSelectDiagnostic}
|
|
73
|
+
/>
|
|
74
|
+
))}
|
|
75
|
+
{/* Project-scoped diagnostics (no canvasId) live in their own buckets. */}
|
|
76
|
+
{validation.channelDiagnostics.length > 0 && (
|
|
77
|
+
<Section
|
|
78
|
+
label={t("channels")}
|
|
79
|
+
diagnostics={validation.channelDiagnostics}
|
|
80
|
+
onSelectDiagnostic={onSelectDiagnostic}
|
|
81
|
+
/>
|
|
82
|
+
)}
|
|
83
|
+
{validation.memoryDiagnostics.length > 0 && (
|
|
84
|
+
<Section
|
|
85
|
+
label={t("memoryFiles")}
|
|
86
|
+
diagnostics={validation.memoryDiagnostics}
|
|
87
|
+
onSelectDiagnostic={onSelectDiagnostic}
|
|
88
|
+
/>
|
|
89
|
+
)}
|
|
90
|
+
{validation.modelDiagnostics.length > 0 && (
|
|
91
|
+
<Section
|
|
92
|
+
label={t("models")}
|
|
93
|
+
diagnostics={validation.modelDiagnostics}
|
|
94
|
+
onSelectDiagnostic={onSelectDiagnostic}
|
|
95
|
+
/>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<DialogFooter>
|
|
101
|
+
{hasErrors || !onContinue ? (
|
|
102
|
+
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
103
|
+
{t("close")}
|
|
104
|
+
</Button>
|
|
105
|
+
) : (
|
|
106
|
+
<>
|
|
107
|
+
<Button variant="outline" onClick={() => onOpenChange(false)}>
|
|
108
|
+
{t("cancel")}
|
|
109
|
+
</Button>
|
|
110
|
+
<Button onClick={onContinue}>{t("continueWithWarnings")}</Button>
|
|
111
|
+
</>
|
|
112
|
+
)}
|
|
113
|
+
</DialogFooter>
|
|
114
|
+
</DialogContent>
|
|
115
|
+
</Dialog>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// One collapsible group of diagnostics — used for both canvases and the
|
|
120
|
+
// project-scoped buckets (channels/memory/models). Counts are derived here.
|
|
121
|
+
function Section({
|
|
122
|
+
label,
|
|
123
|
+
diagnostics,
|
|
124
|
+
onSelectDiagnostic,
|
|
125
|
+
}: {
|
|
126
|
+
label: string;
|
|
127
|
+
diagnostics: Diagnostic[];
|
|
128
|
+
onSelectDiagnostic?: (diagnostic: Diagnostic) => void;
|
|
129
|
+
}) {
|
|
130
|
+
const [open, setOpen] = useState(true);
|
|
131
|
+
const errorCount = diagnostics.filter((d) => d.severity === "error").length;
|
|
132
|
+
const warningCount = diagnostics.length - errorCount;
|
|
133
|
+
|
|
134
|
+
return (
|
|
135
|
+
<div className="rounded-md border border-border/60">
|
|
136
|
+
<button
|
|
137
|
+
type="button"
|
|
138
|
+
onClick={() => setOpen((v) => !v)}
|
|
139
|
+
className="flex items-center gap-2 w-full px-3 py-2 text-left text-sm hover:bg-accent/50 rounded-t-md"
|
|
140
|
+
>
|
|
141
|
+
<ChevronDown className={cn("w-3.5 h-3.5 shrink-0 transition-transform", !open && "-rotate-90")} />
|
|
142
|
+
<span className="truncate flex-1 font-medium">{label}</span>
|
|
143
|
+
<div className="flex items-center gap-1">
|
|
144
|
+
{errorCount > 0 && (
|
|
145
|
+
<Badge
|
|
146
|
+
variant="outline"
|
|
147
|
+
className="text-[10px] px-1.5 py-0 h-4 leading-4 border-destructive/40 text-destructive bg-card"
|
|
148
|
+
>
|
|
149
|
+
{errorCount}
|
|
150
|
+
</Badge>
|
|
151
|
+
)}
|
|
152
|
+
{warningCount > 0 && (
|
|
153
|
+
<Badge
|
|
154
|
+
variant="outline"
|
|
155
|
+
className="text-[10px] px-1.5 py-0 h-4 leading-4 border-warning/40 text-warning bg-card"
|
|
156
|
+
>
|
|
157
|
+
{warningCount}
|
|
158
|
+
</Badge>
|
|
159
|
+
)}
|
|
160
|
+
</div>
|
|
161
|
+
</button>
|
|
162
|
+
{open && (
|
|
163
|
+
<div className="border-t border-border/40 px-3 py-1.5 flex flex-col gap-0.5">
|
|
164
|
+
{diagnostics.map((diag, i) => (
|
|
165
|
+
<button
|
|
166
|
+
key={i}
|
|
167
|
+
type="button"
|
|
168
|
+
onClick={() => onSelectDiagnostic?.(diag)}
|
|
169
|
+
disabled={!onSelectDiagnostic}
|
|
170
|
+
className="flex items-start gap-1.5 px-1 py-1 text-xs text-left rounded transition-colors enabled:hover:bg-accent/50 enabled:cursor-pointer disabled:cursor-default"
|
|
171
|
+
>
|
|
172
|
+
{diag.severity === "error" ? (
|
|
173
|
+
<AlertTriangle className="w-3.5 h-3.5 text-destructive shrink-0 mt-0.5" />
|
|
174
|
+
) : (
|
|
175
|
+
<AlertCircle className="w-3.5 h-3.5 text-warning shrink-0 mt-0.5" />
|
|
176
|
+
)}
|
|
177
|
+
<span className="text-muted-foreground">{diag.message}</span>
|
|
178
|
+
</button>
|
|
179
|
+
))}
|
|
180
|
+
</div>
|
|
181
|
+
)}
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
}
|