github-actions[bot]
Deploy hyper3labs/HyperView-DeepFashion-Text-Search from Hyper3Labs/hyperview-spaces@9eda89a
02c4243 | import { rankedResults } from "./ranked_results.js"; | |
| const sdk = globalThis.HyperViewPanelSDK; | |
| if (!sdk) throw new Error("HyperViewPanelSDK is not available on window."); | |
| const { React, components, hooks } = sdk; | |
| const { Panel, PanelToolbar, PanelToolbarButton } = components; | |
| const { | |
| usePanelSelection, | |
| usePanelSamples, | |
| usePanelCommands, | |
| usePanelProps, | |
| } = hooks; | |
| const colors = { | |
| panelBg: "#111827", | |
| cardBg: "#161f2d", | |
| buttonBg: "#1f2937", | |
| border: "#334155", | |
| text: "#e5e7eb", | |
| strongText: "#f8fafc", | |
| mutedText: "#9ca3af", | |
| bodyText: "#cbd5e1", | |
| accent: "#93c5fd", | |
| success: "#86efac", | |
| error: "#fca5a5", | |
| }; | |
| function Section({ title, children }) { | |
| return React.createElement( | |
| "section", | |
| { style: { display: "flex", flexDirection: "column", gap: 8 } }, | |
| React.createElement( | |
| "h3", | |
| { | |
| style: { | |
| margin: 0, | |
| color: colors.strongText, | |
| fontSize: 13, | |
| fontWeight: 700, | |
| }, | |
| }, | |
| title, | |
| ), | |
| children, | |
| ); | |
| } | |
| function TextBlock({ children }) { | |
| return React.createElement( | |
| "div", | |
| { style: { color: colors.bodyText, fontSize: 12, lineHeight: 1.45 } }, | |
| children, | |
| ); | |
| } | |
| function normalizeModels(value) { | |
| if (!Array.isArray(value)) return []; | |
| return value.map((model, index) => ({ | |
| key: String(model.key || `model-${index}`), | |
| displayName: String(model.displayName || model.display_name || model.key || `Model ${index + 1}`), | |
| buttonLabel: String( | |
| model.buttonLabel || model.button_label || `${model.displayName || model.key || "Model"} query`, | |
| ), | |
| layoutKey: model.layoutKey || model.layout_key || null, | |
| spaceKey: model.spaceKey || model.space_key || null, | |
| })); | |
| } | |
| function getSummary(item, modelKey) { | |
| return item.summaries?.[modelKey] || item.modelSummaries?.[modelKey] || {}; | |
| } | |
| function rankLabel(rank) { | |
| return rank === 0 || rank ? `#${rank}` : "-"; | |
| } | |
| function pct(value) { | |
| if (typeof value !== "number") return "-"; | |
| return `${(value * 100).toFixed(1)}%`; | |
| } | |
| function metricValue(value, kind = "pct") { | |
| if (typeof value !== "number") return "-"; | |
| if (kind === "decimal") return value.toFixed(3); | |
| return pct(value); | |
| } | |
| function modelAccent(model) { | |
| return model.key === "candidate" ? colors.success : colors.bodyText; | |
| } | |
| function modelBorder(model) { | |
| return model.key === "candidate" ? "#3f6f5b" : colors.border; | |
| } | |
| function MetricGrid({ rows }) { | |
| return React.createElement( | |
| "div", | |
| { | |
| style: { | |
| display: "grid", | |
| gridTemplateColumns: "1.5fr 1fr 1fr", | |
| border: `1px solid ${colors.border}`, | |
| borderRadius: 6, | |
| overflow: "hidden", | |
| fontSize: 11, | |
| }, | |
| }, | |
| React.createElement("div", { style: headerCellStyle() }, "Metric"), | |
| React.createElement("div", { style: headerCellStyle() }, "Hyper3"), | |
| React.createElement("div", { style: headerCellStyle() }, "CLIP"), | |
| rows.flatMap((row) => [ | |
| React.createElement("div", { key: `${row.label}-label`, style: bodyCellStyle() }, row.label), | |
| React.createElement( | |
| "div", | |
| { key: `${row.label}-h`, style: bodyCellStyle(colors.success) }, | |
| metricValue(row.hyper3, row.kind), | |
| ), | |
| React.createElement( | |
| "div", | |
| { key: `${row.label}-c`, style: bodyCellStyle() }, | |
| metricValue(row.clip, row.kind), | |
| ), | |
| ]), | |
| ); | |
| } | |
| function headerCellStyle() { | |
| return { | |
| background: "#0f172a", | |
| borderBottom: `1px solid ${colors.border}`, | |
| color: colors.strongText, | |
| fontWeight: 700, | |
| padding: 7, | |
| }; | |
| } | |
| function bodyCellStyle(color = colors.bodyText) { | |
| return { | |
| borderTop: `1px solid ${colors.border}`, | |
| color, | |
| padding: 7, | |
| }; | |
| } | |
| function ExampleChooser({ examples, selectedId, onSelect, compact = false }) { | |
| return React.createElement( | |
| "div", | |
| { style: { display: "grid", gridTemplateColumns: "1fr", gap: compact ? 5 : 6 } }, | |
| examples.map((item) => { | |
| const active = item.id === selectedId; | |
| return React.createElement( | |
| "button", | |
| { | |
| key: item.id, | |
| type: "button", | |
| onClick: () => onSelect(item.id), | |
| style: { | |
| border: `1px solid ${active ? colors.accent : colors.border}`, | |
| background: active ? "rgba(147, 197, 253, 0.08)" : colors.cardBg, | |
| color: colors.text, | |
| borderRadius: 5, | |
| padding: compact ? "6px 7px" : "7px 8px", | |
| textAlign: "left", | |
| cursor: "pointer", | |
| }, | |
| }, | |
| React.createElement( | |
| "div", | |
| { style: { color: colors.strongText, fontSize: 12, fontWeight: 700, marginBottom: 2 } }, | |
| item.title, | |
| ), | |
| React.createElement( | |
| "div", | |
| { | |
| style: { | |
| color: colors.bodyText, | |
| fontSize: 10, | |
| lineHeight: 1.3, | |
| overflow: "hidden", | |
| display: "-webkit-box", | |
| WebkitLineClamp: compact ? 2 : 1, | |
| WebkitBoxOrient: "vertical", | |
| }, | |
| }, | |
| item.query, | |
| ), | |
| ); | |
| }), | |
| ); | |
| } | |
| function ResultCard({ result, model, loadingKey, onSelectResult, compact = false, spacious = false }) { | |
| const choiceKey = `${result.sampleId}:${model.key}:ranked-result`; | |
| const cardBasis = compact ? 72 : spacious ? 102 : 84; | |
| const imageHeight = compact ? 78 : spacious ? 108 : 90; | |
| return React.createElement( | |
| "button", | |
| { | |
| type: "button", | |
| onClick: () => onSelectResult(result, model), | |
| disabled: loadingKey === choiceKey, | |
| title: `Select rank ${result.rank} in ${model.displayName}`, | |
| style: { | |
| width: cardBasis, | |
| minWidth: compact ? 66 : 78, | |
| maxWidth: spacious ? 122 : 104, | |
| flex: `1 1 ${cardBasis}px`, | |
| border: `1px solid ${result.isTarget ? colors.success : colors.border}`, | |
| background: result.isTarget ? "rgba(134, 239, 172, 0.06)" : "#0f172a", | |
| color: colors.text, | |
| borderRadius: 5, | |
| padding: 4, | |
| textAlign: "left", | |
| cursor: loadingKey === choiceKey ? "default" : "pointer", | |
| opacity: loadingKey === choiceKey ? 0.65 : 1, | |
| }, | |
| }, | |
| React.createElement( | |
| "div", | |
| { | |
| style: { | |
| display: "flex", | |
| justifyContent: "space-between", | |
| alignItems: "center", | |
| gap: 4, | |
| marginBottom: 4, | |
| }, | |
| }, | |
| React.createElement( | |
| "span", | |
| { style: { color: colors.strongText, fontSize: 10, fontWeight: 700 } }, | |
| `#${result.rank}`, | |
| ), | |
| result.isTarget | |
| ? React.createElement( | |
| "span", | |
| { style: { color: colors.success, fontSize: 8, fontWeight: 700 } }, | |
| "target", | |
| ) | |
| : null, | |
| ), | |
| React.createElement("img", { | |
| src: result.image, | |
| alt: `${model.displayName} rank ${result.rank}`, | |
| style: { | |
| width: "100%", | |
| height: imageHeight, | |
| objectFit: "cover", | |
| borderRadius: 3, | |
| background: "#ffffff", | |
| display: "block", | |
| marginBottom: 4, | |
| }, | |
| }), | |
| React.createElement( | |
| "div", | |
| { | |
| style: { | |
| color: colors.mutedText, | |
| fontSize: 8, | |
| lineHeight: 1.2, | |
| overflow: "hidden", | |
| display: "-webkit-box", | |
| WebkitLineClamp: 2, | |
| WebkitBoxOrient: "vertical", | |
| }, | |
| }, | |
| `${result.color || "unknown"} ${result.category || ""}`.trim(), | |
| ), | |
| ); | |
| } | |
| function RankedResultStrip({ item, model, rows, targetRank, loadingKey, onSelectResult, compact = false, spacious = false }) { | |
| return React.createElement( | |
| "div", | |
| { | |
| style: { | |
| border: `1px solid ${modelBorder(model)}`, | |
| borderRadius: 6, | |
| background: colors.cardBg, | |
| padding: compact ? 6 : 7, | |
| display: "flex", | |
| flexDirection: "column", | |
| gap: 6, | |
| }, | |
| }, | |
| React.createElement( | |
| "div", | |
| { style: { display: "flex", justifyContent: "space-between", gap: 8, alignItems: "baseline" } }, | |
| React.createElement( | |
| "div", | |
| { style: { color: colors.strongText, fontSize: 12, fontWeight: 700 } }, | |
| model.displayName, | |
| ), | |
| React.createElement( | |
| "div", | |
| { style: { color: modelAccent(model), fontSize: 11, fontWeight: 700 } }, | |
| `exact target ${rankLabel(targetRank)}`, | |
| ), | |
| ), | |
| React.createElement( | |
| "div", | |
| { style: { display: "flex", gap: compact ? 5 : 7, overflowX: "auto", paddingBottom: 2 } }, | |
| rows.map((result) => | |
| React.createElement(ResultCard, { | |
| key: `${item.id}-${model.key}-${result.rank}-${result.itemId}`, | |
| result, | |
| model, | |
| loadingKey, | |
| onSelectResult, | |
| compact, | |
| spacious, | |
| }), | |
| ), | |
| ), | |
| ); | |
| } | |
| function SearchResultComparison({ item, models, loadingKey, onSelectResult, compact = false, spacious = false }) { | |
| const proof = rankedResults[item.id]; | |
| const hasInspectableLayouts = models.some((model) => model.layoutKey); | |
| if (!proof) { | |
| return React.createElement( | |
| "div", | |
| { style: { color: colors.bodyText, fontSize: 12, lineHeight: 1.45 } }, | |
| "Ranked results are not configured for this query.", | |
| ); | |
| } | |
| return React.createElement( | |
| "div", | |
| { style: { display: "flex", flexDirection: "column", gap: compact ? 6 : 7 } }, | |
| React.createElement( | |
| "div", | |
| { | |
| style: { | |
| border: `1px solid ${colors.border}`, | |
| borderRadius: 5, | |
| background: "#0f172a", | |
| padding: compact ? 6 : 7, | |
| color: colors.bodyText, | |
| fontSize: 11, | |
| lineHeight: 1.35, | |
| }, | |
| }, | |
| React.createElement("strong", { style: { color: colors.strongText } }, "Selected query: "), | |
| item.query, | |
| React.createElement( | |
| "div", | |
| { style: { color: colors.mutedText, fontSize: 10, marginTop: 3 } }, | |
| hasInspectableLayouts | |
| ? "Green card = exact product. Click a card to inspect it in Samples and the model neighborhood." | |
| : "Green card = exact product. Click a card to select it in Samples.", | |
| ), | |
| ), | |
| models.map((model) => | |
| React.createElement(RankedResultStrip, { | |
| key: `${item.id}-${model.key}-ranked-results`, | |
| item, | |
| model, | |
| rows: proof.results?.[model.key] || [], | |
| targetRank: proof.targetRanks?.[model.key], | |
| loadingKey, | |
| onSelectResult, | |
| compact, | |
| spacious, | |
| }), | |
| ), | |
| ); | |
| } | |
| function ExactTargetActions({ item, models, loadingKey, onSelectQuery, compact = false }) { | |
| const gridColumns = compact ? "1fr" : "repeat(auto-fit, minmax(220px, 1fr))"; | |
| return React.createElement( | |
| "div", | |
| { style: { display: "grid", gridTemplateColumns: gridColumns, gap: 7 } }, | |
| models.map((model) => { | |
| const summary = getSummary(item, model.key); | |
| const isCandidate = model.key === "candidate"; | |
| const targetRank = rankLabel(summary.rank); | |
| const neighborLine = | |
| typeof summary.categoryHits === "number" | |
| ? `${summary.categoryHits}/${summary.total || 10} same-category image neighbors` | |
| : "Neighbor context available in HyperView"; | |
| const choiceKey = `${item.queryId}:${model.key}`; | |
| return React.createElement( | |
| "button", | |
| { | |
| key: model.key, | |
| type: "button", | |
| onClick: () => onSelectQuery(item, model), | |
| disabled: loadingKey === choiceKey, | |
| title: `Select target item for ${model.displayName}`, | |
| style: { | |
| border: `1px solid ${modelBorder(model)}`, | |
| borderRadius: 5, | |
| padding: compact ? 7 : 8, | |
| background: isCandidate ? "rgba(134, 239, 172, 0.06)" : "transparent", | |
| color: colors.text, | |
| textAlign: "left", | |
| cursor: loadingKey === choiceKey ? "default" : "pointer", | |
| opacity: loadingKey === choiceKey ? 0.65 : 1, | |
| }, | |
| }, | |
| React.createElement( | |
| "div", | |
| { style: { display: "flex", justifyContent: "space-between", gap: 8, marginBottom: 4 } }, | |
| React.createElement( | |
| "span", | |
| { style: { color: colors.strongText, fontSize: 12, fontWeight: 700 } }, | |
| model.displayName, | |
| ), | |
| React.createElement( | |
| "span", | |
| { style: { color: modelAccent(model), fontSize: 11, fontWeight: 700 } }, | |
| `target ${targetRank}`, | |
| ), | |
| ), | |
| React.createElement( | |
| "div", | |
| { style: { color: isCandidate ? colors.success : colors.bodyText, fontSize: 11, lineHeight: 1.35 } }, | |
| summary.text || "", | |
| ), | |
| React.createElement( | |
| "div", | |
| { style: { color: colors.mutedText, fontSize: 10, lineHeight: 1.35, marginTop: 5 } }, | |
| loadingKey === choiceKey ? "Loading map selection..." : neighborLine, | |
| ), | |
| ); | |
| }), | |
| ); | |
| } | |
| function RailBlock({ title, children }) { | |
| return React.createElement( | |
| "div", | |
| { | |
| style: { | |
| border: `1px solid ${colors.border}`, | |
| borderRadius: 6, | |
| background: colors.cardBg, | |
| padding: 10, | |
| display: "flex", | |
| flexDirection: "column", | |
| gap: 7, | |
| }, | |
| }, | |
| React.createElement( | |
| "div", | |
| { style: { color: colors.strongText, fontSize: 11, fontWeight: 700 } }, | |
| title, | |
| ), | |
| children, | |
| ); | |
| } | |
| function MiniStat({ label, value, accent = false }) { | |
| return React.createElement( | |
| "div", | |
| { | |
| style: { | |
| border: `1px solid ${accent ? "#3f6f5b" : colors.border}`, | |
| borderRadius: 5, | |
| background: accent ? "rgba(134, 239, 172, 0.05)" : "#0f172a", | |
| padding: 8, | |
| }, | |
| }, | |
| React.createElement( | |
| "div", | |
| { style: { color: colors.mutedText, fontSize: 9, fontWeight: 700, textTransform: "uppercase" } }, | |
| label, | |
| ), | |
| React.createElement( | |
| "div", | |
| { style: { color: accent ? colors.success : colors.strongText, fontSize: 17, fontWeight: 800, marginTop: 3 } }, | |
| value, | |
| ), | |
| ); | |
| } | |
| function BusinessRail({ item, metrics, fill = false }) { | |
| if (!item) return null; | |
| const clipSummary = getSummary(item, "clip"); | |
| const candidateSummary = getSummary(item, "candidate"); | |
| return React.createElement( | |
| "aside", | |
| { | |
| style: { | |
| flex: fill ? "none" : "0 1 280px", | |
| width: fill ? "100%" : undefined, | |
| minWidth: fill ? 0 : 240, | |
| maxWidth: fill ? "none" : 300, | |
| display: "flex", | |
| flexDirection: "column", | |
| gap: 10, | |
| }, | |
| }, | |
| React.createElement( | |
| RailBlock, | |
| { title: "Buyer Readout" }, | |
| React.createElement( | |
| "div", | |
| { style: { color: colors.bodyText, fontSize: 11, lineHeight: 1.4 } }, | |
| item.targetTitle, | |
| ), | |
| React.createElement( | |
| "div", | |
| { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 7 } }, | |
| React.createElement(MiniStat, { | |
| label: "Hyper3 Target", | |
| value: rankLabel(candidateSummary.rank), | |
| accent: true, | |
| }), | |
| React.createElement(MiniStat, { | |
| label: "CLIP Target", | |
| value: rankLabel(clipSummary.rank), | |
| }), | |
| ), | |
| React.createElement( | |
| "div", | |
| { style: { color: colors.mutedText, fontSize: 10, lineHeight: 1.4 } }, | |
| candidateSummary.text || "Exact target appears earlier in the ranked product results.", | |
| ), | |
| ), | |
| React.createElement( | |
| RailBlock, | |
| { title: "Why This Query Matters" }, | |
| React.createElement( | |
| "div", | |
| { style: { color: colors.bodyText, fontSize: 11, lineHeight: 1.45 } }, | |
| "The search combines color, fit, construction, and item type. That is the kind of typed product search where returning the exact SKU early matters.", | |
| ), | |
| ), | |
| React.createElement( | |
| RailBlock, | |
| { title: "Probe Signal" }, | |
| React.createElement( | |
| "div", | |
| { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 7 } }, | |
| React.createElement(MiniStat, { | |
| label: "Hit@10", | |
| value: pct(metrics.typedHit10Hyper3), | |
| accent: true, | |
| }), | |
| React.createElement(MiniStat, { | |
| label: "CLIP", | |
| value: pct(metrics.typedHit10Clip), | |
| }), | |
| ), | |
| React.createElement( | |
| "div", | |
| { style: { color: colors.mutedText, fontSize: 10, lineHeight: 1.4 } }, | |
| "Benchmark details stay below the demo so the ranked cards remain the main evidence.", | |
| ), | |
| ), | |
| ); | |
| } | |
| function BenchmarkDetails({ typedMetricRows, imageMetricRows }) { | |
| return React.createElement( | |
| "details", | |
| { | |
| style: { | |
| border: `1px solid ${colors.border}`, | |
| borderRadius: 6, | |
| background: colors.cardBg, | |
| padding: 9, | |
| }, | |
| }, | |
| React.createElement( | |
| "summary", | |
| { | |
| style: { | |
| color: colors.strongText, | |
| fontSize: 12, | |
| fontWeight: 700, | |
| cursor: "pointer", | |
| }, | |
| }, | |
| "Benchmark Details", | |
| ), | |
| React.createElement( | |
| "div", | |
| { style: { display: "flex", flexDirection: "column", gap: 8, marginTop: 9 } }, | |
| React.createElement( | |
| TextBlock, | |
| null, | |
| "Bottom reference only. The result strips above are the main demo; these numbers keep the probe traceable.", | |
| ), | |
| React.createElement(MetricGrid, { rows: typedMetricRows }), | |
| React.createElement( | |
| TextBlock, | |
| null, | |
| "Separate image-to-image check, shown only for context because it is not the typed shopper search task.", | |
| ), | |
| React.createElement(MetricGrid, { rows: imageMetricRows }), | |
| ), | |
| ); | |
| } | |
| export default function FashionSearchComparisonPanel() { | |
| const selection = usePanelSelection(); | |
| const samplesState = usePanelSamples(); | |
| const commands = usePanelCommands(); | |
| const panelProps = usePanelProps(); | |
| const [panelError, setPanelError] = React.useState(null); | |
| const [loadingKey, setLoadingKey] = React.useState(null); | |
| const [selectedExampleId, setSelectedExampleId] = React.useState(() => | |
| typeof panelProps.initialExampleId === "string" ? panelProps.initialExampleId : null, | |
| ); | |
| const [contentWidth, setContentWidth] = React.useState(0); | |
| const resultsContentRef = React.useRef(null); | |
| const didPrimeEvidence = React.useRef(false); | |
| const evidenceRequestSeq = React.useRef(0); | |
| const latestEvidenceRequest = React.useRef(null); | |
| const mode = panelProps.mode === "summary" ? "summary" : panelProps.mode === "results" ? "results" : "full"; | |
| const models = React.useMemo(() => normalizeModels(panelProps.models), [panelProps.models]); | |
| const inspectableModels = React.useMemo(() => models.filter((model) => model.layoutKey), [models]); | |
| const examples = Array.isArray(panelProps.examples) ? panelProps.examples : []; | |
| const metrics = panelProps.metrics || {}; | |
| const selectedExample = | |
| examples.find((item) => item.id === selectedExampleId) || examples[0] || null; | |
| const compactLayout = contentWidth > 0 && contentWidth < 680; | |
| const spaciousLayout = contentWidth >= 900; | |
| const typedMetricRows = [ | |
| { label: "Typed search Hit@1", hyper3: metrics.typedHit1Hyper3, clip: metrics.typedHit1Clip }, | |
| { label: "Typed search Hit@10", hyper3: metrics.typedHit10Hyper3, clip: metrics.typedHit10Clip }, | |
| { | |
| label: "Typed category P@10", | |
| hyper3: metrics.typedCategoryP10Hyper3, | |
| clip: metrics.typedCategoryP10Clip, | |
| }, | |
| { label: "Typed search MRR", hyper3: metrics.typedMrrHyper3, clip: metrics.typedMrrClip, kind: "decimal" }, | |
| ]; | |
| const imageMetricRows = [ | |
| { label: "Image-to-image mAP", hyper3: metrics.imageRetrievalMapHyper3, clip: metrics.imageRetrievalMapClip }, | |
| ]; | |
| React.useEffect(() => { | |
| const node = resultsContentRef.current; | |
| if (!node) return undefined; | |
| const updateWidth = () => setContentWidth(Math.round(node.getBoundingClientRect().width)); | |
| updateWidth(); | |
| if (typeof ResizeObserver === "undefined") { | |
| window.addEventListener("resize", updateWidth); | |
| return () => window.removeEventListener("resize", updateWidth); | |
| } | |
| const observer = new ResizeObserver((entries) => { | |
| const entry = entries[0]; | |
| const width = entry?.contentRect?.width; | |
| if (typeof width === "number") { | |
| setContentWidth(Math.round(width)); | |
| } else { | |
| updateWidth(); | |
| } | |
| }); | |
| observer.observe(node); | |
| return () => observer.disconnect(); | |
| }, [mode]); | |
| const selectSimilarityAnchor = React.useCallback( | |
| async (sampleId) => { | |
| if (!sampleId) return; | |
| await commands.setSelection([sampleId], { | |
| persist: false, | |
| source: "panel", | |
| clearLasso: false, | |
| }); | |
| }, | |
| [commands], | |
| ); | |
| const loadEvidenceForSample = React.useCallback( | |
| async ({ sampleId, model, source = "fashion-demo:evidence", errorPrefix = "Could not load the sample evidence panel" }) => { | |
| if (!sampleId || !model?.layoutKey) return; | |
| const requestKey = `${sampleId}:${model.layoutKey}:${model.spaceKey || ""}`; | |
| const request = { sampleId, model, source, errorPrefix, requestKey }; | |
| latestEvidenceRequest.current = request; | |
| const requestSeq = evidenceRequestSeq.current + 1; | |
| evidenceRequestSeq.current = requestSeq; | |
| try { | |
| await commands.setActiveLayout(model.layoutKey, { persist: false }); | |
| if (requestSeq !== evidenceRequestSeq.current) return; | |
| await commands.showSimilar({ | |
| sampleId, | |
| layoutKey: model.layoutKey, | |
| spaceKey: model.spaceKey, | |
| k: 10, | |
| source, | |
| focus: "samples", | |
| persist: false, | |
| }); | |
| if (requestSeq !== evidenceRequestSeq.current) return; | |
| await selectSimilarityAnchor(sampleId); | |
| } catch (error) { | |
| const message = error instanceof Error ? error.message : String(error); | |
| setPanelError(`${errorPrefix}: ${message}`); | |
| } | |
| }, | |
| [commands, selectSimilarityAnchor], | |
| ); | |
| const showExampleEvidence = React.useCallback( | |
| async (item, model, source = "fashion-demo:query") => { | |
| if (!item || !model?.layoutKey) return; | |
| await loadEvidenceForSample({ | |
| sampleId: item.queryId, | |
| model, | |
| source, | |
| errorPrefix: "Could not load the sample evidence panel", | |
| }); | |
| }, | |
| [loadEvidenceForSample], | |
| ); | |
| const chooseExample = React.useCallback( | |
| (exampleId) => { | |
| setSelectedExampleId(exampleId); | |
| const item = examples.find((entry) => entry.id === exampleId); | |
| const evidenceModel = | |
| models.find((model) => model.key === "clip" && model.layoutKey) || | |
| models.find((model) => model.layoutKey); | |
| if (item && evidenceModel) { | |
| void showExampleEvidence(item, evidenceModel, "fashion-demo:query-picker"); | |
| } | |
| }, | |
| [examples, models, showExampleEvidence], | |
| ); | |
| React.useEffect(() => { | |
| if (mode === "summary") return; | |
| if (didPrimeEvidence.current) return; | |
| if (!selectedExample) return; | |
| const evidenceModel = | |
| models.find((model) => model.key === "clip" && model.layoutKey) || | |
| models.find((model) => model.layoutKey); | |
| if (!evidenceModel) return; | |
| didPrimeEvidence.current = true; | |
| void showExampleEvidence(selectedExample, evidenceModel, "fashion-demo:initial"); | |
| }, [mode, models, selectedExample, showExampleEvidence]); | |
| const clearSelection = async () => { | |
| if (commands.setLabelFilter) commands.setLabelFilter(null); | |
| setPanelError(null); | |
| await commands.clearQueryContext({ persist: true }); | |
| }; | |
| const selectModelQuery = async (item, model) => { | |
| const key = `${item.queryId}:${model.key}`; | |
| setPanelError(null); | |
| if (!model.layoutKey) { | |
| await commands.setSelection([item.queryId], { persist: false, source: `fashion-target:${model.key}` }); | |
| commands.focusPanel("grid"); | |
| return; | |
| } | |
| setLoadingKey(key); | |
| try { | |
| await loadEvidenceForSample({ | |
| sampleId: item.queryId, | |
| model, | |
| source: `fashion-demo:${model.key}`, | |
| errorPrefix: "Could not select target item", | |
| }); | |
| } catch (error) { | |
| const message = error instanceof Error ? error.message : String(error); | |
| setPanelError(`Could not select target item: ${message}`); | |
| } finally { | |
| setLoadingKey(null); | |
| } | |
| }; | |
| const selectRankedResult = async (result, model) => { | |
| const key = `${result.sampleId}:${model.key}:ranked-result`; | |
| setPanelError(null); | |
| if (!model.layoutKey) { | |
| await commands.setSelection([result.sampleId], { | |
| persist: false, | |
| source: `fashion-ranked-result:${model.key}`, | |
| }); | |
| commands.focusPanel("grid"); | |
| return; | |
| } | |
| setLoadingKey(key); | |
| try { | |
| await loadEvidenceForSample({ | |
| sampleId: result.sampleId, | |
| model, | |
| source: `fashion-ranked-result:${model.key}`, | |
| errorPrefix: "Could not select ranked result", | |
| }); | |
| } catch (error) { | |
| const message = error instanceof Error ? error.message : String(error); | |
| setPanelError(`Could not select ranked result: ${message}`); | |
| } finally { | |
| setLoadingKey(null); | |
| } | |
| }; | |
| const toolbar = | |
| mode === "summary" | |
| ? null | |
| : React.createElement(PanelToolbar, { | |
| items: [ | |
| { id: "dataset", label: "Data", value: "DeepFashion" }, | |
| { id: "samples", label: "Items", value: String(samplesState.totalSamples ?? "-") }, | |
| { id: "selected", label: "Selected", value: String(selection.selectedIds?.length ?? 0) }, | |
| ], | |
| actions: React.createElement(PanelToolbarButton, { onClick: clearSelection }, "Reset"), | |
| }); | |
| const summaryContent = React.createElement( | |
| "div", | |
| { | |
| style: { | |
| height: "100%", | |
| overflow: "auto", | |
| padding: 12, | |
| background: colors.panelBg, | |
| display: "flex", | |
| flexDirection: "column", | |
| gap: 12, | |
| }, | |
| }, | |
| React.createElement( | |
| Section, | |
| { title: "Buyer Decision View" }, | |
| React.createElement( | |
| TextBlock, | |
| null, | |
| "Business readout for the selected shopper query.", | |
| ), | |
| examples.length | |
| ? React.createElement(ExampleChooser, { | |
| examples, | |
| selectedId: selectedExample?.id, | |
| onSelect: chooseExample, | |
| }) | |
| : null, | |
| ), | |
| React.createElement(BusinessRail, { item: selectedExample, metrics, fill: true }), | |
| ); | |
| const resultsContent = React.createElement( | |
| "div", | |
| { | |
| ref: resultsContentRef, | |
| style: { | |
| height: "100%", | |
| overflow: "auto", | |
| padding: compactLayout ? 10 : 14, | |
| background: colors.panelBg, | |
| }, | |
| }, | |
| React.createElement( | |
| "div", | |
| { | |
| style: { | |
| width: "100%", | |
| margin: 0, | |
| display: "flex", | |
| gap: compactLayout ? 10 : 12, | |
| alignItems: "flex-start", | |
| justifyContent: "stretch", | |
| flexWrap: "wrap", | |
| }, | |
| }, | |
| React.createElement( | |
| "main", | |
| { | |
| style: { | |
| flex: "1 1 auto", | |
| width: "100%", | |
| maxWidth: "none", | |
| minWidth: 0, | |
| display: "flex", | |
| flexDirection: "column", | |
| gap: compactLayout ? 12 : 14, | |
| }, | |
| }, | |
| React.createElement( | |
| Section, | |
| { title: "Pick A Shopper Query" }, | |
| React.createElement( | |
| TextBlock, | |
| null, | |
| "Pick a typed product search and compare where each model ranks the exact item.", | |
| ), | |
| examples.length | |
| ? React.createElement(ExampleChooser, { | |
| examples, | |
| selectedId: selectedExample?.id, | |
| onSelect: chooseExample, | |
| compact: compactLayout, | |
| }) | |
| : React.createElement( | |
| "div", | |
| { style: { color: colors.bodyText, fontSize: 12, lineHeight: 1.45 } }, | |
| "Demo examples are not configured.", | |
| ), | |
| selectedExample && inspectableModels.length | |
| ? React.createElement( | |
| Section, | |
| { title: "Inspect Exact Target" }, | |
| React.createElement( | |
| TextBlock, | |
| null, | |
| `Load the same target product in Samples: ${selectedExample?.targetTitle || "target product"}.`, | |
| ), | |
| React.createElement(ExactTargetActions, { | |
| item: selectedExample, | |
| models: inspectableModels, | |
| loadingKey, | |
| onSelectQuery: selectModelQuery, | |
| compact: compactLayout, | |
| }), | |
| ) | |
| : null, | |
| selectedExample && models.length | |
| ? React.createElement(SearchResultComparison, { | |
| item: selectedExample, | |
| models, | |
| loadingKey, | |
| onSelectResult: selectRankedResult, | |
| compact: compactLayout, | |
| spacious: spaciousLayout, | |
| }) | |
| : null, | |
| ), | |
| React.createElement(BenchmarkDetails, { typedMetricRows, imageMetricRows }), | |
| panelError | |
| ? React.createElement( | |
| "div", | |
| { style: { color: colors.error, fontSize: 11, lineHeight: 1.35 } }, | |
| panelError, | |
| ) | |
| : null, | |
| ), | |
| mode === "full" ? React.createElement(BusinessRail, { item: selectedExample, metrics }) : null, | |
| ), | |
| ); | |
| return React.createElement(Panel, { className: "h-full" }, toolbar, mode === "summary" ? summaryContent : resultsContent); | |
| } | |