mnm-matin commited on
Commit
8e04fbb
·
verified ·
1 Parent(s): ecd36a3

Use actual scatter layout composition

Browse files
.hyperview/extensions/fashion-search-readout/panel.js CHANGED
@@ -67,6 +67,7 @@ function normalizeModels(value) {
67
  model.buttonLabel || model.button_label || `${model.displayName || model.key || "Model"} query`,
68
  ),
69
  layoutKey: model.layoutKey || model.layout_key || null,
 
70
  }));
71
  }
72
 
@@ -316,6 +317,7 @@ function RankedResultStrip({ item, model, rows, targetRank, loadingKey, onSelect
316
 
317
  function SearchResultComparison({ item, models, loadingKey, onSelectResult }) {
318
  const proof = rankedResults[item.id];
 
319
  if (!proof) {
320
  return React.createElement(
321
  "div",
@@ -345,7 +347,9 @@ function SearchResultComparison({ item, models, loadingKey, onSelectResult }) {
345
  React.createElement(
346
  "div",
347
  { style: { color: colors.mutedText, fontSize: 10, marginTop: 3 } },
348
- "Green card = exact product. Click any card to select it in Samples.",
 
 
349
  ),
350
  ),
351
  models.map((model) =>
@@ -601,8 +605,11 @@ export default function FashionSearchComparisonPanel() {
601
  const panelProps = usePanelProps();
602
  const [panelError, setPanelError] = React.useState(null);
603
  const [loadingKey, setLoadingKey] = React.useState(null);
604
- const [selectedExampleId, setSelectedExampleId] = React.useState(null);
 
 
605
  const didApplyDemoLayout = React.useRef(false);
 
606
  const mode = panelProps.mode === "summary" ? "summary" : panelProps.mode === "results" ? "results" : "full";
607
 
608
  const models = React.useMemo(() => normalizeModels(panelProps.models), [panelProps.models]);
@@ -636,10 +643,42 @@ export default function FashionSearchComparisonPanel() {
636
  return () => window.removeEventListener("fashion-search-example-change", handleExampleChange);
637
  }, []);
638
 
639
- const chooseExample = React.useCallback((exampleId) => {
640
- setSelectedExampleId(exampleId);
641
- window.dispatchEvent(new CustomEvent("fashion-search-example-change", { detail: exampleId }));
642
- }, []);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
643
 
644
  React.useEffect(() => {
645
  if (mode === "summary") return;
@@ -663,6 +702,23 @@ export default function FashionSearchComparisonPanel() {
663
  return () => window.clearInterval(timer);
664
  }, [commands, mode]);
665
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
666
  const clearSelection = async () => {
667
  if (commands.setLabelFilter) commands.setLabelFilter(null);
668
  setPanelError(null);
@@ -684,12 +740,12 @@ export default function FashionSearchComparisonPanel() {
684
  await commands.showSimilar({
685
  sampleId: item.queryId,
686
  layoutKey: model.layoutKey,
 
687
  k: 10,
688
  source: `fashion-demo:${model.key}`,
689
- focus: false,
690
  persist: false,
691
  });
692
- commands.focusPanel?.(model.key === "candidate" ? "fashion-map-hyper3" : "fashion-map-clip");
693
  } catch (error) {
694
  const message = error instanceof Error ? error.message : String(error);
695
  setPanelError(`Could not select target item: ${message}`);
@@ -715,12 +771,12 @@ export default function FashionSearchComparisonPanel() {
715
  await commands.showSimilar({
716
  sampleId: result.sampleId,
717
  layoutKey: model.layoutKey,
 
718
  k: 10,
719
  source: `fashion-ranked-result:${model.key}`,
720
- focus: false,
721
  persist: false,
722
  });
723
- commands.focusPanel?.(model.key === "candidate" ? "fashion-map-hyper3" : "fashion-map-clip");
724
  } catch (error) {
725
  const message = error instanceof Error ? error.message : String(error);
726
  setPanelError(`Could not select ranked result: ${message}`);
 
67
  model.buttonLabel || model.button_label || `${model.displayName || model.key || "Model"} query`,
68
  ),
69
  layoutKey: model.layoutKey || model.layout_key || null,
70
+ spaceKey: model.spaceKey || model.space_key || null,
71
  }));
72
  }
73
 
 
317
 
318
  function SearchResultComparison({ item, models, loadingKey, onSelectResult }) {
319
  const proof = rankedResults[item.id];
320
+ const hasInspectableLayouts = models.some((model) => model.layoutKey);
321
  if (!proof) {
322
  return React.createElement(
323
  "div",
 
347
  React.createElement(
348
  "div",
349
  { style: { color: colors.mutedText, fontSize: 10, marginTop: 3 } },
350
+ hasInspectableLayouts
351
+ ? "Green card = exact product. Click a card to inspect it in Samples and the model neighborhood."
352
+ : "Green card = exact product. Click a card to select it in Samples.",
353
  ),
354
  ),
355
  models.map((model) =>
 
605
  const panelProps = usePanelProps();
606
  const [panelError, setPanelError] = React.useState(null);
607
  const [loadingKey, setLoadingKey] = React.useState(null);
608
+ const [selectedExampleId, setSelectedExampleId] = React.useState(() =>
609
+ typeof panelProps.initialExampleId === "string" ? panelProps.initialExampleId : null,
610
+ );
611
  const didApplyDemoLayout = React.useRef(false);
612
+ const didPrimeEvidence = React.useRef(false);
613
  const mode = panelProps.mode === "summary" ? "summary" : panelProps.mode === "results" ? "results" : "full";
614
 
615
  const models = React.useMemo(() => normalizeModels(panelProps.models), [panelProps.models]);
 
643
  return () => window.removeEventListener("fashion-search-example-change", handleExampleChange);
644
  }, []);
645
 
646
+ const showExampleEvidence = React.useCallback(
647
+ async (item, model, source = "fashion-demo:query") => {
648
+ if (!item || !model?.layoutKey) return;
649
+ try {
650
+ await commands.setActiveLayout(model.layoutKey, { persist: false });
651
+ await commands.showSimilar({
652
+ sampleId: item.queryId,
653
+ layoutKey: model.layoutKey,
654
+ spaceKey: model.spaceKey,
655
+ k: 10,
656
+ source,
657
+ focus: "samples",
658
+ persist: false,
659
+ });
660
+ } catch (error) {
661
+ const message = error instanceof Error ? error.message : String(error);
662
+ setPanelError(`Could not load the sample evidence panel: ${message}`);
663
+ }
664
+ },
665
+ [commands],
666
+ );
667
+
668
+ const chooseExample = React.useCallback(
669
+ (exampleId) => {
670
+ setSelectedExampleId(exampleId);
671
+ window.dispatchEvent(new CustomEvent("fashion-search-example-change", { detail: exampleId }));
672
+ const item = examples.find((entry) => entry.id === exampleId);
673
+ const evidenceModel =
674
+ models.find((model) => model.key === "clip" && model.layoutKey) ||
675
+ models.find((model) => model.layoutKey);
676
+ if (item && evidenceModel) {
677
+ void showExampleEvidence(item, evidenceModel, "fashion-demo:query-picker");
678
+ }
679
+ },
680
+ [examples, models, showExampleEvidence],
681
+ );
682
 
683
  React.useEffect(() => {
684
  if (mode === "summary") return;
 
702
  return () => window.clearInterval(timer);
703
  }, [commands, mode]);
704
 
705
+ React.useEffect(() => {
706
+ if (mode === "summary") return;
707
+ if (didPrimeEvidence.current) return;
708
+ if (!selectedExample) return;
709
+ const evidenceModel =
710
+ models.find((model) => model.key === "clip" && model.layoutKey) ||
711
+ models.find((model) => model.layoutKey);
712
+ if (!evidenceModel) return;
713
+ didPrimeEvidence.current = true;
714
+
715
+ const timer = window.setTimeout(() => {
716
+ void showExampleEvidence(selectedExample, evidenceModel, "fashion-demo:initial");
717
+ }, 700);
718
+
719
+ return () => window.clearTimeout(timer);
720
+ }, [mode, models, selectedExample, showExampleEvidence]);
721
+
722
  const clearSelection = async () => {
723
  if (commands.setLabelFilter) commands.setLabelFilter(null);
724
  setPanelError(null);
 
740
  await commands.showSimilar({
741
  sampleId: item.queryId,
742
  layoutKey: model.layoutKey,
743
+ spaceKey: model.spaceKey,
744
  k: 10,
745
  source: `fashion-demo:${model.key}`,
746
+ focus: "samples",
747
  persist: false,
748
  });
 
749
  } catch (error) {
750
  const message = error instanceof Error ? error.message : String(error);
751
  setPanelError(`Could not select target item: ${message}`);
 
771
  await commands.showSimilar({
772
  sampleId: result.sampleId,
773
  layoutKey: model.layoutKey,
774
+ spaceKey: model.spaceKey,
775
  k: 10,
776
  source: `fashion-ranked-result:${model.key}`,
777
+ focus: "samples",
778
  persist: false,
779
  });
 
780
  } catch (error) {
781
  const message = error instanceof Error ? error.message : String(error);
782
  setPanelError(`Could not select ranked result: ${message}`);
Dockerfile CHANGED
@@ -42,9 +42,10 @@ COPY --chown=user . .
42
 
43
  ENV HYPERVIEW_HOST=0.0.0.0 \
44
  HYPERVIEW_PORT=7860 \
45
- HYPERVIEW_WORKSPACE_ID=fashion-retail-search-clusters \
46
  HYPERVIEW_DATASETS_DIR=/home/user/app/demo_data/datasets \
47
  HYPERVIEW_MEDIA_DIR=/home/user/app/demo_data/media \
 
48
  DEEPFASHION_SAMPLES_PER_CATEGORY=10 \
49
  DEEPFASHION_MAX_SAMPLES=180 \
50
  HF_HUB_ETAG_TIMEOUT=30 \
 
42
 
43
  ENV HYPERVIEW_HOST=0.0.0.0 \
44
  HYPERVIEW_PORT=7860 \
45
+ HYPERVIEW_WORKSPACE_ID=fashion-retail-search-actual-scatter \
46
  HYPERVIEW_DATASETS_DIR=/home/user/app/demo_data/datasets \
47
  HYPERVIEW_MEDIA_DIR=/home/user/app/demo_data/media \
48
+ FASHION_ENABLE_CONTEXT_MAPS=1 \
49
  DEEPFASHION_SAMPLES_PER_CATEGORY=10 \
50
  DEEPFASHION_MAX_SAMPLES=180 \
51
  HF_HUB_ETAG_TIMEOUT=30 \
README.md CHANGED
@@ -26,7 +26,7 @@ The demo is not "another embedding map." The buyer-facing story is:
26
  - In the DeepFashion text-to-image probe, Hyper3-CLIP has a small aggregate edge and concrete examples where the exact item appears on the first screen while CLIP buries it.
27
  - The best example is a query for light denim leggings with skinny fit, zipper details, five-pocket construction, and pockets: Hyper3 ranks the exact target first; CLIP first surfaces it at rank 32.
28
 
29
- Use the ranked result strips first. Then use the compact map buttons to inspect the same target item under each embedding space. The text-search ranks are precomputed from the bounded DeepFashion probe; the maps provide visual neighborhood context for the same fashion items.
30
 
31
  ## What Is In The Demo
32
 
 
26
  - In the DeepFashion text-to-image probe, Hyper3-CLIP has a small aggregate edge and concrete examples where the exact item appears on the first screen while CLIP buries it.
27
  - The best example is a query for light denim leggings with skinny fit, zipper details, five-pocket construction, and pockets: Hyper3 ranks the exact target first; CLIP first surfaces it at rank 32.
28
 
29
+ Use the ranked result strips first. The Samples panel then shows the selected catalog item and nearest neighbors, while the smaller context maps show the same item in the actual CLIP and Hyper3-CLIP embedding layouts. The text-search ranks are precomputed from the bounded DeepFashion probe; the maps provide visual neighborhood context for the same fashion items.
30
 
31
  ## What Is In The Demo
32
 
demo.py CHANGED
@@ -6,7 +6,6 @@ from __future__ import annotations
6
  import os
7
  import re
8
  import time
9
- import math
10
  from collections import Counter
11
  from pathlib import Path
12
  from typing import Any
@@ -19,7 +18,7 @@ import hyperview as hv
19
  SPACE_DIR = Path(__file__).resolve().parent
20
  SPACE_HOST = os.environ.get("HYPERVIEW_HOST", "127.0.0.1")
21
  SPACE_PORT = int(os.environ.get("HYPERVIEW_PORT", "6262"))
22
- WORKSPACE_ID = os.environ.get("HYPERVIEW_WORKSPACE_ID", "fashion-retail-search-clusters")
23
  DATASET_NAME = os.environ.get("HYPERVIEW_DATASET_NAME", "deepfashion_text_search_clip_hyper3clip")
24
  EXTENSION_DIR = SPACE_DIR / ".hyperview" / "extensions" / "fashion-search-readout"
25
 
@@ -38,7 +37,7 @@ ALLOW_CANDIDATE_FALLBACK = os.environ.get("HYPERVIEW_ALLOW_CANDIDATE_FALLBACK",
38
  "true",
39
  "yes",
40
  }
41
- ENABLE_CONTEXT_MAPS = os.environ.get("FASHION_ENABLE_CONTEXT_MAPS", "").lower() in {
42
  "1",
43
  "true",
44
  "yes",
@@ -46,6 +45,7 @@ ENABLE_CONTEXT_MAPS = os.environ.get("FASHION_ENABLE_CONTEXT_MAPS", "").lower()
46
  EMBEDDING_MAX_ATTEMPTS = max(1, int(os.environ.get("HYPERVIEW_EMBEDDING_MAX_ATTEMPTS", "4")))
47
  EMBEDDING_RETRY_DELAY_SECONDS = float(os.environ.get("HYPERVIEW_EMBEDDING_RETRY_DELAY_SECONDS", "15"))
48
  RUNTIME_WARNINGS: list[str] = []
 
49
 
50
  MODEL_SPECS = [
51
  {
@@ -335,45 +335,12 @@ def ensure_layouts(dataset: hv.Dataset) -> dict[str, str]:
335
  return layouts
336
 
337
 
338
- def ensure_catalog_layout(dataset: hv.Dataset) -> str:
339
- samples = dataset.samples
340
- if not samples:
341
- raise RuntimeError("DeepFashion demo has no samples to lay out.")
342
-
343
- categories = sorted({str(sample.metadata.get("category") or sample.label or "unknown") for sample in samples})
344
- category_index = {category: index for index, category in enumerate(categories)}
345
- category_totals = Counter(str(sample.metadata.get("category") or sample.label or "unknown") for sample in samples)
346
- category_seen: Counter[str] = Counter()
347
- ids: list[str] = []
348
- coords: list[list[float]] = []
349
-
350
- for sample in samples:
351
- category = str(sample.metadata.get("category") or sample.label or "unknown")
352
- center_angle = (2.0 * math.pi * category_index[category]) / max(1, len(categories))
353
- slot = category_seen[category]
354
- category_seen[category] += 1
355
-
356
- local_angle = (2.0 * math.pi * slot) / max(1, category_totals[category])
357
- local_radius = 0.14 + 0.035 * (slot % 5)
358
- center_radius = 4.0
359
- coords.append(
360
- [
361
- center_radius * math.cos(center_angle) + local_radius * math.cos(local_angle),
362
- center_radius * math.sin(center_angle) + local_radius * math.sin(local_angle),
363
- ]
364
- )
365
- ids.append(sample.id)
366
-
367
- return dataset.set_coords("euclidean", ids, coords)
368
-
369
-
370
  def build_dataset() -> tuple[hv.Dataset, dict[str, str]]:
371
  dataset = hv.Dataset(DATASET_NAME)
372
  add_deepfashion_samples(dataset)
373
  if ENABLE_CONTEXT_MAPS:
374
  layouts = ensure_layouts(dataset)
375
  else:
376
- ensure_catalog_layout(dataset)
377
  layouts = {}
378
  return dataset, layouts
379
 
@@ -388,6 +355,7 @@ def model_panel_props(layouts: dict[str, str]) -> list[dict[str, Any]]:
388
  "displayName": spec["display_name"],
389
  "buttonLabel": spec["button_label"],
390
  "layoutKey": layout_key,
 
391
  }
392
  )
393
  return props
@@ -450,6 +418,7 @@ def build_demo_view(dataset: hv.Dataset, layouts: dict[str, str]) -> hv.ui.View:
450
  shared_props = {
451
  "models": model_panel_props(layouts),
452
  "examples": build_examples(dataset),
 
453
  "warnings": RUNTIME_WARNINGS,
454
  "metrics": {
455
  "typedQueryCount": 180,
@@ -475,16 +444,17 @@ def build_demo_view(dataset: hv.Dataset, layouts: dict[str, str]) -> hv.ui.View:
475
  title="Ranked Search Results",
476
  extension="fashion-search-readout",
477
  panel="fashion-comparison",
478
- position="right",
 
 
479
  props={
480
  **shared_props,
481
  "mode": "results",
482
  "dockview": {
483
  "closeSamplesPanel": False,
484
  "hideExplorerPanel": True,
485
- "initialWidth": int(os.environ.get("FASHION_RESULTS_WIDTH", "780")),
486
  "minimumWidth": 620,
487
- "maximumWidth": int(os.environ.get("FASHION_RESULTS_MAX_WIDTH", "920")),
488
  },
489
  },
490
  )
@@ -494,36 +464,45 @@ def build_demo_view(dataset: hv.Dataset, layouts: dict[str, str]) -> hv.ui.View:
494
 
495
  clip_spec = MODEL_SPECS[0]
496
  candidate_spec = MODEL_SPECS[1]
497
- context_dockview = {
498
- "initialWidth": int(os.environ.get("FASHION_CONTEXT_WIDTH", "360")),
499
- "minimumWidth": 260,
500
- "maximumWidth": int(os.environ.get("FASHION_CONTEXT_MAX_WIDTH", "440")),
501
  }
502
  clip_map = hv.ui.Scatter(
503
  id="fashion-map-clip",
504
  title="Context Map: CLIP",
505
  layout_key=layouts["clip"],
506
- position="right",
507
- reference_panel_id="fashion-ranked-results",
508
- direction="within",
509
  geometry=clip_spec["geometry"],
510
  layout_dimension=clip_spec["layout_dimension"],
511
- props={"dockview": context_dockview},
512
  )
513
  candidate_map = hv.ui.Scatter(
514
  id="fashion-map-hyper3",
515
  title="Context Map: Hyper3",
516
  layout_key=layouts["candidate"],
517
- position="right",
518
  reference_panel_id="fashion-map-clip",
519
- direction="within",
520
  geometry=candidate_spec["geometry"],
521
  layout_dimension=candidate_spec["layout_dimension"],
522
- props={"dockview": context_dockview},
523
  )
524
  return hv.ui.View(results_panel, clip_map, candidate_map)
525
 
526
 
 
 
 
 
 
 
 
 
 
 
527
  def launch_demo(dataset: hv.Dataset, layouts: dict[str, str]) -> hv.Session:
528
  session = hv.launch(
529
  dataset,
@@ -537,11 +516,14 @@ def launch_demo(dataset: hv.Dataset, layouts: dict[str, str]) -> hv.Session:
537
  session.ui.add_extension(EXTENSION_DIR, workspace_id=WORKSPACE_ID)
538
  print("Applying DeepFashion retail search demo view...", flush=True)
539
  session.ui.apply_view(build_demo_view(dataset, layouts), workspace_id=WORKSPACE_ID)
540
- session.ui.set_active_layout(None, workspace_id=WORKSPACE_ID)
541
- session.ui.set_selection([], workspace_id=WORKSPACE_ID)
 
 
 
542
  print(f"\nHyperView DeepFashion text-search demo is running at {session.url}", flush=True)
543
  if ENABLE_CONTEXT_MAPS:
544
- print(" Samples stay visible; scatter maps are opt-in context tabs.", flush=True)
545
  else:
546
  print(" Samples stay visible; ranked text-search results are the main demo.", flush=True)
547
  return session
 
6
  import os
7
  import re
8
  import time
 
9
  from collections import Counter
10
  from pathlib import Path
11
  from typing import Any
 
18
  SPACE_DIR = Path(__file__).resolve().parent
19
  SPACE_HOST = os.environ.get("HYPERVIEW_HOST", "127.0.0.1")
20
  SPACE_PORT = int(os.environ.get("HYPERVIEW_PORT", "6262"))
21
+ WORKSPACE_ID = os.environ.get("HYPERVIEW_WORKSPACE_ID", "fashion-retail-search-actual-scatter")
22
  DATASET_NAME = os.environ.get("HYPERVIEW_DATASET_NAME", "deepfashion_text_search_clip_hyper3clip")
23
  EXTENSION_DIR = SPACE_DIR / ".hyperview" / "extensions" / "fashion-search-readout"
24
 
 
37
  "true",
38
  "yes",
39
  }
40
+ ENABLE_CONTEXT_MAPS = os.environ.get("FASHION_ENABLE_CONTEXT_MAPS", "1").lower() in {
41
  "1",
42
  "true",
43
  "yes",
 
45
  EMBEDDING_MAX_ATTEMPTS = max(1, int(os.environ.get("HYPERVIEW_EMBEDDING_MAX_ATTEMPTS", "4")))
46
  EMBEDDING_RETRY_DELAY_SECONDS = float(os.environ.get("HYPERVIEW_EMBEDDING_RETRY_DELAY_SECONDS", "15"))
47
  RUNTIME_WARNINGS: list[str] = []
48
+ DEFAULT_EXAMPLE_ID = os.environ.get("FASHION_DEFAULT_EXAMPLE_ID", "light-denim-leggings")
49
 
50
  MODEL_SPECS = [
51
  {
 
335
  return layouts
336
 
337
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
338
  def build_dataset() -> tuple[hv.Dataset, dict[str, str]]:
339
  dataset = hv.Dataset(DATASET_NAME)
340
  add_deepfashion_samples(dataset)
341
  if ENABLE_CONTEXT_MAPS:
342
  layouts = ensure_layouts(dataset)
343
  else:
 
344
  layouts = {}
345
  return dataset, layouts
346
 
 
355
  "displayName": spec["display_name"],
356
  "buttonLabel": spec["button_label"],
357
  "layoutKey": layout_key,
358
+ "spaceKey": spec.get("space_key"),
359
  }
360
  )
361
  return props
 
418
  shared_props = {
419
  "models": model_panel_props(layouts),
420
  "examples": build_examples(dataset),
421
+ "initialExampleId": DEFAULT_EXAMPLE_ID,
422
  "warnings": RUNTIME_WARNINGS,
423
  "metrics": {
424
  "typedQueryCount": 180,
 
444
  title="Ranked Search Results",
445
  extension="fashion-search-readout",
446
  panel="fashion-comparison",
447
+ position="center",
448
+ reference_panel_id="grid",
449
+ direction="left",
450
  props={
451
  **shared_props,
452
  "mode": "results",
453
  "dockview": {
454
  "closeSamplesPanel": False,
455
  "hideExplorerPanel": True,
456
+ "initialWidth": int(os.environ.get("FASHION_RESULTS_WIDTH", "760")),
457
  "minimumWidth": 620,
 
458
  },
459
  },
460
  )
 
464
 
465
  clip_spec = MODEL_SPECS[0]
466
  candidate_spec = MODEL_SPECS[1]
467
+ map_dockview = {
468
+ "initialHeight": int(os.environ.get("FASHION_MAP_HEIGHT", "280")),
469
+ "minimumHeight": 220,
 
470
  }
471
  clip_map = hv.ui.Scatter(
472
  id="fashion-map-clip",
473
  title="Context Map: CLIP",
474
  layout_key=layouts["clip"],
475
+ position="bottom",
476
+ reference_panel_id="grid",
477
+ direction="below",
478
  geometry=clip_spec["geometry"],
479
  layout_dimension=clip_spec["layout_dimension"],
480
+ props={"dockview": map_dockview},
481
  )
482
  candidate_map = hv.ui.Scatter(
483
  id="fashion-map-hyper3",
484
  title="Context Map: Hyper3",
485
  layout_key=layouts["candidate"],
486
+ position="bottom",
487
  reference_panel_id="fashion-map-clip",
488
+ direction="right",
489
  geometry=candidate_spec["geometry"],
490
  layout_dimension=candidate_spec["layout_dimension"],
491
+ props={"dockview": map_dockview},
492
  )
493
  return hv.ui.View(results_panel, clip_map, candidate_map)
494
 
495
 
496
+ def initial_target_sample_id() -> str | None:
497
+ example = next(
498
+ (item for item in TEXT_SEARCH_EXAMPLES if item["id"] == DEFAULT_EXAMPLE_ID),
499
+ TEXT_SEARCH_EXAMPLES[0] if TEXT_SEARCH_EXAMPLES else None,
500
+ )
501
+ if example is None:
502
+ return None
503
+ return safe_sample_id(example["targetItemId"])
504
+
505
+
506
  def launch_demo(dataset: hv.Dataset, layouts: dict[str, str]) -> hv.Session:
507
  session = hv.launch(
508
  dataset,
 
516
  session.ui.add_extension(EXTENSION_DIR, workspace_id=WORKSPACE_ID)
517
  print("Applying DeepFashion retail search demo view...", flush=True)
518
  session.ui.apply_view(build_demo_view(dataset, layouts), workspace_id=WORKSPACE_ID)
519
+ if ENABLE_CONTEXT_MAPS and layouts:
520
+ session.ui.set_active_layout(layouts["clip"], workspace_id=WORKSPACE_ID)
521
+ sample_id = initial_target_sample_id()
522
+ if sample_id:
523
+ session.ui.set_selection([sample_id], workspace_id=WORKSPACE_ID)
524
  print(f"\nHyperView DeepFashion text-search demo is running at {session.url}", flush=True)
525
  if ENABLE_CONTEXT_MAPS:
526
+ print(" Samples and nearest neighbors stay visible; scatter maps use the actual CLIP/Hyper3 layouts.", flush=True)
527
  else:
528
  print(" Samples stay visible; ranked text-search results are the main demo.", flush=True)
529
  return session