@camstack/ui-library 0.1.25 → 0.1.26
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/index.cjs +1736 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +279 -2
- package/dist/index.d.ts +279 -2
- package/dist/index.js +1710 -0
- package/dist/index.js.map +1 -1
- package/package.json +10 -1
package/dist/index.js
CHANGED
|
@@ -1978,15 +1978,1715 @@ function DeviceGrid({
|
|
|
1978
1978
|
}
|
|
1979
1979
|
);
|
|
1980
1980
|
}
|
|
1981
|
+
|
|
1982
|
+
// src/composites/pipeline-step.tsx
|
|
1983
|
+
import { useState as useState9 } from "react";
|
|
1984
|
+
import { ChevronRight as ChevronRight3, ChevronDown as ChevronDown2 } from "lucide-react";
|
|
1985
|
+
import { jsx as jsx39, jsxs as jsxs23 } from "react/jsx-runtime";
|
|
1986
|
+
var ADDON_COLORS = {
|
|
1987
|
+
"object-detection": "border-l-blue-500",
|
|
1988
|
+
"motion-detection": "border-l-amber-500",
|
|
1989
|
+
"face-detection": "border-l-purple-500",
|
|
1990
|
+
"face-recognition": "border-l-violet-500",
|
|
1991
|
+
"plate-detection": "border-l-pink-500",
|
|
1992
|
+
"plate-recognition": "border-l-rose-500",
|
|
1993
|
+
"animal-classifier": "border-l-lime-500",
|
|
1994
|
+
"bird-nabirds-classifier": "border-l-emerald-500",
|
|
1995
|
+
"bird-global-classifier": "border-l-teal-500",
|
|
1996
|
+
"audio-classification": "border-l-green-500",
|
|
1997
|
+
"segmentation-refiner": "border-l-cyan-500"
|
|
1998
|
+
};
|
|
1999
|
+
var SLOT_FALLBACK = {
|
|
2000
|
+
detector: "border-l-blue-500",
|
|
2001
|
+
cropper: "border-l-purple-500",
|
|
2002
|
+
classifier: "border-l-lime-500",
|
|
2003
|
+
refiner: "border-l-cyan-500"
|
|
2004
|
+
};
|
|
2005
|
+
function borderColor(addonId, slot) {
|
|
2006
|
+
return ADDON_COLORS[addonId] ?? SLOT_FALLBACK[slot] ?? "border-l-primary";
|
|
2007
|
+
}
|
|
2008
|
+
var BACKEND_FORMAT = {
|
|
2009
|
+
cpu: "onnx",
|
|
2010
|
+
coreml: "coreml",
|
|
2011
|
+
openvino: "openvino",
|
|
2012
|
+
cuda: "onnx",
|
|
2013
|
+
tensorrt: "onnx",
|
|
2014
|
+
"onnx-py": "onnx",
|
|
2015
|
+
pytorch: "pt"
|
|
2016
|
+
};
|
|
2017
|
+
function backendsForRuntime(runtime, caps, schema) {
|
|
2018
|
+
const allBackends = runtime === "node" ? caps.runtimes.node.backends : caps.runtimes.python.backends;
|
|
2019
|
+
if (!schema) return allBackends;
|
|
2020
|
+
const availableFormats = /* @__PURE__ */ new Set();
|
|
2021
|
+
for (const m of schema.models) {
|
|
2022
|
+
for (const fmt of Object.keys(m.formats)) {
|
|
2023
|
+
availableFormats.add(fmt);
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
2026
|
+
if (runtime === "node") availableFormats.add("onnx");
|
|
2027
|
+
return allBackends.map((b) => {
|
|
2028
|
+
const neededFormat = BACKEND_FORMAT[b.id] ?? "onnx";
|
|
2029
|
+
const hasFormat = availableFormats.has(neededFormat);
|
|
2030
|
+
return { ...b, available: b.available && hasFormat };
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
function modelsForStep(schema, runtime, backend, caps) {
|
|
2034
|
+
if (!schema) return [];
|
|
2035
|
+
const fmt = runtime === "node" ? "onnx" : caps.runtimes.python.backends.find((b) => b.id === backend)?.modelFormat ?? "onnx";
|
|
2036
|
+
return schema.models.filter((m) => m.formats[fmt]).map((m) => ({
|
|
2037
|
+
id: m.id,
|
|
2038
|
+
name: m.name,
|
|
2039
|
+
downloaded: m.formats[fmt]?.downloaded ?? false
|
|
2040
|
+
}));
|
|
2041
|
+
}
|
|
2042
|
+
function runtimeOptions(caps) {
|
|
2043
|
+
const opts = [
|
|
2044
|
+
{ id: "node", label: "Node.js (ONNX)", available: true }
|
|
2045
|
+
];
|
|
2046
|
+
if (caps.runtimes.python.available && caps.runtimes.python.backends.some((b) => b.available)) {
|
|
2047
|
+
opts.unshift({ id: "python", label: "Python", available: true });
|
|
2048
|
+
}
|
|
2049
|
+
return opts;
|
|
2050
|
+
}
|
|
2051
|
+
function PipelineStep({
|
|
2052
|
+
step,
|
|
2053
|
+
schema,
|
|
2054
|
+
allSchemas,
|
|
2055
|
+
capabilities,
|
|
2056
|
+
depth = 0,
|
|
2057
|
+
onChange,
|
|
2058
|
+
onDelete,
|
|
2059
|
+
readOnly = false
|
|
2060
|
+
}) {
|
|
2061
|
+
const [expanded, setExpanded] = useState9(false);
|
|
2062
|
+
const color = borderColor(step.addonId, step.slot);
|
|
2063
|
+
const backends = backendsForRuntime(step.runtime, capabilities, schema);
|
|
2064
|
+
const rtOptions = runtimeOptions(capabilities);
|
|
2065
|
+
const currentBackendAvailable = backends.find((b) => b.id === step.backend)?.available ?? false;
|
|
2066
|
+
if (!currentBackendAvailable && !readOnly) {
|
|
2067
|
+
const firstAvailable = backends.find((b) => b.available);
|
|
2068
|
+
if (firstAvailable && firstAvailable.id !== step.backend) {
|
|
2069
|
+
queueMicrotask(() => onChange({ ...step, backend: firstAvailable.id }));
|
|
2070
|
+
}
|
|
2071
|
+
}
|
|
2072
|
+
const models = modelsForStep(schema, step.runtime, step.backend, capabilities);
|
|
2073
|
+
if (models.length > 0 && !models.some((m) => m.id === step.modelId) && !readOnly) {
|
|
2074
|
+
const defaultModel = schema?.defaultModelId ? models.find((m) => m.id === schema.defaultModelId) : null;
|
|
2075
|
+
const fallback = defaultModel ?? models[0];
|
|
2076
|
+
if (fallback && fallback.id !== step.modelId) {
|
|
2077
|
+
queueMicrotask(() => onChange({ ...step, modelId: fallback.id }));
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
function handleClick(e) {
|
|
2081
|
+
if (e.target.closest(".step-config")) return;
|
|
2082
|
+
setExpanded((v) => !v);
|
|
2083
|
+
}
|
|
2084
|
+
return /* @__PURE__ */ jsx39("div", { className: "space-y-2", children: /* @__PURE__ */ jsxs23(
|
|
2085
|
+
"div",
|
|
2086
|
+
{
|
|
2087
|
+
className: cn(
|
|
2088
|
+
"rounded-xl border border-border bg-surface overflow-hidden border-l-4 shadow-sm",
|
|
2089
|
+
color,
|
|
2090
|
+
!step.enabled && "opacity-[0.45]"
|
|
2091
|
+
),
|
|
2092
|
+
children: [
|
|
2093
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-2.5 px-3 py-2.5 cursor-pointer select-none", onClick: handleClick, children: [
|
|
2094
|
+
/* @__PURE__ */ jsx39("span", { className: "text-foreground-subtle", children: expanded ? /* @__PURE__ */ jsx39(ChevronDown2, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx39(ChevronRight3, { className: "h-4 w-4" }) }),
|
|
2095
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex-1 min-w-0", children: [
|
|
2096
|
+
/* @__PURE__ */ jsx39("span", { className: "text-[10px] uppercase tracking-wider font-medium text-foreground-subtle/60 block leading-none", children: step.slot }),
|
|
2097
|
+
/* @__PURE__ */ jsx39("span", { className: "text-sm font-semibold text-foreground truncate block leading-tight", children: step.addonName }),
|
|
2098
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex items-center gap-1 mt-0.5 flex-wrap", children: [
|
|
2099
|
+
step.inputClasses.map((c) => /* @__PURE__ */ jsx39("span", { className: "text-[9px] uppercase font-semibold tracking-wide px-1.5 py-0.5 rounded bg-blue-500/12 text-blue-400", children: c }, c)),
|
|
2100
|
+
step.inputClasses.length > 0 && step.outputClasses.length > 0 && /* @__PURE__ */ jsx39("span", { className: "text-foreground-subtle/40 text-[10px]", children: "\u2192" }),
|
|
2101
|
+
step.outputClasses.map((c) => /* @__PURE__ */ jsx39("span", { className: "text-[9px] uppercase font-semibold tracking-wide px-1.5 py-0.5 rounded bg-green-500/12 text-green-400", children: c }, c))
|
|
2102
|
+
] })
|
|
2103
|
+
] }),
|
|
2104
|
+
/* @__PURE__ */ jsx39(
|
|
2105
|
+
"button",
|
|
2106
|
+
{
|
|
2107
|
+
onClick: (e) => {
|
|
2108
|
+
e.stopPropagation();
|
|
2109
|
+
onChange({ ...step, enabled: !step.enabled });
|
|
2110
|
+
},
|
|
2111
|
+
className: cn(
|
|
2112
|
+
"relative inline-flex h-6 w-11 shrink-0 items-center rounded-full transition-colors",
|
|
2113
|
+
step.enabled ? "bg-success" : "bg-foreground-subtle/30"
|
|
2114
|
+
),
|
|
2115
|
+
children: /* @__PURE__ */ jsx39("span", { className: cn(
|
|
2116
|
+
"inline-block h-4 w-4 rounded-full bg-white shadow transition-transform",
|
|
2117
|
+
step.enabled ? "translate-x-6" : "translate-x-1"
|
|
2118
|
+
) })
|
|
2119
|
+
}
|
|
2120
|
+
)
|
|
2121
|
+
] }),
|
|
2122
|
+
expanded && /* @__PURE__ */ jsxs23("div", { className: "step-config border-t border-border bg-background px-4 py-4 space-y-3", children: [
|
|
2123
|
+
/* @__PURE__ */ jsxs23("div", { className: "grid grid-cols-2 gap-3", children: [
|
|
2124
|
+
/* @__PURE__ */ jsx39(
|
|
2125
|
+
ConfigSelect,
|
|
2126
|
+
{
|
|
2127
|
+
label: "Agent",
|
|
2128
|
+
value: step.agentId,
|
|
2129
|
+
disabled: readOnly,
|
|
2130
|
+
options: [{ value: step.agentId || "hub", label: step.agentId || "Hub (local)" }],
|
|
2131
|
+
onChange: (v) => onChange({ ...step, agentId: v })
|
|
2132
|
+
}
|
|
2133
|
+
),
|
|
2134
|
+
/* @__PURE__ */ jsx39(
|
|
2135
|
+
ConfigSelect,
|
|
2136
|
+
{
|
|
2137
|
+
label: "Runtime",
|
|
2138
|
+
value: step.runtime,
|
|
2139
|
+
disabled: readOnly,
|
|
2140
|
+
options: rtOptions.map((r) => ({ value: r.id, label: r.label, disabled: !r.available })),
|
|
2141
|
+
onChange: (v) => onChange({ ...step, runtime: v })
|
|
2142
|
+
}
|
|
2143
|
+
),
|
|
2144
|
+
/* @__PURE__ */ jsx39(
|
|
2145
|
+
ConfigSelect,
|
|
2146
|
+
{
|
|
2147
|
+
label: "Backend",
|
|
2148
|
+
value: step.backend,
|
|
2149
|
+
disabled: readOnly,
|
|
2150
|
+
options: backends.map((b) => ({ value: b.id, label: b.label, disabled: !b.available })),
|
|
2151
|
+
onChange: (v) => onChange({ ...step, backend: v })
|
|
2152
|
+
}
|
|
2153
|
+
),
|
|
2154
|
+
/* @__PURE__ */ jsx39(
|
|
2155
|
+
ConfigSelect,
|
|
2156
|
+
{
|
|
2157
|
+
label: "Model",
|
|
2158
|
+
value: step.modelId,
|
|
2159
|
+
disabled: readOnly,
|
|
2160
|
+
options: models.map((m) => ({ value: m.id, label: `${m.name}${m.downloaded ? " \u2713" : ""}` })),
|
|
2161
|
+
onChange: (v) => onChange({ ...step, modelId: v })
|
|
2162
|
+
}
|
|
2163
|
+
)
|
|
2164
|
+
] }),
|
|
2165
|
+
/* @__PURE__ */ jsxs23("div", { children: [
|
|
2166
|
+
/* @__PURE__ */ jsxs23("div", { className: "flex items-center justify-between mb-1", children: [
|
|
2167
|
+
/* @__PURE__ */ jsx39("span", { className: "text-[10px] font-medium text-foreground-subtle uppercase tracking-wide", children: "Confidence" }),
|
|
2168
|
+
/* @__PURE__ */ jsxs23("span", { className: "text-xs font-medium text-foreground tabular-nums", children: [
|
|
2169
|
+
(step.confidence * 100).toFixed(0),
|
|
2170
|
+
"%"
|
|
2171
|
+
] })
|
|
2172
|
+
] }),
|
|
2173
|
+
/* @__PURE__ */ jsx39(
|
|
2174
|
+
"input",
|
|
2175
|
+
{
|
|
2176
|
+
type: "range",
|
|
2177
|
+
min: 0,
|
|
2178
|
+
max: 1,
|
|
2179
|
+
step: 0.01,
|
|
2180
|
+
value: step.confidence,
|
|
2181
|
+
disabled: readOnly,
|
|
2182
|
+
onChange: (e) => onChange({ ...step, confidence: Number(e.target.value) }),
|
|
2183
|
+
className: "w-full accent-primary h-1.5"
|
|
2184
|
+
}
|
|
2185
|
+
)
|
|
2186
|
+
] })
|
|
2187
|
+
] })
|
|
2188
|
+
]
|
|
2189
|
+
}
|
|
2190
|
+
) });
|
|
2191
|
+
}
|
|
2192
|
+
function ConfigSelect({ label, value, options, disabled, onChange }) {
|
|
2193
|
+
return /* @__PURE__ */ jsxs23("div", { children: [
|
|
2194
|
+
/* @__PURE__ */ jsx39("label", { className: "block text-[10px] font-medium text-foreground-subtle uppercase tracking-wide mb-1.5", children: label }),
|
|
2195
|
+
/* @__PURE__ */ jsxs23(
|
|
2196
|
+
"select",
|
|
2197
|
+
{
|
|
2198
|
+
value,
|
|
2199
|
+
onChange: (e) => onChange(e.target.value),
|
|
2200
|
+
disabled,
|
|
2201
|
+
className: "w-full rounded-lg border border-border bg-surface px-3 py-2 text-xs text-foreground focus:outline-none focus:border-primary/50",
|
|
2202
|
+
children: [
|
|
2203
|
+
options.length === 0 && /* @__PURE__ */ jsx39("option", { value, children: value || "default" }),
|
|
2204
|
+
options.map((o) => /* @__PURE__ */ jsx39("option", { value: o.value, disabled: o.disabled, children: o.label }, o.value))
|
|
2205
|
+
]
|
|
2206
|
+
}
|
|
2207
|
+
)
|
|
2208
|
+
] });
|
|
2209
|
+
}
|
|
2210
|
+
|
|
2211
|
+
// src/composites/pipeline-runtime-selector.tsx
|
|
2212
|
+
import { Cpu, Star } from "lucide-react";
|
|
2213
|
+
import { jsx as jsx40, jsxs as jsxs24 } from "react/jsx-runtime";
|
|
2214
|
+
function PipelineRuntimeSelector({ options, value, onChange }) {
|
|
2215
|
+
return /* @__PURE__ */ jsx40("div", { className: "flex flex-wrap gap-2", children: options.map((opt) => {
|
|
2216
|
+
const active = opt.id === value;
|
|
2217
|
+
return /* @__PURE__ */ jsxs24(
|
|
2218
|
+
"button",
|
|
2219
|
+
{
|
|
2220
|
+
onClick: () => opt.available && onChange(opt.id),
|
|
2221
|
+
disabled: !opt.available,
|
|
2222
|
+
className: `flex items-center gap-2 rounded-lg border px-3 py-2 text-xs font-medium transition-all ${active ? "border-primary/40 bg-primary/10 text-primary" : opt.available ? "border-border bg-surface text-foreground-subtle hover:bg-surface-hover hover:text-foreground" : "border-border/40 bg-surface/40 text-foreground-subtle/40 cursor-not-allowed"}`,
|
|
2223
|
+
children: [
|
|
2224
|
+
/* @__PURE__ */ jsx40(Cpu, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
2225
|
+
opt.label,
|
|
2226
|
+
opt.isBest && /* @__PURE__ */ jsxs24("span", { className: "inline-flex items-center gap-0.5 rounded-full bg-amber-500/15 px-1.5 py-0.5 text-[10px] font-semibold text-amber-400", children: [
|
|
2227
|
+
/* @__PURE__ */ jsx40(Star, { className: "h-2.5 w-2.5" }),
|
|
2228
|
+
"Best"
|
|
2229
|
+
] }),
|
|
2230
|
+
opt.platformScore != null && /* @__PURE__ */ jsxs24("span", { className: "text-[10px] text-foreground-subtle/60", children: [
|
|
2231
|
+
"(",
|
|
2232
|
+
opt.platformScore,
|
|
2233
|
+
")"
|
|
2234
|
+
] }),
|
|
2235
|
+
/* @__PURE__ */ jsx40(
|
|
2236
|
+
"span",
|
|
2237
|
+
{
|
|
2238
|
+
className: `h-1.5 w-1.5 rounded-full ${opt.available ? "bg-success" : "bg-danger"}`
|
|
2239
|
+
}
|
|
2240
|
+
)
|
|
2241
|
+
]
|
|
2242
|
+
},
|
|
2243
|
+
opt.id
|
|
2244
|
+
);
|
|
2245
|
+
}) });
|
|
2246
|
+
}
|
|
2247
|
+
|
|
2248
|
+
// src/composites/pipeline-builder.tsx
|
|
2249
|
+
import { useMemo as useMemo3, useState as useState10 } from "react";
|
|
2250
|
+
import { Save, CopyPlus, Trash2, PlusCircle, X as X2 } from "lucide-react";
|
|
2251
|
+
|
|
2252
|
+
// src/lib/validate-template.ts
|
|
2253
|
+
function validateTemplate(steps, schema) {
|
|
2254
|
+
const availableAddonIds = new Set(
|
|
2255
|
+
schema.slots.flatMap((s) => s.addons.map((a) => a.id))
|
|
2256
|
+
);
|
|
2257
|
+
const warnings = [];
|
|
2258
|
+
function validateStep(step) {
|
|
2259
|
+
if (!availableAddonIds.has(step.addonId)) {
|
|
2260
|
+
warnings.push(`Addon "${step.addonId}" is no longer available \u2014 step removed`);
|
|
2261
|
+
return null;
|
|
2262
|
+
}
|
|
2263
|
+
const addon = schema.slots.flatMap((s) => s.addons).find((a) => a.id === step.addonId);
|
|
2264
|
+
let modelId = step.modelId;
|
|
2265
|
+
if (addon && !addon.models.some((m) => m.id === modelId)) {
|
|
2266
|
+
const fallback = addon.defaultModelId;
|
|
2267
|
+
warnings.push(`Model "${modelId}" not available for ${step.addonId} \u2014 using "${fallback}"`);
|
|
2268
|
+
modelId = fallback;
|
|
2269
|
+
}
|
|
2270
|
+
const validChildren = step.children.map((c) => validateStep(c)).filter((c) => c !== null);
|
|
2271
|
+
return { ...step, modelId, children: validChildren };
|
|
2272
|
+
}
|
|
2273
|
+
const validSteps = steps.map((s) => validateStep(s)).filter((s) => s !== null);
|
|
2274
|
+
return {
|
|
2275
|
+
valid: warnings.length === 0,
|
|
2276
|
+
steps: validSteps,
|
|
2277
|
+
warnings
|
|
2278
|
+
};
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
// src/composites/pipeline-builder.tsx
|
|
2282
|
+
import { jsx as jsx41, jsxs as jsxs25 } from "react/jsx-runtime";
|
|
2283
|
+
function buildSchemaMap(schema) {
|
|
2284
|
+
const map = /* @__PURE__ */ new Map();
|
|
2285
|
+
for (const slot of schema.slots) {
|
|
2286
|
+
for (const addon of slot.addons) {
|
|
2287
|
+
map.set(addon.id, addon);
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
return map;
|
|
2291
|
+
}
|
|
2292
|
+
function createDefaultStep(addon, fallbackRuntime, fallbackBackend) {
|
|
2293
|
+
return {
|
|
2294
|
+
addonId: addon.id,
|
|
2295
|
+
addonName: addon.name,
|
|
2296
|
+
slot: addon.slot,
|
|
2297
|
+
inputClasses: [...addon.inputClasses],
|
|
2298
|
+
outputClasses: [...addon.outputClasses],
|
|
2299
|
+
enabled: true,
|
|
2300
|
+
agentId: "hub",
|
|
2301
|
+
// Use per-addon defaults from backend PlatformScorer, fallback to provided
|
|
2302
|
+
runtime: addon.defaultRuntime ?? fallbackRuntime ?? "node",
|
|
2303
|
+
backend: addon.defaultBackend ?? fallbackBackend ?? "cpu",
|
|
2304
|
+
modelId: addon.defaultModelId,
|
|
2305
|
+
confidence: addon.defaultConfidence,
|
|
2306
|
+
classFilters: [...addon.inputClasses],
|
|
2307
|
+
children: []
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
function PlaceholderStep({ addon, onClick }) {
|
|
2311
|
+
return /* @__PURE__ */ jsx41(
|
|
2312
|
+
"button",
|
|
2313
|
+
{
|
|
2314
|
+
type: "button",
|
|
2315
|
+
onClick,
|
|
2316
|
+
className: "w-full rounded-xl border-2 border-dashed border-border/60 px-4 py-3 text-left transition-all hover:border-primary/30 hover:bg-surface/60 group",
|
|
2317
|
+
children: /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-3", children: [
|
|
2318
|
+
/* @__PURE__ */ jsx41(PlusCircle, { className: "h-[18px] w-[18px] text-foreground-subtle/30 group-hover:text-primary/60 shrink-0" }),
|
|
2319
|
+
/* @__PURE__ */ jsxs25("div", { className: "flex-1 min-w-0", children: [
|
|
2320
|
+
/* @__PURE__ */ jsx41("span", { className: "text-[13px] font-medium text-foreground-subtle/50 group-hover:text-foreground-subtle block truncate", children: addon.name }),
|
|
2321
|
+
/* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-1 mt-0.5 flex-wrap", children: [
|
|
2322
|
+
addon.inputClasses.map((c) => /* @__PURE__ */ jsx41("span", { className: "text-[9px] uppercase font-semibold tracking-wide px-1.5 py-0.5 rounded bg-blue-500/8 text-blue-400/50", children: c }, c)),
|
|
2323
|
+
addon.inputClasses.length > 0 && addon.outputClasses.length > 0 && /* @__PURE__ */ jsx41("span", { className: "text-foreground-subtle/25 text-[10px]", children: "\u2192" }),
|
|
2324
|
+
addon.outputClasses.map((c) => /* @__PURE__ */ jsx41("span", { className: "text-[9px] uppercase font-semibold tracking-wide px-1.5 py-0.5 rounded bg-green-500/8 text-green-400/50", children: c }, c))
|
|
2325
|
+
] })
|
|
2326
|
+
] })
|
|
2327
|
+
] })
|
|
2328
|
+
}
|
|
2329
|
+
);
|
|
2330
|
+
}
|
|
2331
|
+
function PipelineBuilder({
|
|
2332
|
+
schema,
|
|
2333
|
+
capabilities,
|
|
2334
|
+
steps,
|
|
2335
|
+
onChange,
|
|
2336
|
+
templates,
|
|
2337
|
+
selectedTemplateId,
|
|
2338
|
+
onSelectTemplate,
|
|
2339
|
+
onSaveTemplate,
|
|
2340
|
+
onUpdateTemplate,
|
|
2341
|
+
onDeleteTemplate,
|
|
2342
|
+
readOnly = false,
|
|
2343
|
+
excludeAddons = []
|
|
2344
|
+
}) {
|
|
2345
|
+
const excluded = useMemo3(() => new Set(excludeAddons), [excludeAddons]);
|
|
2346
|
+
const schemaMap = useMemo3(() => buildSchemaMap(schema), [schema]);
|
|
2347
|
+
const [warnings, setWarnings] = useState10([]);
|
|
2348
|
+
const bestPlatformScore = capabilities.platformScores?.find((s) => s.available);
|
|
2349
|
+
const hasPython = capabilities.runtimes.python.available && capabilities.runtimes.python.backends.some((b) => b.available);
|
|
2350
|
+
const defaultRuntime = bestPlatformScore?.runtime ?? (hasPython ? "python" : "node");
|
|
2351
|
+
const defaultBackend = bestPlatformScore?.backend ?? (hasPython ? capabilities.runtimes.python.backends.find((b) => b.available)?.id ?? "cpu" : capabilities.runtimes.node.backends.find((b) => b.available && b.id !== "cpu")?.id ?? "cpu");
|
|
2352
|
+
const dirty = selectedTemplateId ? JSON.stringify(steps) !== JSON.stringify(templates.find((t) => t.id === selectedTemplateId)?.steps) : false;
|
|
2353
|
+
function handleSelectTemplate(e) {
|
|
2354
|
+
const id = e.target.value || null;
|
|
2355
|
+
if (id) {
|
|
2356
|
+
const tpl = templates.find((t) => t.id === id);
|
|
2357
|
+
if (tpl) {
|
|
2358
|
+
const result = validateTemplate(tpl.steps, schema);
|
|
2359
|
+
setWarnings([...result.warnings]);
|
|
2360
|
+
}
|
|
2361
|
+
} else {
|
|
2362
|
+
setWarnings([]);
|
|
2363
|
+
}
|
|
2364
|
+
onSelectTemplate(id);
|
|
2365
|
+
}
|
|
2366
|
+
function handleSave() {
|
|
2367
|
+
if (selectedTemplateId) onUpdateTemplate(selectedTemplateId, steps);
|
|
2368
|
+
}
|
|
2369
|
+
function handleSaveAs() {
|
|
2370
|
+
const name = window.prompt("Template name:");
|
|
2371
|
+
if (name?.trim()) onSaveTemplate(name.trim(), steps);
|
|
2372
|
+
}
|
|
2373
|
+
function handleDelete() {
|
|
2374
|
+
if (!selectedTemplateId) return;
|
|
2375
|
+
const tpl = templates.find((t) => t.id === selectedTemplateId);
|
|
2376
|
+
if (tpl && window.confirm(`Delete template "${tpl.name}"?`)) {
|
|
2377
|
+
onDeleteTemplate(selectedTemplateId);
|
|
2378
|
+
}
|
|
2379
|
+
}
|
|
2380
|
+
function handleStepChange(updated) {
|
|
2381
|
+
onChange(steps.map((s) => {
|
|
2382
|
+
if (s.addonId !== updated.addonId) return s;
|
|
2383
|
+
return autoEnableAncestors(updated);
|
|
2384
|
+
}));
|
|
2385
|
+
}
|
|
2386
|
+
function autoEnableAncestors(step) {
|
|
2387
|
+
const hasEnabledChild = step.children.some((c) => c.enabled || c.children.some((gc) => gc.enabled));
|
|
2388
|
+
return {
|
|
2389
|
+
...step,
|
|
2390
|
+
enabled: step.enabled || hasEnabledChild,
|
|
2391
|
+
children: step.children.map((c) => {
|
|
2392
|
+
const hasEnabledGrandchild = c.children.some((gc) => gc.enabled);
|
|
2393
|
+
return {
|
|
2394
|
+
...c,
|
|
2395
|
+
enabled: c.enabled || hasEnabledGrandchild
|
|
2396
|
+
};
|
|
2397
|
+
})
|
|
2398
|
+
};
|
|
2399
|
+
}
|
|
2400
|
+
function handleAddChildToStep(parentAddonId, addon) {
|
|
2401
|
+
const child = createDefaultStep(addon, defaultRuntime, defaultBackend);
|
|
2402
|
+
onChange(steps.map((s) => {
|
|
2403
|
+
if (s.addonId !== parentAddonId) return s;
|
|
2404
|
+
return { ...s, children: [...s.children, child] };
|
|
2405
|
+
}));
|
|
2406
|
+
}
|
|
2407
|
+
function collectIds(list) {
|
|
2408
|
+
const ids = /* @__PURE__ */ new Set();
|
|
2409
|
+
for (const s of list) {
|
|
2410
|
+
ids.add(s.addonId);
|
|
2411
|
+
for (const c of s.children) ids.add(c.addonId);
|
|
2412
|
+
const childIds = collectIds(s.children);
|
|
2413
|
+
for (const id of childIds) ids.add(id);
|
|
2414
|
+
}
|
|
2415
|
+
return ids;
|
|
2416
|
+
}
|
|
2417
|
+
const existingIds = collectIds(steps);
|
|
2418
|
+
function getChildPlaceholders(step) {
|
|
2419
|
+
const stepSchema = schemaMap.get(step.addonId);
|
|
2420
|
+
if (!stepSchema) return [];
|
|
2421
|
+
const childSlotIds = stepSchema.childSlots;
|
|
2422
|
+
const childIds = new Set(step.children.map((c) => c.addonId));
|
|
2423
|
+
const placeholders = [];
|
|
2424
|
+
for (const slot of schema.slots) {
|
|
2425
|
+
if (!childSlotIds.includes(slot.id)) continue;
|
|
2426
|
+
for (const addon of slot.addons) {
|
|
2427
|
+
if (childIds.has(addon.id) || excluded.has(addon.id)) continue;
|
|
2428
|
+
const compatible = addon.inputClasses.some((ic) => step.outputClasses.includes(ic));
|
|
2429
|
+
if (compatible) placeholders.push(addon);
|
|
2430
|
+
}
|
|
2431
|
+
}
|
|
2432
|
+
return placeholders;
|
|
2433
|
+
}
|
|
2434
|
+
function renderStep(step) {
|
|
2435
|
+
const childPlaceholders = getChildPlaceholders(step);
|
|
2436
|
+
return /* @__PURE__ */ jsxs25("div", { className: "space-y-1.5", children: [
|
|
2437
|
+
/* @__PURE__ */ jsx41(
|
|
2438
|
+
PipelineStep,
|
|
2439
|
+
{
|
|
2440
|
+
step,
|
|
2441
|
+
schema: schemaMap.get(step.addonId) ?? null,
|
|
2442
|
+
allSchemas: schemaMap,
|
|
2443
|
+
capabilities,
|
|
2444
|
+
onChange: handleStepChange,
|
|
2445
|
+
onDelete: readOnly ? void 0 : (id) => onChange(steps.filter((s) => s.addonId !== id)),
|
|
2446
|
+
readOnly
|
|
2447
|
+
}
|
|
2448
|
+
),
|
|
2449
|
+
(step.children.length > 0 || childPlaceholders.length > 0) && /* @__PURE__ */ jsxs25("div", { className: "ml-6 pl-4 border-l-2 border-dashed border-border/40 space-y-1.5", children: [
|
|
2450
|
+
/* @__PURE__ */ jsx41("span", { className: "text-[10px] font-semibold uppercase tracking-widest text-foreground-subtle/40", children: "Slot: Cropper / Classifier" }),
|
|
2451
|
+
step.children.map((child) => {
|
|
2452
|
+
const childChildPlaceholders = getChildPlaceholders(child);
|
|
2453
|
+
return /* @__PURE__ */ jsxs25("div", { className: "space-y-1.5", children: [
|
|
2454
|
+
/* @__PURE__ */ jsx41(
|
|
2455
|
+
PipelineStep,
|
|
2456
|
+
{
|
|
2457
|
+
step: child,
|
|
2458
|
+
schema: schemaMap.get(child.addonId) ?? null,
|
|
2459
|
+
allSchemas: schemaMap,
|
|
2460
|
+
capabilities,
|
|
2461
|
+
depth: 1,
|
|
2462
|
+
onChange: (updated) => {
|
|
2463
|
+
handleStepChange({
|
|
2464
|
+
...step,
|
|
2465
|
+
children: step.children.map((c) => c.addonId === updated.addonId ? updated : c)
|
|
2466
|
+
});
|
|
2467
|
+
},
|
|
2468
|
+
onDelete: readOnly ? void 0 : (id) => {
|
|
2469
|
+
handleStepChange({
|
|
2470
|
+
...step,
|
|
2471
|
+
children: step.children.filter((c) => c.addonId !== id)
|
|
2472
|
+
});
|
|
2473
|
+
},
|
|
2474
|
+
readOnly
|
|
2475
|
+
}
|
|
2476
|
+
),
|
|
2477
|
+
(child.children.length > 0 || childChildPlaceholders.length > 0) && /* @__PURE__ */ jsxs25("div", { className: "ml-6 pl-4 border-l-2 border-dashed border-border/30 space-y-1.5", children: [
|
|
2478
|
+
/* @__PURE__ */ jsx41("span", { className: "text-[10px] font-semibold uppercase tracking-widest text-foreground-subtle/30", children: "Slot: Recognizer" }),
|
|
2479
|
+
child.children.map((grandchild) => /* @__PURE__ */ jsx41(
|
|
2480
|
+
PipelineStep,
|
|
2481
|
+
{
|
|
2482
|
+
step: grandchild,
|
|
2483
|
+
schema: schemaMap.get(grandchild.addonId) ?? null,
|
|
2484
|
+
allSchemas: schemaMap,
|
|
2485
|
+
capabilities,
|
|
2486
|
+
depth: 2,
|
|
2487
|
+
onChange: (updatedGrandchild) => {
|
|
2488
|
+
handleStepChange({
|
|
2489
|
+
...step,
|
|
2490
|
+
children: step.children.map(
|
|
2491
|
+
(c) => c.addonId === child.addonId ? { ...c, children: c.children.map((gc) => gc.addonId === updatedGrandchild.addonId ? updatedGrandchild : gc) } : c
|
|
2492
|
+
)
|
|
2493
|
+
});
|
|
2494
|
+
},
|
|
2495
|
+
onDelete: readOnly ? void 0 : (id) => {
|
|
2496
|
+
handleStepChange({
|
|
2497
|
+
...step,
|
|
2498
|
+
children: step.children.map(
|
|
2499
|
+
(c) => c.addonId === child.addonId ? { ...c, children: c.children.filter((gc) => gc.addonId !== id) } : c
|
|
2500
|
+
)
|
|
2501
|
+
});
|
|
2502
|
+
},
|
|
2503
|
+
readOnly
|
|
2504
|
+
},
|
|
2505
|
+
grandchild.addonId
|
|
2506
|
+
)),
|
|
2507
|
+
!readOnly && childChildPlaceholders.map((addon) => /* @__PURE__ */ jsx41(
|
|
2508
|
+
PlaceholderStep,
|
|
2509
|
+
{
|
|
2510
|
+
addon,
|
|
2511
|
+
onClick: () => {
|
|
2512
|
+
const newChild = createDefaultStep(addon, defaultRuntime, defaultBackend);
|
|
2513
|
+
handleStepChange({
|
|
2514
|
+
...step,
|
|
2515
|
+
children: step.children.map(
|
|
2516
|
+
(c) => c.addonId === child.addonId ? { ...c, children: [...c.children, newChild] } : c
|
|
2517
|
+
)
|
|
2518
|
+
});
|
|
2519
|
+
}
|
|
2520
|
+
},
|
|
2521
|
+
addon.id
|
|
2522
|
+
))
|
|
2523
|
+
] })
|
|
2524
|
+
] }, child.addonId);
|
|
2525
|
+
}),
|
|
2526
|
+
!readOnly && childPlaceholders.map((addon) => /* @__PURE__ */ jsx41(
|
|
2527
|
+
PlaceholderStep,
|
|
2528
|
+
{
|
|
2529
|
+
addon,
|
|
2530
|
+
onClick: () => handleAddChildToStep(step.addonId, addon)
|
|
2531
|
+
},
|
|
2532
|
+
addon.id
|
|
2533
|
+
))
|
|
2534
|
+
] })
|
|
2535
|
+
] }, step.addonId);
|
|
2536
|
+
}
|
|
2537
|
+
const rootSlots = schema.slots.filter((s) => s.parentSlot === null).sort((a, b) => a.priority - b.priority);
|
|
2538
|
+
return /* @__PURE__ */ jsxs25("div", { className: "space-y-4", children: [
|
|
2539
|
+
/* @__PURE__ */ jsx41("div", { className: "rounded-xl border border-border bg-surface p-3", children: /* @__PURE__ */ jsxs25("div", { className: "flex items-center gap-2", children: [
|
|
2540
|
+
/* @__PURE__ */ jsx41("div", { className: "relative flex-1 min-w-0", children: /* @__PURE__ */ jsxs25(
|
|
2541
|
+
"select",
|
|
2542
|
+
{
|
|
2543
|
+
value: selectedTemplateId ?? "",
|
|
2544
|
+
onChange: handleSelectTemplate,
|
|
2545
|
+
className: "w-full rounded-lg border border-border bg-background px-3 py-2 text-sm text-foreground focus:outline-none focus:border-primary/50",
|
|
2546
|
+
children: [
|
|
2547
|
+
/* @__PURE__ */ jsx41("option", { value: "", children: "No template" }),
|
|
2548
|
+
templates.map((t) => /* @__PURE__ */ jsx41("option", { value: t.id, children: t.name }, t.id))
|
|
2549
|
+
]
|
|
2550
|
+
}
|
|
2551
|
+
) }),
|
|
2552
|
+
dirty && /* @__PURE__ */ jsx41("span", { className: "h-1.5 w-1.5 rounded-full bg-amber-500 shrink-0" }),
|
|
2553
|
+
/* @__PURE__ */ jsx41(
|
|
2554
|
+
"button",
|
|
2555
|
+
{
|
|
2556
|
+
onClick: handleSave,
|
|
2557
|
+
disabled: !selectedTemplateId || readOnly,
|
|
2558
|
+
title: "Save",
|
|
2559
|
+
className: cn(
|
|
2560
|
+
"p-2 rounded-lg border border-border transition-colors",
|
|
2561
|
+
selectedTemplateId && !readOnly ? "text-foreground-subtle hover:bg-surface-hover" : "text-foreground-subtle/30 cursor-not-allowed"
|
|
2562
|
+
),
|
|
2563
|
+
children: /* @__PURE__ */ jsx41(Save, { className: "h-4 w-4" })
|
|
2564
|
+
}
|
|
2565
|
+
),
|
|
2566
|
+
/* @__PURE__ */ jsx41(
|
|
2567
|
+
"button",
|
|
2568
|
+
{
|
|
2569
|
+
onClick: handleSaveAs,
|
|
2570
|
+
disabled: readOnly,
|
|
2571
|
+
title: "Save As",
|
|
2572
|
+
className: cn(
|
|
2573
|
+
"p-2 rounded-lg border border-border transition-colors",
|
|
2574
|
+
!readOnly ? "text-foreground-subtle hover:bg-surface-hover" : "text-foreground-subtle/30 cursor-not-allowed"
|
|
2575
|
+
),
|
|
2576
|
+
children: /* @__PURE__ */ jsx41(CopyPlus, { className: "h-4 w-4" })
|
|
2577
|
+
}
|
|
2578
|
+
),
|
|
2579
|
+
/* @__PURE__ */ jsx41(
|
|
2580
|
+
"button",
|
|
2581
|
+
{
|
|
2582
|
+
onClick: handleDelete,
|
|
2583
|
+
disabled: !selectedTemplateId || readOnly,
|
|
2584
|
+
title: "Delete",
|
|
2585
|
+
className: cn(
|
|
2586
|
+
"p-2 rounded-lg border border-border transition-colors",
|
|
2587
|
+
selectedTemplateId && !readOnly ? "text-foreground-subtle hover:text-danger" : "text-foreground-subtle/30 cursor-not-allowed"
|
|
2588
|
+
),
|
|
2589
|
+
children: /* @__PURE__ */ jsx41(Trash2, { className: "h-4 w-4" })
|
|
2590
|
+
}
|
|
2591
|
+
)
|
|
2592
|
+
] }) }),
|
|
2593
|
+
warnings.length > 0 && /* @__PURE__ */ jsxs25("div", { className: "rounded-lg border border-amber-500/30 bg-amber-500/5 p-3 text-xs text-amber-400 space-y-1", children: [
|
|
2594
|
+
/* @__PURE__ */ jsxs25("div", { className: "flex items-center justify-between", children: [
|
|
2595
|
+
/* @__PURE__ */ jsx41("span", { className: "font-medium", children: "Template loaded with warnings:" }),
|
|
2596
|
+
/* @__PURE__ */ jsx41("button", { onClick: () => setWarnings([]), className: "text-amber-400/60 hover:text-amber-400", children: /* @__PURE__ */ jsx41(X2, { className: "h-3.5 w-3.5" }) })
|
|
2597
|
+
] }),
|
|
2598
|
+
warnings.map((w, i) => /* @__PURE__ */ jsxs25("div", { children: [
|
|
2599
|
+
"\u2022 ",
|
|
2600
|
+
w
|
|
2601
|
+
] }, i))
|
|
2602
|
+
] }),
|
|
2603
|
+
rootSlots.map((slot) => {
|
|
2604
|
+
const slotSteps = steps.filter((s) => s.slot === slot.id && !excluded.has(s.addonId));
|
|
2605
|
+
const missingRootAddons = slot.addons.filter((a) => !existingIds.has(a.id) && !excluded.has(a.id));
|
|
2606
|
+
return /* @__PURE__ */ jsxs25("div", { className: "space-y-2", children: [
|
|
2607
|
+
/* @__PURE__ */ jsxs25("span", { className: "text-[10px] font-semibold uppercase tracking-widest text-foreground-subtle/50", children: [
|
|
2608
|
+
"Slot: ",
|
|
2609
|
+
slot.label
|
|
2610
|
+
] }),
|
|
2611
|
+
slotSteps.map((step) => renderStep(step)),
|
|
2612
|
+
!readOnly && missingRootAddons.map((addon) => /* @__PURE__ */ jsx41(PlaceholderStep, { addon, onClick: () => {
|
|
2613
|
+
onChange([...steps, createDefaultStep(addon, defaultRuntime, defaultBackend)]);
|
|
2614
|
+
} }, addon.id))
|
|
2615
|
+
] }, slot.id);
|
|
2616
|
+
})
|
|
2617
|
+
] });
|
|
2618
|
+
}
|
|
2619
|
+
|
|
2620
|
+
// src/composites/detection-colors.ts
|
|
2621
|
+
var CLASS_COLORS = {
|
|
2622
|
+
// Primary detection classes
|
|
2623
|
+
person: "#3b82f6",
|
|
2624
|
+
// blue-500
|
|
2625
|
+
vehicle: "#f59e0b",
|
|
2626
|
+
// amber-500
|
|
2627
|
+
animal: "#22c55e",
|
|
2628
|
+
// green-500
|
|
2629
|
+
// Sub-detection classes
|
|
2630
|
+
face: "#a855f7",
|
|
2631
|
+
// purple-500
|
|
2632
|
+
plate: "#ec4899",
|
|
2633
|
+
// pink-500
|
|
2634
|
+
// Specific animal types
|
|
2635
|
+
bird: "#14b8a6",
|
|
2636
|
+
// teal-500
|
|
2637
|
+
dog: "#84cc16",
|
|
2638
|
+
// lime-500
|
|
2639
|
+
cat: "#f97316",
|
|
2640
|
+
// orange-500
|
|
2641
|
+
// Specific vehicle types
|
|
2642
|
+
car: "#f59e0b",
|
|
2643
|
+
// amber-500
|
|
2644
|
+
truck: "#d97706",
|
|
2645
|
+
// amber-600
|
|
2646
|
+
bus: "#b45309",
|
|
2647
|
+
// amber-700
|
|
2648
|
+
motorcycle: "#eab308",
|
|
2649
|
+
// yellow-500
|
|
2650
|
+
bicycle: "#ca8a04",
|
|
2651
|
+
// yellow-600
|
|
2652
|
+
// Other
|
|
2653
|
+
motion: "#facc15"
|
|
2654
|
+
// yellow-400
|
|
2655
|
+
};
|
|
2656
|
+
var FALLBACK_PALETTE = [
|
|
2657
|
+
"#ef4444",
|
|
2658
|
+
// red-500
|
|
2659
|
+
"#8b5cf6",
|
|
2660
|
+
// violet-500
|
|
2661
|
+
"#06b6d4",
|
|
2662
|
+
// cyan-500
|
|
2663
|
+
"#f97316",
|
|
2664
|
+
// orange-500
|
|
2665
|
+
"#10b981",
|
|
2666
|
+
// emerald-500
|
|
2667
|
+
"#6366f1",
|
|
2668
|
+
// indigo-500
|
|
2669
|
+
"#e11d48",
|
|
2670
|
+
// rose-600
|
|
2671
|
+
"#0891b2",
|
|
2672
|
+
// cyan-600
|
|
2673
|
+
"#7c3aed",
|
|
2674
|
+
// violet-600
|
|
2675
|
+
"#059669"
|
|
2676
|
+
// emerald-600
|
|
2677
|
+
];
|
|
2678
|
+
var DEFAULT_COLOR = "#f59e42";
|
|
2679
|
+
function getClassColor(className, customColors) {
|
|
2680
|
+
if (customColors?.[className]) return customColors[className];
|
|
2681
|
+
if (customColors?.[className.toLowerCase()]) return customColors[className.toLowerCase()];
|
|
2682
|
+
if (CLASS_COLORS[className]) return CLASS_COLORS[className];
|
|
2683
|
+
if (CLASS_COLORS[className.toLowerCase()]) return CLASS_COLORS[className.toLowerCase()];
|
|
2684
|
+
let hash = 0;
|
|
2685
|
+
for (let i = 0; i < className.length; i++) {
|
|
2686
|
+
hash = hash * 31 + (className.codePointAt(i) ?? 0) >>> 0;
|
|
2687
|
+
}
|
|
2688
|
+
return FALLBACK_PALETTE[hash % FALLBACK_PALETTE.length] ?? DEFAULT_COLOR;
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
// src/composites/detection-canvas.tsx
|
|
2692
|
+
import { useRef as useRef6, useEffect as useEffect6 } from "react";
|
|
2693
|
+
import { Fragment as Fragment2, jsx as jsx42, jsxs as jsxs26 } from "react/jsx-runtime";
|
|
2694
|
+
var DEFAULT_CLASS_COLORS = CLASS_COLORS;
|
|
2695
|
+
function DetectionCanvas({
|
|
2696
|
+
src,
|
|
2697
|
+
imageWidth,
|
|
2698
|
+
imageHeight,
|
|
2699
|
+
detections = [],
|
|
2700
|
+
classColors,
|
|
2701
|
+
aspectRatio,
|
|
2702
|
+
className,
|
|
2703
|
+
placeholder,
|
|
2704
|
+
showConfidence = true,
|
|
2705
|
+
minConfidence = 0,
|
|
2706
|
+
borderWidth = 2
|
|
2707
|
+
}) {
|
|
2708
|
+
function getColor(className2) {
|
|
2709
|
+
return getClassColor(className2, classColors);
|
|
2710
|
+
}
|
|
2711
|
+
const ratio = aspectRatio ?? (imageWidth && imageHeight ? `${imageWidth}/${imageHeight}` : "16/9");
|
|
2712
|
+
const filteredDetections = detections.filter((d) => d.confidence >= minConfidence);
|
|
2713
|
+
return /* @__PURE__ */ jsx42(
|
|
2714
|
+
"div",
|
|
2715
|
+
{
|
|
2716
|
+
className: cn(
|
|
2717
|
+
"rounded-lg border border-border bg-surface overflow-hidden relative",
|
|
2718
|
+
className
|
|
2719
|
+
),
|
|
2720
|
+
style: { aspectRatio: ratio },
|
|
2721
|
+
children: src ? /* @__PURE__ */ jsxs26(Fragment2, { children: [
|
|
2722
|
+
/* @__PURE__ */ jsx42("img", { src, className: "absolute inset-0 w-full h-full object-fill", alt: "" }),
|
|
2723
|
+
filteredDetections.map(
|
|
2724
|
+
(d, i) => d.mask && d.maskWidth && d.maskHeight ? /* @__PURE__ */ jsx42(
|
|
2725
|
+
MaskOverlay,
|
|
2726
|
+
{
|
|
2727
|
+
mask: d.mask,
|
|
2728
|
+
maskWidth: d.maskWidth,
|
|
2729
|
+
maskHeight: d.maskHeight,
|
|
2730
|
+
bbox: d.bbox,
|
|
2731
|
+
imageWidth,
|
|
2732
|
+
imageHeight,
|
|
2733
|
+
color: getColor(d.className)
|
|
2734
|
+
},
|
|
2735
|
+
`mask-${i}`
|
|
2736
|
+
) : null
|
|
2737
|
+
),
|
|
2738
|
+
filteredDetections.map((d, i) => /* @__PURE__ */ jsx42(
|
|
2739
|
+
BoundingBox,
|
|
2740
|
+
{
|
|
2741
|
+
detection: d,
|
|
2742
|
+
imageWidth,
|
|
2743
|
+
imageHeight,
|
|
2744
|
+
color: getColor(d.className),
|
|
2745
|
+
showConfidence,
|
|
2746
|
+
borderWidth: d.mask ? 1 : borderWidth,
|
|
2747
|
+
children: d.children?.filter((c) => {
|
|
2748
|
+
if (c.confidence < minConfidence) return false;
|
|
2749
|
+
const [cx1, cy1, cx2, cy2] = c.bbox;
|
|
2750
|
+
const cw = cx2 - cx1;
|
|
2751
|
+
const ch = cy2 - cy1;
|
|
2752
|
+
if (cw <= 0 || ch <= 0) return false;
|
|
2753
|
+
const [px1, py1, px2, py2] = d.bbox;
|
|
2754
|
+
const pw = px2 - px1;
|
|
2755
|
+
const ph = py2 - py1;
|
|
2756
|
+
if (pw > 0 && ph > 0 && cw * ch / (pw * ph) > 0.8) return false;
|
|
2757
|
+
return true;
|
|
2758
|
+
}).map((child, j) => /* @__PURE__ */ jsx42(
|
|
2759
|
+
ChildBoundingBox,
|
|
2760
|
+
{
|
|
2761
|
+
child,
|
|
2762
|
+
parentBbox: d.bbox,
|
|
2763
|
+
color: getColor(child.className),
|
|
2764
|
+
showConfidence
|
|
2765
|
+
},
|
|
2766
|
+
`child-${j}`
|
|
2767
|
+
))
|
|
2768
|
+
},
|
|
2769
|
+
`det-${i}`
|
|
2770
|
+
))
|
|
2771
|
+
] }) : /* @__PURE__ */ jsx42("div", { className: "w-full h-full flex items-center justify-center text-foreground-subtle text-sm", children: placeholder ?? "No image loaded" })
|
|
2772
|
+
}
|
|
2773
|
+
);
|
|
2774
|
+
}
|
|
2775
|
+
function BoundingBox({
|
|
2776
|
+
detection,
|
|
2777
|
+
imageWidth,
|
|
2778
|
+
imageHeight,
|
|
2779
|
+
color,
|
|
2780
|
+
showConfidence,
|
|
2781
|
+
borderWidth,
|
|
2782
|
+
children
|
|
2783
|
+
}) {
|
|
2784
|
+
const [x1, y1, x2, y2] = detection.bbox;
|
|
2785
|
+
const labelCount = 1 + (detection.labelsData?.length ?? 0);
|
|
2786
|
+
const labelHeightPx = labelCount * 16 + 4;
|
|
2787
|
+
const topPct = y1 / imageHeight * 100;
|
|
2788
|
+
const containerRef = useRef6(null);
|
|
2789
|
+
const showBelow = topPct < labelHeightPx / imageHeight * 100 * 1.5;
|
|
2790
|
+
const labelsElement = /* @__PURE__ */ jsxs26(
|
|
2791
|
+
"div",
|
|
2792
|
+
{
|
|
2793
|
+
className: `absolute left-0 flex flex-col items-start gap-px ${showBelow ? "" : ""}`,
|
|
2794
|
+
style: showBelow ? { top: "100%", marginTop: "2px" } : { bottom: "100%", marginBottom: "2px" },
|
|
2795
|
+
children: [
|
|
2796
|
+
/* @__PURE__ */ jsxs26(
|
|
2797
|
+
"span",
|
|
2798
|
+
{
|
|
2799
|
+
className: "text-[10px] px-1 rounded-sm whitespace-nowrap text-white",
|
|
2800
|
+
style: { backgroundColor: color },
|
|
2801
|
+
children: [
|
|
2802
|
+
detection.className,
|
|
2803
|
+
showConfidence && ` ${(detection.confidence * 100).toFixed(0)}%`
|
|
2804
|
+
]
|
|
2805
|
+
}
|
|
2806
|
+
),
|
|
2807
|
+
detection.labelsData?.map((l, k) => /* @__PURE__ */ jsxs26(
|
|
2808
|
+
"span",
|
|
2809
|
+
{
|
|
2810
|
+
className: "text-[9px] font-semibold px-1 rounded-sm whitespace-nowrap text-white",
|
|
2811
|
+
style: { backgroundColor: getClassColor(l.addonId ?? l.label) },
|
|
2812
|
+
children: [
|
|
2813
|
+
l.label,
|
|
2814
|
+
" ",
|
|
2815
|
+
(l.score * 100).toFixed(0),
|
|
2816
|
+
"%"
|
|
2817
|
+
]
|
|
2818
|
+
},
|
|
2819
|
+
k
|
|
2820
|
+
))
|
|
2821
|
+
]
|
|
2822
|
+
}
|
|
2823
|
+
);
|
|
2824
|
+
return /* @__PURE__ */ jsxs26(
|
|
2825
|
+
"div",
|
|
2826
|
+
{
|
|
2827
|
+
ref: containerRef,
|
|
2828
|
+
className: "absolute rounded-sm",
|
|
2829
|
+
style: {
|
|
2830
|
+
left: `${x1 / imageWidth * 100}%`,
|
|
2831
|
+
top: `${y1 / imageHeight * 100}%`,
|
|
2832
|
+
width: `${(x2 - x1) / imageWidth * 100}%`,
|
|
2833
|
+
height: `${(y2 - y1) / imageHeight * 100}%`,
|
|
2834
|
+
borderWidth: `${borderWidth}px`,
|
|
2835
|
+
borderStyle: "solid",
|
|
2836
|
+
borderColor: color
|
|
2837
|
+
},
|
|
2838
|
+
children: [
|
|
2839
|
+
labelsElement,
|
|
2840
|
+
children
|
|
2841
|
+
]
|
|
2842
|
+
}
|
|
2843
|
+
);
|
|
2844
|
+
}
|
|
2845
|
+
function MaskOverlay({
|
|
2846
|
+
mask,
|
|
2847
|
+
maskWidth,
|
|
2848
|
+
maskHeight,
|
|
2849
|
+
bbox,
|
|
2850
|
+
imageWidth,
|
|
2851
|
+
imageHeight,
|
|
2852
|
+
color
|
|
2853
|
+
}) {
|
|
2854
|
+
const canvasRef = useRef6(null);
|
|
2855
|
+
useEffect6(() => {
|
|
2856
|
+
const canvas = canvasRef.current;
|
|
2857
|
+
if (!canvas) return;
|
|
2858
|
+
const ctx = canvas.getContext("2d");
|
|
2859
|
+
if (!ctx) return;
|
|
2860
|
+
const binary = atob(mask);
|
|
2861
|
+
const bytes = new Uint8Array(binary.length);
|
|
2862
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
2863
|
+
const r = parseInt(color.slice(1, 3), 16);
|
|
2864
|
+
const g = parseInt(color.slice(3, 5), 16);
|
|
2865
|
+
const b = parseInt(color.slice(5, 7), 16);
|
|
2866
|
+
canvas.width = maskWidth;
|
|
2867
|
+
canvas.height = maskHeight;
|
|
2868
|
+
const imageData = ctx.createImageData(maskWidth, maskHeight);
|
|
2869
|
+
const totalPixels = maskWidth * maskHeight;
|
|
2870
|
+
for (let i = 0; i < totalPixels; i++) {
|
|
2871
|
+
const val = i < bytes.length ? bytes[i] : 0;
|
|
2872
|
+
const px = i * 4;
|
|
2873
|
+
if (val > 0) {
|
|
2874
|
+
imageData.data[px] = r;
|
|
2875
|
+
imageData.data[px + 1] = g;
|
|
2876
|
+
imageData.data[px + 2] = b;
|
|
2877
|
+
imageData.data[px + 3] = 120;
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
ctx.putImageData(imageData, 0, 0);
|
|
2881
|
+
}, [mask, maskWidth, maskHeight, color]);
|
|
2882
|
+
const [x1, y1, x2, y2] = bbox;
|
|
2883
|
+
return /* @__PURE__ */ jsx42(
|
|
2884
|
+
"canvas",
|
|
2885
|
+
{
|
|
2886
|
+
ref: canvasRef,
|
|
2887
|
+
className: "absolute pointer-events-none",
|
|
2888
|
+
style: {
|
|
2889
|
+
left: `${x1 / imageWidth * 100}%`,
|
|
2890
|
+
top: `${y1 / imageHeight * 100}%`,
|
|
2891
|
+
width: `${(x2 - x1) / imageWidth * 100}%`,
|
|
2892
|
+
height: `${(y2 - y1) / imageHeight * 100}%`,
|
|
2893
|
+
imageRendering: "pixelated"
|
|
2894
|
+
}
|
|
2895
|
+
}
|
|
2896
|
+
);
|
|
2897
|
+
}
|
|
2898
|
+
function ChildBoundingBox({
|
|
2899
|
+
child,
|
|
2900
|
+
parentBbox,
|
|
2901
|
+
color,
|
|
2902
|
+
showConfidence
|
|
2903
|
+
}) {
|
|
2904
|
+
const [px1, py1, px2, py2] = parentBbox;
|
|
2905
|
+
const [cx1, cy1, cx2, cy2] = child.bbox;
|
|
2906
|
+
const pw = px2 - px1;
|
|
2907
|
+
const ph = py2 - py1;
|
|
2908
|
+
if (pw <= 0 || ph <= 0) return null;
|
|
2909
|
+
return /* @__PURE__ */ jsx42(
|
|
2910
|
+
"div",
|
|
2911
|
+
{
|
|
2912
|
+
className: "absolute rounded-sm",
|
|
2913
|
+
style: {
|
|
2914
|
+
left: `${Math.max(0, (cx1 - px1) / pw * 100)}%`,
|
|
2915
|
+
top: `${Math.max(0, (cy1 - py1) / ph * 100)}%`,
|
|
2916
|
+
width: `${Math.min(100, (cx2 - cx1) / pw * 100)}%`,
|
|
2917
|
+
height: `${Math.min(100, (cy2 - cy1) / ph * 100)}%`,
|
|
2918
|
+
borderWidth: "1px",
|
|
2919
|
+
borderStyle: "solid",
|
|
2920
|
+
borderColor: color
|
|
2921
|
+
},
|
|
2922
|
+
children: (() => {
|
|
2923
|
+
const labelCount = 1 + (child.labelsData?.length ?? 0);
|
|
2924
|
+
const relTop = (cy1 - py1) / ph * 100;
|
|
2925
|
+
const showBelow = relTop < labelCount * 6;
|
|
2926
|
+
return /* @__PURE__ */ jsxs26(
|
|
2927
|
+
"div",
|
|
2928
|
+
{
|
|
2929
|
+
className: "absolute left-0 flex flex-col items-start gap-px",
|
|
2930
|
+
style: showBelow ? { top: "100%", marginTop: "1px" } : { bottom: "100%", marginBottom: "1px" },
|
|
2931
|
+
children: [
|
|
2932
|
+
/* @__PURE__ */ jsxs26(
|
|
2933
|
+
"span",
|
|
2934
|
+
{
|
|
2935
|
+
className: "text-[9px] px-0.5 rounded-sm whitespace-nowrap text-white",
|
|
2936
|
+
style: { backgroundColor: color },
|
|
2937
|
+
children: [
|
|
2938
|
+
child.className,
|
|
2939
|
+
showConfidence && ` ${(child.confidence * 100).toFixed(0)}%`
|
|
2940
|
+
]
|
|
2941
|
+
}
|
|
2942
|
+
),
|
|
2943
|
+
child.labelsData?.map((l, k) => /* @__PURE__ */ jsxs26(
|
|
2944
|
+
"span",
|
|
2945
|
+
{
|
|
2946
|
+
className: "text-[8px] font-semibold px-0.5 rounded-sm whitespace-nowrap text-white",
|
|
2947
|
+
style: { backgroundColor: getClassColor(l.addonId ?? l.label) },
|
|
2948
|
+
children: [
|
|
2949
|
+
l.label,
|
|
2950
|
+
" ",
|
|
2951
|
+
(l.score * 100).toFixed(0),
|
|
2952
|
+
"%"
|
|
2953
|
+
]
|
|
2954
|
+
},
|
|
2955
|
+
k
|
|
2956
|
+
))
|
|
2957
|
+
]
|
|
2958
|
+
}
|
|
2959
|
+
);
|
|
2960
|
+
})()
|
|
2961
|
+
}
|
|
2962
|
+
);
|
|
2963
|
+
}
|
|
2964
|
+
|
|
2965
|
+
// src/composites/detection-result-tree.tsx
|
|
2966
|
+
import { jsx as jsx43, jsxs as jsxs27 } from "react/jsx-runtime";
|
|
2967
|
+
function DetectionResultTree({
|
|
2968
|
+
detections,
|
|
2969
|
+
classColors,
|
|
2970
|
+
className,
|
|
2971
|
+
hiddenKeys,
|
|
2972
|
+
onToggleVisibility
|
|
2973
|
+
}) {
|
|
2974
|
+
const colors = classColors;
|
|
2975
|
+
if (detections.length === 0) {
|
|
2976
|
+
return /* @__PURE__ */ jsx43("div", { className: "text-sm text-foreground-subtle italic text-center py-4", children: "No detections" });
|
|
2977
|
+
}
|
|
2978
|
+
return /* @__PURE__ */ jsxs27("div", { className, children: [
|
|
2979
|
+
/* @__PURE__ */ jsxs27("div", { className: "text-xs font-medium text-foreground-subtle uppercase tracking-wide mb-2", children: [
|
|
2980
|
+
"Detections (",
|
|
2981
|
+
detections.length,
|
|
2982
|
+
")"
|
|
2983
|
+
] }),
|
|
2984
|
+
/* @__PURE__ */ jsx43("div", { className: "space-y-2", children: detections.map((d, i) => /* @__PURE__ */ jsx43(
|
|
2985
|
+
DetectionNode,
|
|
2986
|
+
{
|
|
2987
|
+
detection: d,
|
|
2988
|
+
path: String(i),
|
|
2989
|
+
colors,
|
|
2990
|
+
hiddenKeys,
|
|
2991
|
+
onToggleVisibility
|
|
2992
|
+
},
|
|
2993
|
+
i
|
|
2994
|
+
)) })
|
|
2995
|
+
] });
|
|
2996
|
+
}
|
|
2997
|
+
function DetectionNode({
|
|
2998
|
+
detection,
|
|
2999
|
+
path,
|
|
3000
|
+
colors,
|
|
3001
|
+
hiddenKeys,
|
|
3002
|
+
onToggleVisibility
|
|
3003
|
+
}) {
|
|
3004
|
+
const color = getClassColor(detection.className, colors);
|
|
3005
|
+
const isVisible = !hiddenKeys?.has(path);
|
|
3006
|
+
return /* @__PURE__ */ jsxs27("div", { className: `rounded-md border border-border bg-surface p-3 space-y-1 ${isVisible ? "" : "opacity-40"}`, children: [
|
|
3007
|
+
/* @__PURE__ */ jsxs27("div", { className: "flex justify-between items-center", children: [
|
|
3008
|
+
/* @__PURE__ */ jsxs27("div", { className: "flex items-center gap-2", children: [
|
|
3009
|
+
onToggleVisibility && /* @__PURE__ */ jsx43(
|
|
3010
|
+
"input",
|
|
3011
|
+
{
|
|
3012
|
+
type: "checkbox",
|
|
3013
|
+
checked: isVisible,
|
|
3014
|
+
onChange: () => onToggleVisibility(path, !isVisible),
|
|
3015
|
+
className: "h-3.5 w-3.5 rounded border-border accent-primary cursor-pointer shrink-0"
|
|
3016
|
+
}
|
|
3017
|
+
),
|
|
3018
|
+
/* @__PURE__ */ jsx43(
|
|
3019
|
+
"span",
|
|
3020
|
+
{
|
|
3021
|
+
className: "h-2.5 w-2.5 rounded-full shrink-0",
|
|
3022
|
+
style: { backgroundColor: color }
|
|
3023
|
+
}
|
|
3024
|
+
),
|
|
3025
|
+
/* @__PURE__ */ jsx43("span", { className: "text-sm font-medium text-foreground", children: detection.className }),
|
|
3026
|
+
detection.mask && detection.maskWidth && detection.maskHeight && /* @__PURE__ */ jsxs27("span", { className: "text-[9px] font-mono px-1 py-0.5 rounded bg-primary/10 text-primary", children: [
|
|
3027
|
+
"mask ",
|
|
3028
|
+
detection.maskWidth,
|
|
3029
|
+
"x",
|
|
3030
|
+
detection.maskHeight
|
|
3031
|
+
] })
|
|
3032
|
+
] }),
|
|
3033
|
+
/* @__PURE__ */ jsx43(ConfidenceBadge, { confidence: detection.confidence })
|
|
3034
|
+
] }),
|
|
3035
|
+
/* @__PURE__ */ jsxs27("div", { className: "text-[10px] text-foreground-subtle font-mono", children: [
|
|
3036
|
+
"bbox: [",
|
|
3037
|
+
detection.bbox.map((v) => Math.round(v)).join(", "),
|
|
3038
|
+
"]"
|
|
3039
|
+
] }),
|
|
3040
|
+
detection.labelsData && detection.labelsData.length > 0 && /* @__PURE__ */ jsx43("div", { className: "flex flex-wrap gap-1 mt-1", children: detection.labelsData.map((l, k) => /* @__PURE__ */ jsxs27(
|
|
3041
|
+
"span",
|
|
3042
|
+
{
|
|
3043
|
+
className: "inline-flex items-center gap-1 text-[10px] font-medium px-1.5 py-0.5 rounded-full",
|
|
3044
|
+
style: { backgroundColor: getClassColor(l.addonId ?? l.label, colors) + "20", color: getClassColor(l.addonId ?? l.label, colors) },
|
|
3045
|
+
children: [
|
|
3046
|
+
l.label,
|
|
3047
|
+
/* @__PURE__ */ jsxs27("span", { className: "opacity-60", children: [
|
|
3048
|
+
(l.score * 100).toFixed(0),
|
|
3049
|
+
"%"
|
|
3050
|
+
] }),
|
|
3051
|
+
l.addonId && /* @__PURE__ */ jsx43("span", { className: "opacity-40 text-[8px]", children: l.addonId })
|
|
3052
|
+
]
|
|
3053
|
+
},
|
|
3054
|
+
k
|
|
3055
|
+
)) }),
|
|
3056
|
+
detection.children && detection.children.length > 0 && /* @__PURE__ */ jsx43(
|
|
3057
|
+
ChildrenTree,
|
|
3058
|
+
{
|
|
3059
|
+
children: detection.children,
|
|
3060
|
+
parentPath: path,
|
|
3061
|
+
colors,
|
|
3062
|
+
hiddenKeys,
|
|
3063
|
+
onToggleVisibility
|
|
3064
|
+
}
|
|
3065
|
+
)
|
|
3066
|
+
] });
|
|
3067
|
+
}
|
|
3068
|
+
function ChildrenTree({
|
|
3069
|
+
children,
|
|
3070
|
+
parentPath,
|
|
3071
|
+
colors,
|
|
3072
|
+
hiddenKeys,
|
|
3073
|
+
onToggleVisibility
|
|
3074
|
+
}) {
|
|
3075
|
+
return /* @__PURE__ */ jsx43("div", { className: "ml-4 mt-1.5 space-y-1.5 border-l-2 border-border pl-3", children: children.map((child, j) => {
|
|
3076
|
+
const childPath = `${parentPath}.${j}`;
|
|
3077
|
+
const childColor = getClassColor(child.className, colors);
|
|
3078
|
+
const isVisible = !hiddenKeys?.has(childPath);
|
|
3079
|
+
return /* @__PURE__ */ jsxs27("div", { className: `text-xs space-y-0.5 ${isVisible ? "" : "opacity-40"}`, children: [
|
|
3080
|
+
/* @__PURE__ */ jsxs27("div", { className: "flex items-center gap-1.5", children: [
|
|
3081
|
+
onToggleVisibility && /* @__PURE__ */ jsx43(
|
|
3082
|
+
"input",
|
|
3083
|
+
{
|
|
3084
|
+
type: "checkbox",
|
|
3085
|
+
checked: isVisible,
|
|
3086
|
+
onChange: () => onToggleVisibility(childPath, !isVisible),
|
|
3087
|
+
className: "h-3 w-3 rounded border-border accent-primary cursor-pointer shrink-0"
|
|
3088
|
+
}
|
|
3089
|
+
),
|
|
3090
|
+
/* @__PURE__ */ jsx43(
|
|
3091
|
+
"span",
|
|
3092
|
+
{
|
|
3093
|
+
className: "h-1.5 w-1.5 rounded-full shrink-0",
|
|
3094
|
+
style: { backgroundColor: childColor }
|
|
3095
|
+
}
|
|
3096
|
+
),
|
|
3097
|
+
/* @__PURE__ */ jsx43("span", { className: "font-medium", style: { color: childColor }, children: child.className }),
|
|
3098
|
+
/* @__PURE__ */ jsxs27("span", { className: "text-foreground-subtle", children: [
|
|
3099
|
+
(child.confidence * 100).toFixed(0),
|
|
3100
|
+
"%"
|
|
3101
|
+
] }),
|
|
3102
|
+
child.mask && child.maskWidth && child.maskHeight && /* @__PURE__ */ jsxs27("span", { className: "text-[9px] font-mono px-1 py-0.5 rounded bg-primary/10 text-primary", children: [
|
|
3103
|
+
"mask ",
|
|
3104
|
+
child.maskWidth,
|
|
3105
|
+
"x",
|
|
3106
|
+
child.maskHeight
|
|
3107
|
+
] })
|
|
3108
|
+
] }),
|
|
3109
|
+
child.labelsData && child.labelsData.length > 0 && /* @__PURE__ */ jsx43("div", { className: "flex flex-wrap gap-1 ml-5 mt-0.5", children: child.labelsData.map((l, k) => /* @__PURE__ */ jsxs27(
|
|
3110
|
+
"span",
|
|
3111
|
+
{
|
|
3112
|
+
className: "inline-flex items-center gap-0.5 text-[9px] font-medium px-1 py-0.5 rounded-full",
|
|
3113
|
+
style: { backgroundColor: getClassColor(l.addonId ?? l.label, colors) + "20", color: getClassColor(l.addonId ?? l.label, colors) },
|
|
3114
|
+
children: [
|
|
3115
|
+
l.label,
|
|
3116
|
+
" ",
|
|
3117
|
+
/* @__PURE__ */ jsxs27("span", { className: "opacity-60", children: [
|
|
3118
|
+
(l.score * 100).toFixed(0),
|
|
3119
|
+
"%"
|
|
3120
|
+
] })
|
|
3121
|
+
]
|
|
3122
|
+
},
|
|
3123
|
+
k
|
|
3124
|
+
)) }),
|
|
3125
|
+
child.children && child.children.length > 0 && /* @__PURE__ */ jsx43(
|
|
3126
|
+
ChildrenTree,
|
|
3127
|
+
{
|
|
3128
|
+
children: child.children,
|
|
3129
|
+
parentPath: childPath,
|
|
3130
|
+
colors,
|
|
3131
|
+
hiddenKeys,
|
|
3132
|
+
onToggleVisibility
|
|
3133
|
+
}
|
|
3134
|
+
)
|
|
3135
|
+
] }, j);
|
|
3136
|
+
}) });
|
|
3137
|
+
}
|
|
3138
|
+
function ConfidenceBadge({ confidence }) {
|
|
3139
|
+
const level = confidence >= 0.8 ? "bg-success/10 text-success" : confidence >= 0.5 ? "bg-warning/10 text-warning" : "bg-danger/10 text-danger";
|
|
3140
|
+
return /* @__PURE__ */ jsxs27("span", { className: `text-xs font-medium px-2 py-0.5 rounded-full ${level}`, children: [
|
|
3141
|
+
(confidence * 100).toFixed(1),
|
|
3142
|
+
"%"
|
|
3143
|
+
] });
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
// src/composites/step-timings.tsx
|
|
3147
|
+
import { jsx as jsx44, jsxs as jsxs28 } from "react/jsx-runtime";
|
|
3148
|
+
function StepTimings({ timings, totalMs, className }) {
|
|
3149
|
+
const entries = Object.entries(timings);
|
|
3150
|
+
if (entries.length === 0 && totalMs === void 0) return null;
|
|
3151
|
+
return /* @__PURE__ */ jsxs28("div", { className: `rounded-lg border border-border bg-surface p-3 space-y-2 ${className ?? ""}`, children: [
|
|
3152
|
+
/* @__PURE__ */ jsx44("div", { className: "text-xs font-medium text-foreground-subtle uppercase tracking-wide", children: "Timings" }),
|
|
3153
|
+
/* @__PURE__ */ jsxs28("div", { className: "space-y-1 text-xs", children: [
|
|
3154
|
+
entries.map(([step, ms]) => /* @__PURE__ */ jsxs28("div", { className: "flex justify-between", children: [
|
|
3155
|
+
/* @__PURE__ */ jsx44("span", { className: "text-foreground-subtle", children: step }),
|
|
3156
|
+
/* @__PURE__ */ jsxs28("span", { className: "font-mono text-foreground", children: [
|
|
3157
|
+
ms.toFixed(1),
|
|
3158
|
+
"ms"
|
|
3159
|
+
] })
|
|
3160
|
+
] }, step)),
|
|
3161
|
+
totalMs !== void 0 && /* @__PURE__ */ jsxs28("div", { className: "flex justify-between pt-1 border-t border-border font-medium text-foreground", children: [
|
|
3162
|
+
/* @__PURE__ */ jsx44("span", { children: "Total" }),
|
|
3163
|
+
/* @__PURE__ */ jsxs28("span", { className: "font-mono", children: [
|
|
3164
|
+
totalMs.toFixed(1),
|
|
3165
|
+
"ms"
|
|
3166
|
+
] })
|
|
3167
|
+
] })
|
|
3168
|
+
] })
|
|
3169
|
+
] });
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
// src/composites/image-selector.tsx
|
|
3173
|
+
import { jsx as jsx45, jsxs as jsxs29 } from "react/jsx-runtime";
|
|
3174
|
+
function ImageSelector({
|
|
3175
|
+
images,
|
|
3176
|
+
selectedFilename,
|
|
3177
|
+
uploadedName,
|
|
3178
|
+
onSelect,
|
|
3179
|
+
onUpload,
|
|
3180
|
+
className
|
|
3181
|
+
}) {
|
|
3182
|
+
const handleUploadClick = () => {
|
|
3183
|
+
const input = document.createElement("input");
|
|
3184
|
+
input.type = "file";
|
|
3185
|
+
input.accept = "image/*";
|
|
3186
|
+
input.onchange = (ev) => {
|
|
3187
|
+
const file = ev.target.files?.[0];
|
|
3188
|
+
if (!file) return;
|
|
3189
|
+
const reader = new FileReader();
|
|
3190
|
+
reader.onload = (e) => {
|
|
3191
|
+
const dataUrl = e.target?.result;
|
|
3192
|
+
const b64 = dataUrl.split(",")[1];
|
|
3193
|
+
if (b64) onUpload(b64, file.name, dataUrl);
|
|
3194
|
+
};
|
|
3195
|
+
reader.readAsDataURL(file);
|
|
3196
|
+
};
|
|
3197
|
+
input.click();
|
|
3198
|
+
};
|
|
3199
|
+
return /* @__PURE__ */ jsxs29("div", { className: `flex flex-wrap items-center gap-2 ${className ?? ""}`, children: [
|
|
3200
|
+
images.map((img) => /* @__PURE__ */ jsx45(
|
|
3201
|
+
"button",
|
|
3202
|
+
{
|
|
3203
|
+
onClick: () => onSelect(img.filename),
|
|
3204
|
+
className: `px-3 py-1.5 text-xs rounded-md border transition-colors ${selectedFilename === img.filename ? "bg-primary text-primary-foreground border-primary" : "bg-surface border-border text-foreground hover:border-primary/50"}`,
|
|
3205
|
+
children: img.id
|
|
3206
|
+
},
|
|
3207
|
+
img.filename
|
|
3208
|
+
)),
|
|
3209
|
+
/* @__PURE__ */ jsx45(
|
|
3210
|
+
"button",
|
|
3211
|
+
{
|
|
3212
|
+
onClick: handleUploadClick,
|
|
3213
|
+
className: "px-3 py-1.5 text-xs rounded-md border border-border bg-surface text-foreground hover:bg-surface-hover transition-colors",
|
|
3214
|
+
children: "Upload..."
|
|
3215
|
+
}
|
|
3216
|
+
),
|
|
3217
|
+
uploadedName && /* @__PURE__ */ jsx45("span", { className: "text-xs text-foreground-subtle", children: uploadedName })
|
|
3218
|
+
] });
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
// src/composites/inference-config-selector.tsx
|
|
3222
|
+
import { jsx as jsx46, jsxs as jsxs30 } from "react/jsx-runtime";
|
|
3223
|
+
var SELECT_CLASS = "w-full px-3 py-2 text-sm rounded-md border border-border bg-surface text-foreground focus:outline-none focus:ring-2 focus:ring-primary/50";
|
|
3224
|
+
function InferenceConfigSelector({
|
|
3225
|
+
runtime,
|
|
3226
|
+
backend,
|
|
3227
|
+
modelId,
|
|
3228
|
+
agentId = "hub",
|
|
3229
|
+
runtimes,
|
|
3230
|
+
backends,
|
|
3231
|
+
models,
|
|
3232
|
+
agents = [],
|
|
3233
|
+
onRuntimeChange,
|
|
3234
|
+
onBackendChange,
|
|
3235
|
+
onModelChange,
|
|
3236
|
+
onAgentChange,
|
|
3237
|
+
layout = "grid",
|
|
3238
|
+
className,
|
|
3239
|
+
showAgent = false
|
|
3240
|
+
}) {
|
|
3241
|
+
const containerClass = layout === "grid" ? "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4" : layout === "horizontal" ? "flex flex-wrap items-end gap-4" : "space-y-3";
|
|
3242
|
+
return /* @__PURE__ */ jsxs30("div", { className: `${containerClass} ${className ?? ""}`, children: [
|
|
3243
|
+
showAgent && agents.length > 0 && /* @__PURE__ */ jsxs30("label", { className: "space-y-1", children: [
|
|
3244
|
+
/* @__PURE__ */ jsx46("span", { className: "text-xs font-medium text-foreground-subtle", children: "Agent" }),
|
|
3245
|
+
/* @__PURE__ */ jsx46(
|
|
3246
|
+
"select",
|
|
3247
|
+
{
|
|
3248
|
+
value: agentId,
|
|
3249
|
+
onChange: (e) => onAgentChange?.(e.target.value),
|
|
3250
|
+
className: SELECT_CLASS,
|
|
3251
|
+
children: agents.map((a) => /* @__PURE__ */ jsxs30("option", { value: a.id, children: [
|
|
3252
|
+
a.name,
|
|
3253
|
+
" (",
|
|
3254
|
+
a.status,
|
|
3255
|
+
")"
|
|
3256
|
+
] }, a.id))
|
|
3257
|
+
}
|
|
3258
|
+
)
|
|
3259
|
+
] }),
|
|
3260
|
+
/* @__PURE__ */ jsxs30("label", { className: "space-y-1", children: [
|
|
3261
|
+
/* @__PURE__ */ jsx46("span", { className: "text-xs font-medium text-foreground-subtle", children: "Runtime" }),
|
|
3262
|
+
/* @__PURE__ */ jsx46(
|
|
3263
|
+
"select",
|
|
3264
|
+
{
|
|
3265
|
+
value: runtime,
|
|
3266
|
+
onChange: (e) => onRuntimeChange(e.target.value),
|
|
3267
|
+
className: SELECT_CLASS,
|
|
3268
|
+
children: runtimes.map((r) => /* @__PURE__ */ jsxs30("option", { value: r.value, disabled: !r.available, children: [
|
|
3269
|
+
r.label,
|
|
3270
|
+
!r.available ? " (unavailable)" : ""
|
|
3271
|
+
] }, r.value))
|
|
3272
|
+
}
|
|
3273
|
+
)
|
|
3274
|
+
] }),
|
|
3275
|
+
/* @__PURE__ */ jsxs30("label", { className: "space-y-1", children: [
|
|
3276
|
+
/* @__PURE__ */ jsx46("span", { className: "text-xs font-medium text-foreground-subtle", children: "Backend" }),
|
|
3277
|
+
/* @__PURE__ */ jsx46(
|
|
3278
|
+
"select",
|
|
3279
|
+
{
|
|
3280
|
+
value: backend,
|
|
3281
|
+
onChange: (e) => onBackendChange(e.target.value),
|
|
3282
|
+
className: SELECT_CLASS,
|
|
3283
|
+
children: backends.map((b) => /* @__PURE__ */ jsxs30("option", { value: b.id, disabled: !b.available, children: [
|
|
3284
|
+
b.label,
|
|
3285
|
+
!b.available ? " (unavailable)" : ""
|
|
3286
|
+
] }, b.id))
|
|
3287
|
+
}
|
|
3288
|
+
)
|
|
3289
|
+
] }),
|
|
3290
|
+
/* @__PURE__ */ jsxs30("label", { className: "space-y-1", children: [
|
|
3291
|
+
/* @__PURE__ */ jsx46("span", { className: "text-xs font-medium text-foreground-subtle", children: "Model" }),
|
|
3292
|
+
/* @__PURE__ */ jsx46(
|
|
3293
|
+
"select",
|
|
3294
|
+
{
|
|
3295
|
+
value: modelId,
|
|
3296
|
+
onChange: (e) => onModelChange(e.target.value),
|
|
3297
|
+
className: SELECT_CLASS,
|
|
3298
|
+
children: models.length === 0 ? /* @__PURE__ */ jsx46("option", { value: "", children: "No compatible models" }) : models.map((m) => /* @__PURE__ */ jsxs30("option", { value: m.id, children: [
|
|
3299
|
+
m.name,
|
|
3300
|
+
m.downloaded ? " \u2713" : ""
|
|
3301
|
+
] }, m.id))
|
|
3302
|
+
}
|
|
3303
|
+
)
|
|
3304
|
+
] })
|
|
3305
|
+
] });
|
|
3306
|
+
}
|
|
3307
|
+
|
|
3308
|
+
// src/composites/mount-addon-page.tsx
|
|
3309
|
+
import { createElement } from "react";
|
|
3310
|
+
import { createRoot } from "react-dom/client";
|
|
3311
|
+
|
|
3312
|
+
// src/composites/dev-shell.tsx
|
|
3313
|
+
import { createContext as createContext7, useCallback as useCallback8, useContext as useContext7, useMemo as useMemo4, useState as useState12 } from "react";
|
|
3314
|
+
import { createTRPCClient, createWSClient, wsLink, httpLink, splitLink } from "@trpc/client";
|
|
3315
|
+
import superjson from "superjson";
|
|
3316
|
+
|
|
3317
|
+
// src/composites/login-form.tsx
|
|
3318
|
+
import { useState as useState11 } from "react";
|
|
3319
|
+
import { jsx as jsx47, jsxs as jsxs31 } from "react/jsx-runtime";
|
|
3320
|
+
function EyeIcon({ className }) {
|
|
3321
|
+
return /* @__PURE__ */ jsxs31(
|
|
3322
|
+
"svg",
|
|
3323
|
+
{
|
|
3324
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3325
|
+
viewBox: "0 0 24 24",
|
|
3326
|
+
fill: "none",
|
|
3327
|
+
stroke: "currentColor",
|
|
3328
|
+
strokeWidth: "2",
|
|
3329
|
+
strokeLinecap: "round",
|
|
3330
|
+
strokeLinejoin: "round",
|
|
3331
|
+
className,
|
|
3332
|
+
children: [
|
|
3333
|
+
/* @__PURE__ */ jsx47("path", { d: "M2.062 12.348a1 1 0 0 1 0-.696 10.75 10.75 0 0 1 19.876 0 1 1 0 0 1 0 .696 10.75 10.75 0 0 1-19.876 0" }),
|
|
3334
|
+
/* @__PURE__ */ jsx47("circle", { cx: "12", cy: "12", r: "3" })
|
|
3335
|
+
]
|
|
3336
|
+
}
|
|
3337
|
+
);
|
|
3338
|
+
}
|
|
3339
|
+
function EyeOffIcon({ className }) {
|
|
3340
|
+
return /* @__PURE__ */ jsxs31(
|
|
3341
|
+
"svg",
|
|
3342
|
+
{
|
|
3343
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3344
|
+
viewBox: "0 0 24 24",
|
|
3345
|
+
fill: "none",
|
|
3346
|
+
stroke: "currentColor",
|
|
3347
|
+
strokeWidth: "2",
|
|
3348
|
+
strokeLinecap: "round",
|
|
3349
|
+
strokeLinejoin: "round",
|
|
3350
|
+
className,
|
|
3351
|
+
children: [
|
|
3352
|
+
/* @__PURE__ */ jsx47("path", { d: "M10.733 5.076a10.744 10.744 0 0 1 11.205 6.575 1 1 0 0 1 0 .696 10.747 10.747 0 0 1-1.444 2.49" }),
|
|
3353
|
+
/* @__PURE__ */ jsx47("path", { d: "M14.084 14.158a3 3 0 0 1-4.242-4.242" }),
|
|
3354
|
+
/* @__PURE__ */ jsx47("path", { d: "M17.479 17.499a10.75 10.75 0 0 1-15.417-5.151 1 1 0 0 1 0-.696 10.75 10.75 0 0 1 4.446-5.143" }),
|
|
3355
|
+
/* @__PURE__ */ jsx47("path", { d: "m2 2 20 20" })
|
|
3356
|
+
]
|
|
3357
|
+
}
|
|
3358
|
+
);
|
|
3359
|
+
}
|
|
3360
|
+
function SpinnerIcon({ className }) {
|
|
3361
|
+
return /* @__PURE__ */ jsx47(
|
|
3362
|
+
"svg",
|
|
3363
|
+
{
|
|
3364
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3365
|
+
viewBox: "0 0 24 24",
|
|
3366
|
+
fill: "none",
|
|
3367
|
+
stroke: "currentColor",
|
|
3368
|
+
strokeWidth: "2",
|
|
3369
|
+
strokeLinecap: "round",
|
|
3370
|
+
strokeLinejoin: "round",
|
|
3371
|
+
className,
|
|
3372
|
+
children: /* @__PURE__ */ jsx47("path", { d: "M21 12a9 9 0 1 1-6.219-8.56" })
|
|
3373
|
+
}
|
|
3374
|
+
);
|
|
3375
|
+
}
|
|
3376
|
+
function LoginForm({
|
|
3377
|
+
onLogin,
|
|
3378
|
+
serverUrl,
|
|
3379
|
+
logoSrc,
|
|
3380
|
+
error: externalError,
|
|
3381
|
+
className
|
|
3382
|
+
}) {
|
|
3383
|
+
const [username, setUsername] = useState11("");
|
|
3384
|
+
const [password, setPassword] = useState11("");
|
|
3385
|
+
const [showPassword, setShowPassword] = useState11(false);
|
|
3386
|
+
const [submitting, setSubmitting] = useState11(false);
|
|
3387
|
+
const [internalError, setInternalError] = useState11(null);
|
|
3388
|
+
const error = externalError ?? internalError;
|
|
3389
|
+
const handleSubmit = async (e) => {
|
|
3390
|
+
e.preventDefault();
|
|
3391
|
+
if (submitting) return;
|
|
3392
|
+
setInternalError(null);
|
|
3393
|
+
setSubmitting(true);
|
|
3394
|
+
try {
|
|
3395
|
+
await onLogin(username, password);
|
|
3396
|
+
} catch (err) {
|
|
3397
|
+
const message = err instanceof Error ? err.message : "Login failed. Please try again.";
|
|
3398
|
+
setInternalError(message);
|
|
3399
|
+
} finally {
|
|
3400
|
+
setSubmitting(false);
|
|
3401
|
+
}
|
|
3402
|
+
};
|
|
3403
|
+
return /* @__PURE__ */ jsx47(
|
|
3404
|
+
"div",
|
|
3405
|
+
{
|
|
3406
|
+
className: cn(
|
|
3407
|
+
"flex min-h-screen items-center justify-center bg-background p-4",
|
|
3408
|
+
className
|
|
3409
|
+
),
|
|
3410
|
+
children: /* @__PURE__ */ jsxs31("div", { className: "w-full max-w-sm", children: [
|
|
3411
|
+
logoSrc && /* @__PURE__ */ jsx47("div", { className: "flex justify-center mb-8", children: /* @__PURE__ */ jsx47("img", { src: logoSrc, alt: "Logo", className: "h-12" }) }),
|
|
3412
|
+
serverUrl && /* @__PURE__ */ jsx47("p", { className: "mb-4 text-center text-xs text-foreground-subtle truncate", children: serverUrl }),
|
|
3413
|
+
/* @__PURE__ */ jsxs31(
|
|
3414
|
+
"form",
|
|
3415
|
+
{
|
|
3416
|
+
onSubmit: handleSubmit,
|
|
3417
|
+
className: "space-y-4 rounded-xl border border-border bg-surface p-6 shadow-xl shadow-black/10",
|
|
3418
|
+
children: [
|
|
3419
|
+
error && /* @__PURE__ */ jsx47("div", { className: "rounded-md bg-danger/10 border border-danger/20 px-3 py-2 text-xs text-danger", children: error }),
|
|
3420
|
+
/* @__PURE__ */ jsxs31("div", { className: "space-y-1.5", children: [
|
|
3421
|
+
/* @__PURE__ */ jsx47("label", { className: "text-xs font-medium text-foreground-subtle", children: "Username" }),
|
|
3422
|
+
/* @__PURE__ */ jsx47(
|
|
3423
|
+
"input",
|
|
3424
|
+
{
|
|
3425
|
+
type: "text",
|
|
3426
|
+
value: username,
|
|
3427
|
+
onChange: (e) => setUsername(e.target.value),
|
|
3428
|
+
autoComplete: "username",
|
|
3429
|
+
required: true,
|
|
3430
|
+
className: "w-full rounded-lg border border-border bg-background px-3 py-2.5 text-sm text-foreground placeholder:text-foreground-subtle focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
|
|
3431
|
+
}
|
|
3432
|
+
)
|
|
3433
|
+
] }),
|
|
3434
|
+
/* @__PURE__ */ jsxs31("div", { className: "space-y-1.5", children: [
|
|
3435
|
+
/* @__PURE__ */ jsx47("label", { className: "text-xs font-medium text-foreground-subtle", children: "Password" }),
|
|
3436
|
+
/* @__PURE__ */ jsxs31("div", { className: "relative", children: [
|
|
3437
|
+
/* @__PURE__ */ jsx47(
|
|
3438
|
+
"input",
|
|
3439
|
+
{
|
|
3440
|
+
type: showPassword ? "text" : "password",
|
|
3441
|
+
value: password,
|
|
3442
|
+
onChange: (e) => setPassword(e.target.value),
|
|
3443
|
+
autoComplete: "current-password",
|
|
3444
|
+
required: true,
|
|
3445
|
+
className: "w-full rounded-lg border border-border bg-background px-3 py-2.5 pr-10 text-sm text-foreground placeholder:text-foreground-subtle focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary"
|
|
3446
|
+
}
|
|
3447
|
+
),
|
|
3448
|
+
/* @__PURE__ */ jsx47(
|
|
3449
|
+
"button",
|
|
3450
|
+
{
|
|
3451
|
+
type: "button",
|
|
3452
|
+
onClick: () => setShowPassword((prev) => !prev),
|
|
3453
|
+
className: "absolute right-2.5 top-1/2 -translate-y-1/2 text-foreground-subtle hover:text-foreground",
|
|
3454
|
+
tabIndex: -1,
|
|
3455
|
+
children: showPassword ? /* @__PURE__ */ jsx47(EyeOffIcon, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx47(EyeIcon, { className: "h-4 w-4" })
|
|
3456
|
+
}
|
|
3457
|
+
)
|
|
3458
|
+
] })
|
|
3459
|
+
] }),
|
|
3460
|
+
/* @__PURE__ */ jsxs31(
|
|
3461
|
+
"button",
|
|
3462
|
+
{
|
|
3463
|
+
type: "submit",
|
|
3464
|
+
disabled: submitting,
|
|
3465
|
+
className: "w-full rounded-lg bg-primary px-4 py-2.5 text-sm font-semibold text-primary-foreground hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center justify-center gap-2",
|
|
3466
|
+
children: [
|
|
3467
|
+
submitting && /* @__PURE__ */ jsx47(SpinnerIcon, { className: "h-4 w-4 animate-spin" }),
|
|
3468
|
+
submitting ? "Logging in..." : "Log in"
|
|
3469
|
+
]
|
|
3470
|
+
}
|
|
3471
|
+
)
|
|
3472
|
+
]
|
|
3473
|
+
}
|
|
3474
|
+
)
|
|
3475
|
+
] })
|
|
3476
|
+
}
|
|
3477
|
+
);
|
|
3478
|
+
}
|
|
3479
|
+
|
|
3480
|
+
// src/composites/dev-shell.tsx
|
|
3481
|
+
import { jsx as jsx48, jsxs as jsxs32 } from "react/jsx-runtime";
|
|
3482
|
+
var STORAGE_KEY = "camstack_dev_token";
|
|
3483
|
+
var DevShellContext = createContext7(null);
|
|
3484
|
+
function useDevShell() {
|
|
3485
|
+
const ctx = useContext7(DevShellContext);
|
|
3486
|
+
if (!ctx) {
|
|
3487
|
+
throw new Error("useDevShell must be used within a DevShell");
|
|
3488
|
+
}
|
|
3489
|
+
return ctx;
|
|
3490
|
+
}
|
|
3491
|
+
function getStoredToken() {
|
|
3492
|
+
if (typeof window === "undefined") return null;
|
|
3493
|
+
return localStorage.getItem(STORAGE_KEY);
|
|
3494
|
+
}
|
|
3495
|
+
function SunIcon({ className }) {
|
|
3496
|
+
return /* @__PURE__ */ jsxs32(
|
|
3497
|
+
"svg",
|
|
3498
|
+
{
|
|
3499
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3500
|
+
viewBox: "0 0 24 24",
|
|
3501
|
+
fill: "none",
|
|
3502
|
+
stroke: "currentColor",
|
|
3503
|
+
strokeWidth: "2",
|
|
3504
|
+
strokeLinecap: "round",
|
|
3505
|
+
strokeLinejoin: "round",
|
|
3506
|
+
className,
|
|
3507
|
+
children: [
|
|
3508
|
+
/* @__PURE__ */ jsx48("circle", { cx: "12", cy: "12", r: "4" }),
|
|
3509
|
+
/* @__PURE__ */ jsx48("path", { d: "M12 2v2" }),
|
|
3510
|
+
/* @__PURE__ */ jsx48("path", { d: "M12 20v2" }),
|
|
3511
|
+
/* @__PURE__ */ jsx48("path", { d: "m4.93 4.93 1.41 1.41" }),
|
|
3512
|
+
/* @__PURE__ */ jsx48("path", { d: "m17.66 17.66 1.41 1.41" }),
|
|
3513
|
+
/* @__PURE__ */ jsx48("path", { d: "M2 12h2" }),
|
|
3514
|
+
/* @__PURE__ */ jsx48("path", { d: "M20 12h2" }),
|
|
3515
|
+
/* @__PURE__ */ jsx48("path", { d: "m6.34 17.66-1.41 1.41" }),
|
|
3516
|
+
/* @__PURE__ */ jsx48("path", { d: "m19.07 4.93-1.41 1.41" })
|
|
3517
|
+
]
|
|
3518
|
+
}
|
|
3519
|
+
);
|
|
3520
|
+
}
|
|
3521
|
+
function MoonIcon({ className }) {
|
|
3522
|
+
return /* @__PURE__ */ jsx48(
|
|
3523
|
+
"svg",
|
|
3524
|
+
{
|
|
3525
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3526
|
+
viewBox: "0 0 24 24",
|
|
3527
|
+
fill: "none",
|
|
3528
|
+
stroke: "currentColor",
|
|
3529
|
+
strokeWidth: "2",
|
|
3530
|
+
strokeLinecap: "round",
|
|
3531
|
+
strokeLinejoin: "round",
|
|
3532
|
+
className,
|
|
3533
|
+
children: /* @__PURE__ */ jsx48("path", { d: "M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z" })
|
|
3534
|
+
}
|
|
3535
|
+
);
|
|
3536
|
+
}
|
|
3537
|
+
function DevShellInner({
|
|
3538
|
+
children,
|
|
3539
|
+
serverUrl,
|
|
3540
|
+
title,
|
|
3541
|
+
token,
|
|
3542
|
+
onLogout
|
|
3543
|
+
}) {
|
|
3544
|
+
const theme = useThemeMode();
|
|
3545
|
+
const trpc = useMemo4(
|
|
3546
|
+
() => {
|
|
3547
|
+
const wsUrl = serverUrl.replace(/^http/, "ws") + "/trpc";
|
|
3548
|
+
const wsClient = createWSClient({
|
|
3549
|
+
url: wsUrl,
|
|
3550
|
+
connectionParams: () => ({ token })
|
|
3551
|
+
});
|
|
3552
|
+
return createTRPCClient({
|
|
3553
|
+
links: [
|
|
3554
|
+
splitLink({
|
|
3555
|
+
condition: (op) => op.type === "subscription",
|
|
3556
|
+
true: wsLink({ client: wsClient, transformer: superjson }),
|
|
3557
|
+
false: httpLink({
|
|
3558
|
+
url: `${serverUrl}/trpc`,
|
|
3559
|
+
transformer: superjson,
|
|
3560
|
+
headers: () => ({ authorization: `Bearer ${token}` })
|
|
3561
|
+
})
|
|
3562
|
+
})
|
|
3563
|
+
]
|
|
3564
|
+
});
|
|
3565
|
+
},
|
|
3566
|
+
[serverUrl, token]
|
|
3567
|
+
);
|
|
3568
|
+
const contextValue = useMemo4(
|
|
3569
|
+
() => ({ trpc, token, logout: onLogout }),
|
|
3570
|
+
[trpc, token, onLogout]
|
|
3571
|
+
);
|
|
3572
|
+
return /* @__PURE__ */ jsx48(DevShellContext.Provider, { value: contextValue, children: /* @__PURE__ */ jsxs32("div", { className: "min-h-screen bg-background text-foreground", children: [
|
|
3573
|
+
/* @__PURE__ */ jsxs32("div", { className: "flex items-center justify-between border-b border-border bg-surface px-4 py-2", children: [
|
|
3574
|
+
/* @__PURE__ */ jsxs32("div", { className: "flex items-center gap-2", children: [
|
|
3575
|
+
/* @__PURE__ */ jsx48("span", { className: "rounded bg-warning/20 px-2 py-0.5 text-xs font-bold text-warning", children: "DEV MODE" }),
|
|
3576
|
+
title && /* @__PURE__ */ jsx48("span", { className: "text-sm font-medium text-foreground", children: title }),
|
|
3577
|
+
/* @__PURE__ */ jsx48("span", { className: "text-xs text-foreground-subtle", children: serverUrl })
|
|
3578
|
+
] }),
|
|
3579
|
+
/* @__PURE__ */ jsxs32("div", { className: "flex items-center gap-2", children: [
|
|
3580
|
+
/* @__PURE__ */ jsxs32(
|
|
3581
|
+
"button",
|
|
3582
|
+
{
|
|
3583
|
+
type: "button",
|
|
3584
|
+
onClick: theme.toggleMode,
|
|
3585
|
+
className: "flex items-center gap-1.5 rounded-md px-2 py-1 text-xs font-medium text-foreground-subtle hover:text-foreground hover:bg-surface-hover transition-colors",
|
|
3586
|
+
title: `Theme: ${theme.mode}`,
|
|
3587
|
+
children: [
|
|
3588
|
+
theme.resolvedMode === "dark" ? /* @__PURE__ */ jsx48(SunIcon, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx48(MoonIcon, { className: "h-3.5 w-3.5" }),
|
|
3589
|
+
theme.mode === "dark" ? "Dark" : theme.mode === "light" ? "Light" : "System"
|
|
3590
|
+
]
|
|
3591
|
+
}
|
|
3592
|
+
),
|
|
3593
|
+
/* @__PURE__ */ jsx48(
|
|
3594
|
+
"button",
|
|
3595
|
+
{
|
|
3596
|
+
type: "button",
|
|
3597
|
+
onClick: onLogout,
|
|
3598
|
+
className: "rounded-md px-2.5 py-1 text-xs font-medium text-danger hover:bg-danger/10 transition-colors",
|
|
3599
|
+
children: "Logout"
|
|
3600
|
+
}
|
|
3601
|
+
)
|
|
3602
|
+
] })
|
|
3603
|
+
] }),
|
|
3604
|
+
/* @__PURE__ */ jsx48("div", { className: "p-4", children: children({ trpc, theme }) })
|
|
3605
|
+
] }) });
|
|
3606
|
+
}
|
|
3607
|
+
function DevShell({
|
|
3608
|
+
children,
|
|
3609
|
+
serverUrl = "https://localhost:4443",
|
|
3610
|
+
title
|
|
3611
|
+
}) {
|
|
3612
|
+
const [token, setToken] = useState12(getStoredToken);
|
|
3613
|
+
const handleLogin = useCallback8(
|
|
3614
|
+
async (username, password) => {
|
|
3615
|
+
const anonClient = createTRPCClient({
|
|
3616
|
+
links: [
|
|
3617
|
+
httpLink({
|
|
3618
|
+
url: `${serverUrl}/trpc`,
|
|
3619
|
+
transformer: superjson
|
|
3620
|
+
})
|
|
3621
|
+
]
|
|
3622
|
+
});
|
|
3623
|
+
const res = await anonClient.auth.login.mutate({ username, password });
|
|
3624
|
+
if (!res?.token) throw new Error("No token returned");
|
|
3625
|
+
localStorage.setItem(STORAGE_KEY, res.token);
|
|
3626
|
+
setToken(res.token);
|
|
3627
|
+
},
|
|
3628
|
+
[serverUrl]
|
|
3629
|
+
);
|
|
3630
|
+
const handleLogout = useCallback8(() => {
|
|
3631
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
3632
|
+
setToken(null);
|
|
3633
|
+
}, []);
|
|
3634
|
+
if (!token) {
|
|
3635
|
+
return /* @__PURE__ */ jsx48(ThemeProvider, { children: /* @__PURE__ */ jsx48(LoginForm, { onLogin: handleLogin, serverUrl }) });
|
|
3636
|
+
}
|
|
3637
|
+
return /* @__PURE__ */ jsx48(ThemeProvider, { children: /* @__PURE__ */ jsx48(
|
|
3638
|
+
DevShellInner,
|
|
3639
|
+
{
|
|
3640
|
+
serverUrl,
|
|
3641
|
+
title,
|
|
3642
|
+
token,
|
|
3643
|
+
onLogout: handleLogout,
|
|
3644
|
+
children
|
|
3645
|
+
}
|
|
3646
|
+
) });
|
|
3647
|
+
}
|
|
3648
|
+
|
|
3649
|
+
// src/composites/mount-addon-page.tsx
|
|
3650
|
+
function mountAddonPage(PageComponent, options = {}) {
|
|
3651
|
+
const {
|
|
3652
|
+
serverUrl = "https://localhost:4443",
|
|
3653
|
+
title,
|
|
3654
|
+
rootId = "root"
|
|
3655
|
+
} = options;
|
|
3656
|
+
const root = document.getElementById(rootId);
|
|
3657
|
+
if (!root) {
|
|
3658
|
+
console.error(`[mountAddonPage] Element #${rootId} not found`);
|
|
3659
|
+
return;
|
|
3660
|
+
}
|
|
3661
|
+
createRoot(root).render(
|
|
3662
|
+
createElement(DevShell, {
|
|
3663
|
+
serverUrl,
|
|
3664
|
+
title,
|
|
3665
|
+
children: ({ trpc, theme }) => createElement(PageComponent, {
|
|
3666
|
+
trpc,
|
|
3667
|
+
theme: { isDark: theme.resolvedMode === "dark" },
|
|
3668
|
+
navigate: (path) => {
|
|
3669
|
+
console.log("[dev] navigate:", path);
|
|
3670
|
+
}
|
|
3671
|
+
})
|
|
3672
|
+
})
|
|
3673
|
+
);
|
|
3674
|
+
}
|
|
1981
3675
|
export {
|
|
1982
3676
|
AppShell,
|
|
1983
3677
|
Badge,
|
|
1984
3678
|
Button,
|
|
3679
|
+
CLASS_COLORS,
|
|
1985
3680
|
Card,
|
|
1986
3681
|
Checkbox,
|
|
1987
3682
|
CodeBlock,
|
|
1988
3683
|
ConfirmDialog,
|
|
3684
|
+
DEFAULT_CLASS_COLORS,
|
|
3685
|
+
DEFAULT_COLOR,
|
|
1989
3686
|
DataTable,
|
|
3687
|
+
DetectionCanvas,
|
|
3688
|
+
DetectionResultTree,
|
|
3689
|
+
DevShell,
|
|
1990
3690
|
DeviceCard,
|
|
1991
3691
|
DeviceGrid,
|
|
1992
3692
|
Dialog,
|
|
@@ -2005,10 +3705,16 @@ export {
|
|
|
2005
3705
|
FloatingPanel,
|
|
2006
3706
|
FormField,
|
|
2007
3707
|
IconButton,
|
|
3708
|
+
ImageSelector,
|
|
3709
|
+
InferenceConfigSelector,
|
|
2008
3710
|
Input,
|
|
2009
3711
|
KeyValueList,
|
|
2010
3712
|
Label,
|
|
3713
|
+
LoginForm,
|
|
2011
3714
|
PageHeader,
|
|
3715
|
+
PipelineBuilder,
|
|
3716
|
+
PipelineRuntimeSelector,
|
|
3717
|
+
PipelineStep,
|
|
2012
3718
|
Popover,
|
|
2013
3719
|
PopoverContent,
|
|
2014
3720
|
PopoverTrigger,
|
|
@@ -2021,6 +3727,7 @@ export {
|
|
|
2021
3727
|
Skeleton,
|
|
2022
3728
|
StatCard,
|
|
2023
3729
|
StatusBadge,
|
|
3730
|
+
StepTimings,
|
|
2024
3731
|
Switch,
|
|
2025
3732
|
Tabs,
|
|
2026
3733
|
TabsContent,
|
|
@@ -2034,10 +3741,13 @@ export {
|
|
|
2034
3741
|
createTheme,
|
|
2035
3742
|
darkColors,
|
|
2036
3743
|
defaultTheme,
|
|
3744
|
+
getClassColor,
|
|
2037
3745
|
lightColors,
|
|
3746
|
+
mountAddonPage,
|
|
2038
3747
|
providerIcons,
|
|
2039
3748
|
statusIcons,
|
|
2040
3749
|
themeToCss,
|
|
3750
|
+
useDevShell,
|
|
2041
3751
|
useThemeMode
|
|
2042
3752
|
};
|
|
2043
3753
|
//# sourceMappingURL=index.js.map
|