PageSourceSearch

https://pressthink.org/j/rosen-archive/frontend/components/SemanticSearchToggle.js?v=3.8.36

js pressthink.org collected 2026-10-02 04:26:47 UTC 3,989 bytes, 107 lines download raw bytes

1import { useState } from 'react';
2import { html } from '../html.js?v=3.8.36';
3import { Sparkles } from 'lucide-react';
4import SemanticSearchHelpDialog from './SemanticSearchHelpDialog.js?v=3.8.36';
5
6/**
7 * Short line for the archive's live region, announced when the semantic state
8 * changes (#279).
9 *
10 * The toggle is rendered twice, once beside each search box, so it must not
11 * carry the live region itself: two copies in the accessibility tree announce
12 * one change twice. App renders one live region and feeds it this message.
13 */
14export function semanticStatusMessage({ enabled, status = 'idle', coverage = null } = {}) {
15  if (status === 'error') {
16    return 'Search by meaning is not available. Search by words still works.';
17  }
18  if (!enabled) return 'Search by meaning is off.';
19  if (status === 'loading') return 'Search by meaning is loading the language model.';
20  if (status === 'searching') return 'Finding records by meaning.';
21  return Number.isFinite(coverage) && coverage > 0
22    ? `Search by meaning is on. Searching ${coverage.toLocaleString()} records.`
23    : 'Search by meaning is on.';
24}
25
26/**
27 * The opt-in semantic search switch that sits beside the archive search box
28 * (#279).
29 *
30 * Off by default, and off costs nothing: no model, no vectors, no extra bytes.
31 * Turning it on downloads a language model once, so the off state says so
32 * before the reader commits to it.
33 *
34 * The on state reports the loaded coverage so the reader can confirm how much
35 * of the archive is ready to search by meaning.
36 *
37 * A plain checkbox, not a custom widget: it is keyboard operable, announces its
38 * own state, and takes the app's focus ring for free. `describedBy` ties the
39 * hint to the control so a screen reader hears the cost and the current state
40 * with the label. The hint is a description only; the change announcement comes
41 * from the single live region App owns.
42 */
43const SemanticSearchToggle = ({
44  enabled,
45  status = 'idle',
46  coverage = null,
47  onToggle,
48  inputId = 'semantic-search',
49}) => {
50  const [helpOpen, setHelpOpen] = useState(false);
51  const hintId = `${inputId}-hint`;
52  const helpId = `${inputId}-help`;
53  const covered = Number.isFinite(coverage) && coverage > 0
54    ? coverage.toLocaleString()
55    : null;
56
57  let hint;
58  if (status === 'error') {
59    hint = 'Search by meaning is not available right now. Search by words still works.';
60  } else if (!enabled) {
61    hint = 'Also find records that match what you mean, not just the words you typed. Downloads about 50 MB the first time.';
62  } else if (status === 'loading') {
63    hint = 'Loading the language model. The first time takes a few seconds.';
64  } else if (status === 'searching') {
65    hint = 'Finding records by meaning...';
66  } else {
67    hint = covered
68      ? `Searching ${covered} records by meaning. Each result says how it matched.`
69      : 'Searching records by meaning. Each result says how it matched.';
70  }
71
72  return html`
73    <div className="archive-semantic-toggle">
74      <label className="archive-semantic-toggle__control" htmlFor=${inputId}>
75        <input
76          id=${inputId}
77          type="checkbox"
78          checked=${Boolean(enabled)}
79          onChange=${event => onToggle(event.target.checked)}
80          aria-describedby=${hintId}
81        />
82        <${Sparkles} className="archive-semantic-toggle__icon" aria-hidden="true" />
83        <span>Search by meaning</span>
84      </label>
85      <p id=${hintId} className="archive-semantic-toggle__hint">
86        ${hint}
87      </p>
88      <button
89        type="button"
90        className="archive-semantic-toggle__help"
91        onClick=${() => setHelpOpen(true)}
92        aria-haspopup="dialog"
93        aria-controls=${helpId}
94      >
95        How does this work?
96      </button>
97      <${SemanticSearchHelpDialog}
98        isOpen=${helpOpen}
99        onClose=${() => setHelpOpen(false)}
100        coverage=${coverage}
101        dialogId=${helpId}
102      />
103    </div>
104  `;
105};
106
107export default SemanticSearchToggle;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.