@mailwoman/react 0.0.1 → 7.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +34 -4
- package/common/ClientOnly.tsx +30 -0
- package/common/CopyButton.tsx +44 -0
- package/common/KindBadge.tsx +44 -0
- package/common/LoadingIndicator.tsx +104 -0
- package/common/PresetChips.tsx +43 -0
- package/common/cx.ts +34 -0
- package/common/useClipboard.ts +73 -0
- package/common/useDebouncedValue.ts +23 -0
- package/index.ts +86 -4
- package/out/common/ClientOnly.d.ts +22 -0
- package/out/common/ClientOnly.d.ts.map +1 -0
- package/out/common/ClientOnly.js +20 -0
- package/out/common/ClientOnly.js.map +1 -0
- package/out/common/CopyButton.d.ts +21 -0
- package/out/common/CopyButton.d.ts.map +1 -0
- package/out/common/CopyButton.js +7 -0
- package/out/common/CopyButton.js.map +1 -0
- package/out/common/KindBadge.d.ts +24 -0
- package/out/common/KindBadge.d.ts.map +1 -0
- package/out/common/KindBadge.js +6 -0
- package/out/common/KindBadge.js.map +1 -0
- package/out/common/LoadingIndicator.d.ts +27 -0
- package/out/common/LoadingIndicator.d.ts.map +1 -0
- package/out/common/LoadingIndicator.js +41 -0
- package/out/common/LoadingIndicator.js.map +1 -0
- package/out/common/PresetChips.d.ts +22 -0
- package/out/common/PresetChips.d.ts.map +1 -0
- package/out/common/PresetChips.js +5 -0
- package/out/common/PresetChips.js.map +1 -0
- package/out/common/cx.d.ts +13 -0
- package/out/common/cx.d.ts.map +1 -0
- package/out/common/cx.js +28 -0
- package/out/common/cx.js.map +1 -0
- package/out/common/useClipboard.d.ts +16 -0
- package/out/common/useClipboard.d.ts.map +1 -0
- package/out/common/useClipboard.js +56 -0
- package/out/common/useClipboard.js.map +1 -0
- package/out/common/useDebouncedValue.d.ts +11 -0
- package/out/common/useDebouncedValue.d.ts.map +1 -0
- package/out/common/useDebouncedValue.js +19 -0
- package/out/common/useDebouncedValue.js.map +1 -0
- package/out/index.d.ts +57 -4
- package/out/index.d.ts.map +1 -1
- package/out/index.js +37 -4
- package/out/index.js.map +1 -1
- package/out/pipeline/CandidatePicker.d.ts +17 -0
- package/out/pipeline/CandidatePicker.d.ts.map +1 -0
- package/out/pipeline/CandidatePicker.js +6 -0
- package/out/pipeline/CandidatePicker.js.map +1 -0
- package/out/pipeline/ComponentTable.d.ts +14 -0
- package/out/pipeline/ComponentTable.d.ts.map +1 -0
- package/out/pipeline/ComponentTable.js +6 -0
- package/out/pipeline/ComponentTable.js.map +1 -0
- package/out/pipeline/ConfidenceCell.d.ts +13 -0
- package/out/pipeline/ConfidenceCell.d.ts.map +1 -0
- package/out/pipeline/ConfidenceCell.js +22 -0
- package/out/pipeline/ConfidenceCell.js.map +1 -0
- package/out/pipeline/PipelineExplorer.d.ts +25 -0
- package/out/pipeline/PipelineExplorer.d.ts.map +1 -0
- package/out/pipeline/PipelineExplorer.js +27 -0
- package/out/pipeline/PipelineExplorer.js.map +1 -0
- package/out/pipeline/QueryForm.d.ts +19 -0
- package/out/pipeline/QueryForm.d.ts.map +1 -0
- package/out/pipeline/QueryForm.js +9 -0
- package/out/pipeline/QueryForm.js.map +1 -0
- package/out/pipeline/ResolvedPlace.d.ts +16 -0
- package/out/pipeline/ResolvedPlace.d.ts.map +1 -0
- package/out/pipeline/ResolvedPlace.js +14 -0
- package/out/pipeline/ResolvedPlace.js.map +1 -0
- package/out/pipeline/copy.d.ts +11 -0
- package/out/pipeline/copy.d.ts.map +1 -0
- package/out/pipeline/copy.js +31 -0
- package/out/pipeline/copy.js.map +1 -0
- package/out/pipeline/presets.d.ts +12 -0
- package/out/pipeline/presets.d.ts.map +1 -0
- package/out/pipeline/presets.js +21 -0
- package/out/pipeline/presets.js.map +1 -0
- package/out/pipeline/types.d.ts +110 -0
- package/out/pipeline/types.d.ts.map +1 -0
- package/out/pipeline/types.js +12 -0
- package/out/pipeline/types.js.map +1 -0
- package/out/pipeline/useParsePipeline.d.ts +34 -0
- package/out/pipeline/useParsePipeline.d.ts.map +1 -0
- package/out/pipeline/useParsePipeline.js +55 -0
- package/out/pipeline/useParsePipeline.js.map +1 -0
- package/out/poi/AbstainPanel.d.ts +14 -0
- package/out/poi/AbstainPanel.d.ts.map +1 -0
- package/out/poi/AbstainPanel.js +5 -0
- package/out/poi/AbstainPanel.js.map +1 -0
- package/out/poi/LiveResultsBlock.d.ts +21 -0
- package/out/poi/LiveResultsBlock.d.ts.map +1 -0
- package/out/poi/LiveResultsBlock.js +7 -0
- package/out/poi/LiveResultsBlock.js.map +1 -0
- package/out/poi/OverpassBlock.d.ts +15 -0
- package/out/poi/OverpassBlock.d.ts.map +1 -0
- package/out/poi/OverpassBlock.js +11 -0
- package/out/poi/OverpassBlock.js.map +1 -0
- package/out/poi/POIExplorer.d.ts +31 -0
- package/out/poi/POIExplorer.d.ts.map +1 -0
- package/out/poi/POIExplorer.js +43 -0
- package/out/poi/POIExplorer.js.map +1 -0
- package/out/poi/QueryInput.d.ts +17 -0
- package/out/poi/QueryInput.d.ts.map +1 -0
- package/out/poi/QueryInput.js +5 -0
- package/out/poi/QueryInput.js.map +1 -0
- package/out/poi/SubjectPanel.d.ts +16 -0
- package/out/poi/SubjectPanel.d.ts.map +1 -0
- package/out/poi/SubjectPanel.js +12 -0
- package/out/poi/SubjectPanel.js.map +1 -0
- package/out/poi/runtime.d.ts +43 -0
- package/out/poi/runtime.d.ts.map +1 -0
- package/out/poi/runtime.js +67 -0
- package/out/poi/runtime.js.map +1 -0
- package/out/poi/types.d.ts +113 -0
- package/out/poi/types.d.ts.map +1 -0
- package/out/poi/types.js +12 -0
- package/out/poi/types.js.map +1 -0
- package/out/poi/usePOISearch.d.ts +42 -0
- package/out/poi/usePOISearch.d.ts.map +1 -0
- package/out/poi/usePOISearch.js +158 -0
- package/out/poi/usePOISearch.js.map +1 -0
- package/package.json +52 -8
- package/pipeline/CandidatePicker.tsx +45 -0
- package/pipeline/ComponentTable.tsx +41 -0
- package/pipeline/ConfidenceCell.tsx +33 -0
- package/pipeline/PipelineExplorer.tsx +148 -0
- package/pipeline/QueryForm.tsx +52 -0
- package/pipeline/ResolvedPlace.tsx +50 -0
- package/pipeline/copy.ts +37 -0
- package/pipeline/presets.ts +24 -0
- package/pipeline/types.ts +118 -0
- package/pipeline/useParsePipeline.ts +84 -0
- package/poi/AbstainPanel.tsx +22 -0
- package/poi/LiveResultsBlock.tsx +70 -0
- package/poi/OverpassBlock.tsx +37 -0
- package/poi/POIExplorer.tsx +133 -0
- package/poi/QueryInput.tsx +26 -0
- package/poi/SubjectPanel.tsx +79 -0
- package/poi/runtime.ts +78 -0
- package/poi/types.ts +112 -0
- package/poi/usePOISearch.ts +234 -0
- package/styles.css +648 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mailwoman/react",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "React components + headless hooks for mailwoman — parse/geocode/POI explorers, Storybook-tested, Docusaurus-independent.
|
|
3
|
+
"version": "7.5.0",
|
|
4
|
+
"description": "React components + headless hooks for mailwoman — parse/geocode/POI explorers, Storybook-tested, Docusaurus-independent.",
|
|
5
5
|
"license": "AGPL-3.0-only OR LicenseRef-Commercial",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -13,6 +13,8 @@
|
|
|
13
13
|
"out/**/*.js.map",
|
|
14
14
|
"out/**/*.d.ts",
|
|
15
15
|
"out/**/*.d.ts.map",
|
|
16
|
+
"README.md",
|
|
17
|
+
"styles.css",
|
|
16
18
|
"*.ts",
|
|
17
19
|
"*.tsx",
|
|
18
20
|
"**/*.ts",
|
|
@@ -20,21 +22,63 @@
|
|
|
20
22
|
"!*.test.ts",
|
|
21
23
|
"!*.test.tsx",
|
|
22
24
|
"!**/*.test.ts",
|
|
23
|
-
"!**/*.test.tsx"
|
|
25
|
+
"!**/*.test.tsx",
|
|
26
|
+
"!**/*.stories.ts",
|
|
27
|
+
"!**/*.stories.tsx",
|
|
28
|
+
"!.storybook/**",
|
|
29
|
+
"!vitest.config.ts",
|
|
30
|
+
"!test/**",
|
|
31
|
+
"!out/test/**",
|
|
32
|
+
"!out/storybook-static/**",
|
|
33
|
+
"!storybook-static/**"
|
|
24
34
|
],
|
|
25
35
|
"type": "module",
|
|
26
36
|
"exports": {
|
|
27
37
|
"./package.json": "./package.json",
|
|
38
|
+
"./styles.css": "./styles.css",
|
|
28
39
|
".": {
|
|
29
|
-
"
|
|
30
|
-
"default": "./out/index.js"
|
|
31
|
-
"types": "./out/index.d.ts"
|
|
40
|
+
"types": "./out/index.d.ts",
|
|
41
|
+
"default": "./out/index.js"
|
|
32
42
|
}
|
|
33
43
|
},
|
|
34
44
|
"publishConfig": {
|
|
35
|
-
"access": "public"
|
|
45
|
+
"access": "public",
|
|
46
|
+
"exports": {
|
|
47
|
+
"./package.json": "./package.json",
|
|
48
|
+
"./styles.css": "./styles.css",
|
|
49
|
+
".": {
|
|
50
|
+
"types": "./out/index.d.ts",
|
|
51
|
+
"default": "./out/index.js"
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"scripts": {
|
|
56
|
+
"storybook": "storybook dev -p 6007 --no-open",
|
|
57
|
+
"build-storybook": "storybook build",
|
|
58
|
+
"test:browser": "vitest run --config vitest.config.ts"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@mailwoman/kind-classifier": "7.5.0",
|
|
62
|
+
"@mailwoman/poi-taxonomy": "7.5.0",
|
|
63
|
+
"@mailwoman/query-shape": "7.5.0"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@storybook/addon-docs": "^10.5.2",
|
|
67
|
+
"@storybook/react-vite": "^10.5.2",
|
|
68
|
+
"@types/react": "^19.2.17",
|
|
69
|
+
"@types/react-dom": "^19.2.3",
|
|
70
|
+
"@vitejs/plugin-react": "^6.0.3",
|
|
71
|
+
"@vitest/browser": "4.1.10",
|
|
72
|
+
"@vitest/browser-playwright": "4.1.10",
|
|
73
|
+
"playwright": "^1.61.1",
|
|
74
|
+
"react": "^19.2.7",
|
|
75
|
+
"react-dom": "^19.2.7",
|
|
76
|
+
"storybook": "^10.5.2",
|
|
77
|
+
"vite": "^8.1.5",
|
|
78
|
+
"vitest": "4.1.10"
|
|
36
79
|
},
|
|
37
80
|
"peerDependencies": {
|
|
38
|
-
"react": ">=18"
|
|
81
|
+
"react": ">=18",
|
|
82
|
+
"react-dom": ">=18"
|
|
39
83
|
}
|
|
40
84
|
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `CandidatePicker` — the ranked list of alternate gazetteer candidates; clicking one selects it.
|
|
7
|
+
* Presentational; the selected index is owned by the caller (the pipeline hook).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ReactNode } from "react"
|
|
11
|
+
|
|
12
|
+
import { cx } from "../common/cx.ts"
|
|
13
|
+
import type { ResolvedPlaceView } from "./types.ts"
|
|
14
|
+
|
|
15
|
+
export interface CandidatePickerProps {
|
|
16
|
+
candidates: ResolvedPlaceView[]
|
|
17
|
+
selectedIndex: number
|
|
18
|
+
onSelect: (index: number) => void
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function CandidatePicker({ candidates, selectedIndex, onSelect }: CandidatePickerProps): ReactNode {
|
|
22
|
+
return (
|
|
23
|
+
<div className="mw-candidates">
|
|
24
|
+
<h2>Other candidates ({candidates.length - 1})</h2>
|
|
25
|
+
<ol className="mw-candidates__list">
|
|
26
|
+
{candidates.map((candidate, i) => (
|
|
27
|
+
<li key={`${candidate.id}-${i}`}>
|
|
28
|
+
<button
|
|
29
|
+
type="button"
|
|
30
|
+
className={cx("mw-candidates__btn", { "mw-candidates__btn--active": i === selectedIndex })}
|
|
31
|
+
onClick={() => onSelect(i)}
|
|
32
|
+
title={`${candidate.placetype} • WOF ${candidate.id} • score ${candidate.score.toFixed(3)}`}
|
|
33
|
+
>
|
|
34
|
+
<span className="mw-candidates__rank">#{i + 1}</span>
|
|
35
|
+
<span className="mw-candidates__name">{candidate.name}</span>
|
|
36
|
+
<span className="mw-candidates__meta">
|
|
37
|
+
{candidate.placetype} · {candidate.score.toFixed(2)}
|
|
38
|
+
</span>
|
|
39
|
+
</button>
|
|
40
|
+
</li>
|
|
41
|
+
))}
|
|
42
|
+
</ol>
|
|
43
|
+
</div>
|
|
44
|
+
)
|
|
45
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `ComponentTable` — the tag / value / confidence table of decoded components. Presentational.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { ReactNode } from "react"
|
|
10
|
+
|
|
11
|
+
import { ConfidenceCell } from "./ConfidenceCell.tsx"
|
|
12
|
+
import type { ParsedComponent } from "./types.ts"
|
|
13
|
+
|
|
14
|
+
export interface ComponentTableProps {
|
|
15
|
+
nodes: ParsedComponent[]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function ComponentTable({ nodes }: ComponentTableProps): ReactNode {
|
|
19
|
+
return (
|
|
20
|
+
<table className="mw-components">
|
|
21
|
+
<thead>
|
|
22
|
+
<tr>
|
|
23
|
+
<th>tag</th>
|
|
24
|
+
<th>value</th>
|
|
25
|
+
<th>confidence</th>
|
|
26
|
+
</tr>
|
|
27
|
+
</thead>
|
|
28
|
+
<tbody>
|
|
29
|
+
{nodes.map((node, i) => (
|
|
30
|
+
<tr key={i}>
|
|
31
|
+
<td>{node.tag}</td>
|
|
32
|
+
<td>{String(node.value ?? "")}</td>
|
|
33
|
+
<td>
|
|
34
|
+
<ConfidenceCell confidence={node.confidence} />
|
|
35
|
+
</td>
|
|
36
|
+
</tr>
|
|
37
|
+
))}
|
|
38
|
+
</tbody>
|
|
39
|
+
</table>
|
|
40
|
+
)
|
|
41
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `ConfidenceCell` — a compact confidence bar + value for the component table. Tiers the fill colour
|
|
7
|
+
* at 0.8 / 0.5. Presentational.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { cx } from "../common/cx.ts"
|
|
11
|
+
|
|
12
|
+
function tier(confidence?: number): "high" | "mid" | "low" {
|
|
13
|
+
if (confidence == null) return "mid"
|
|
14
|
+
|
|
15
|
+
return confidence >= 0.8 ? "high" : confidence >= 0.5 ? "mid" : "low"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ConfidenceCellProps {
|
|
19
|
+
confidence?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ConfidenceCell({ confidence }: ConfidenceCellProps) {
|
|
23
|
+
if (confidence == null) return <span className="mw-conf__dash">—</span>
|
|
24
|
+
|
|
25
|
+
const pct = Math.max(0, Math.min(1, confidence)) * 100
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="mw-conf">
|
|
29
|
+
<div className={cx("mw-conf__bar", `mw-conf__bar--${tier(confidence)}`)} style={{ width: `${pct}%` }} />
|
|
30
|
+
<span className="mw-conf__value">{confidence.toFixed(2)}</span>
|
|
31
|
+
</div>
|
|
32
|
+
)
|
|
33
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `PipelineExplorer` — the composed parse+resolve tester. Drives the injected {@link PipelineRuntime}
|
|
7
|
+
* through {@link useParsePipeline}, renders the core result (kind badge, component table, resolved
|
|
8
|
+
* place, candidate picker), and slots host-supplied panels (span highlight, tree, timing, …) around
|
|
9
|
+
* it. No model/gazetteer code here — composition + a `ClientOnly` SSR boundary only.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ReactNode } from "react"
|
|
13
|
+
|
|
14
|
+
import { ClientOnly } from "../common/ClientOnly.tsx"
|
|
15
|
+
import { CopyButton } from "../common/CopyButton.tsx"
|
|
16
|
+
import { KindBadge } from "../common/KindBadge.tsx"
|
|
17
|
+
import { LoadingIndicator } from "../common/LoadingIndicator.tsx"
|
|
18
|
+
import { PresetChips, type Preset } from "../common/PresetChips.tsx"
|
|
19
|
+
import { CandidatePicker } from "./CandidatePicker.tsx"
|
|
20
|
+
import { ComponentTable } from "./ComponentTable.tsx"
|
|
21
|
+
import { buildParsePayload } from "./copy.ts"
|
|
22
|
+
import { PIPELINE_DEFAULT_ADDRESS, PIPELINE_PRESETS } from "./presets.ts"
|
|
23
|
+
import { QueryForm } from "./QueryForm.tsx"
|
|
24
|
+
import { ResolvedPlace } from "./ResolvedPlace.tsx"
|
|
25
|
+
import type { PipelinePanels, PipelineRuntime } from "./types.ts"
|
|
26
|
+
import { useParsePipeline } from "./useParsePipeline.ts"
|
|
27
|
+
|
|
28
|
+
export interface PipelineExplorerProps {
|
|
29
|
+
/** The injected model/gazetteer runtime (the host wires this to its bundle). */
|
|
30
|
+
runtime: PipelineRuntime
|
|
31
|
+
/** Address to pre-fill. @default the White House */
|
|
32
|
+
defaultAddress?: string
|
|
33
|
+
/** Example chips. @default the built-in address presets */
|
|
34
|
+
presets?: ReadonlyArray<Preset>
|
|
35
|
+
/** Host-injected panels + controls (version selector, span highlight, tree, …). */
|
|
36
|
+
panels?: PipelinePanels
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface PipelineExplorerInnerProps extends Required<Pick<PipelineExplorerProps, "runtime" | "defaultAddress">> {
|
|
40
|
+
presets: ReadonlyArray<Preset>
|
|
41
|
+
panels: PipelinePanels
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function PipelineExplorerInner({ runtime, defaultAddress, presets, panels }: PipelineExplorerInnerProps): ReactNode {
|
|
45
|
+
const pipeline = useParsePipeline({ runtime, defaultText: defaultAddress })
|
|
46
|
+
const { text, setText, busy, parseStage, result, parseError, selectedCandidate } = pipeline
|
|
47
|
+
|
|
48
|
+
const loading = runtime.loading
|
|
49
|
+
const errorMessage = parseError ?? runtime.errorMessage ?? null
|
|
50
|
+
|
|
51
|
+
return (
|
|
52
|
+
<div className="mw-pipeline-explorer">
|
|
53
|
+
{panels.header}
|
|
54
|
+
{panels.releaseInfo}
|
|
55
|
+
{panels.versionControl}
|
|
56
|
+
{panels.backendControl}
|
|
57
|
+
|
|
58
|
+
<QueryForm
|
|
59
|
+
value={text}
|
|
60
|
+
onChange={setText}
|
|
61
|
+
onSubmit={pipeline.submit}
|
|
62
|
+
disabled={!runtime.ready}
|
|
63
|
+
busy={busy}
|
|
64
|
+
placeholder={PIPELINE_DEFAULT_ADDRESS}
|
|
65
|
+
/>
|
|
66
|
+
|
|
67
|
+
<PresetChips
|
|
68
|
+
presets={presets}
|
|
69
|
+
disabled={!runtime.ready || busy}
|
|
70
|
+
onPick={(value) => {
|
|
71
|
+
setText(value)
|
|
72
|
+
pipeline.reset()
|
|
73
|
+
}}
|
|
74
|
+
/>
|
|
75
|
+
|
|
76
|
+
{loading && !runtime.ready ? (
|
|
77
|
+
<LoadingIndicator
|
|
78
|
+
mode="staged"
|
|
79
|
+
steps={loading.stepLabels.length > 0 ? loading.stepLabels : undefined}
|
|
80
|
+
activeStep={loading.stepIndex}
|
|
81
|
+
label={loading.progress}
|
|
82
|
+
/>
|
|
83
|
+
) : null}
|
|
84
|
+
{errorMessage ? <p className="mw-error">{errorMessage}</p> : null}
|
|
85
|
+
|
|
86
|
+
{busy ? (
|
|
87
|
+
<div className="mw-result">
|
|
88
|
+
<LoadingIndicator mode="staged" steps={runtime.parseStageLabels} activeStep={parseStage} />
|
|
89
|
+
</div>
|
|
90
|
+
) : result ? (
|
|
91
|
+
<div className="mw-result">
|
|
92
|
+
<div className="mw-result__header">
|
|
93
|
+
<h2>Parsed components</h2>
|
|
94
|
+
<CopyButton
|
|
95
|
+
value={() => buildParsePayload(result, selectedCandidate)}
|
|
96
|
+
label="Copy JSON"
|
|
97
|
+
copiedLabel="✓ Copied"
|
|
98
|
+
/>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{result.kindResult ? <KindBadge kindResult={result.kindResult} /> : null}
|
|
102
|
+
|
|
103
|
+
{panels.extras ? panels.extras(result) : null}
|
|
104
|
+
|
|
105
|
+
<ComponentTable nodes={result.nodes} />
|
|
106
|
+
|
|
107
|
+
{selectedCandidate ? (
|
|
108
|
+
<>
|
|
109
|
+
<ResolvedPlace place={selectedCandidate} dualRoles={result.dualRoles} />
|
|
110
|
+
{result.candidates.length > 1 ? (
|
|
111
|
+
<CandidatePicker
|
|
112
|
+
candidates={result.candidates}
|
|
113
|
+
selectedIndex={pipeline.selectedCandidateIndex}
|
|
114
|
+
onSelect={pipeline.selectCandidate}
|
|
115
|
+
/>
|
|
116
|
+
) : null}
|
|
117
|
+
</>
|
|
118
|
+
) : panels.failure ? (
|
|
119
|
+
panels.failure(result)
|
|
120
|
+
) : null}
|
|
121
|
+
</div>
|
|
122
|
+
) : null}
|
|
123
|
+
|
|
124
|
+
{panels.footer}
|
|
125
|
+
</div>
|
|
126
|
+
)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function PipelineExplorer({
|
|
130
|
+
runtime,
|
|
131
|
+
defaultAddress = PIPELINE_DEFAULT_ADDRESS,
|
|
132
|
+
presets = PIPELINE_PRESETS,
|
|
133
|
+
panels = {},
|
|
134
|
+
}: PipelineExplorerProps): ReactNode {
|
|
135
|
+
return (
|
|
136
|
+
<ClientOnly
|
|
137
|
+
fallback={
|
|
138
|
+
<div className="mw-pipeline-explorer">
|
|
139
|
+
<p>Loading demo embed…</p>
|
|
140
|
+
</div>
|
|
141
|
+
}
|
|
142
|
+
>
|
|
143
|
+
{() => (
|
|
144
|
+
<PipelineExplorerInner runtime={runtime} defaultAddress={defaultAddress} presets={presets} panels={panels} />
|
|
145
|
+
)}
|
|
146
|
+
</ClientOnly>
|
|
147
|
+
)
|
|
148
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `QueryForm` — the address input + submit button for the pipeline explorer. Shows a spinner while
|
|
7
|
+
* parsing. Presentational; `onSubmit` fires on form submission.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ReactNode } from "react"
|
|
11
|
+
|
|
12
|
+
import { LoadingIndicator } from "../common/LoadingIndicator.tsx"
|
|
13
|
+
|
|
14
|
+
export interface QueryFormProps {
|
|
15
|
+
value: string
|
|
16
|
+
onChange: (value: string) => void
|
|
17
|
+
onSubmit: () => void
|
|
18
|
+
disabled?: boolean
|
|
19
|
+
busy?: boolean
|
|
20
|
+
placeholder?: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function QueryForm({ value, onChange, onSubmit, disabled, busy, placeholder }: QueryFormProps): ReactNode {
|
|
24
|
+
return (
|
|
25
|
+
<form
|
|
26
|
+
className="mw-field"
|
|
27
|
+
onSubmit={(e) => {
|
|
28
|
+
e.preventDefault()
|
|
29
|
+
onSubmit()
|
|
30
|
+
}}
|
|
31
|
+
>
|
|
32
|
+
<label htmlFor="mw-pipeline-input">Address</label>
|
|
33
|
+
<input
|
|
34
|
+
id="mw-pipeline-input"
|
|
35
|
+
type="text"
|
|
36
|
+
value={value}
|
|
37
|
+
onChange={(e) => onChange(e.target.value)}
|
|
38
|
+
disabled={disabled}
|
|
39
|
+
placeholder={placeholder}
|
|
40
|
+
/>
|
|
41
|
+
<button type="submit" disabled={disabled || busy}>
|
|
42
|
+
{busy ? (
|
|
43
|
+
<>
|
|
44
|
+
<LoadingIndicator mode="spinner" size="small" /> Parsing…
|
|
45
|
+
</>
|
|
46
|
+
) : (
|
|
47
|
+
"Parse + resolve"
|
|
48
|
+
)}
|
|
49
|
+
</button>
|
|
50
|
+
</form>
|
|
51
|
+
)
|
|
52
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* `ResolvedPlace` — the "Resolved place" detail list (name, placetype, WOF id, coords, score) plus an
|
|
7
|
+
* optional dual-role note. Presentational.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Fragment, type ReactNode } from "react"
|
|
11
|
+
|
|
12
|
+
import type { DualRoleView, ResolvedPlaceView } from "./types.ts"
|
|
13
|
+
|
|
14
|
+
export interface ResolvedPlaceProps {
|
|
15
|
+
place: ResolvedPlaceView
|
|
16
|
+
dualRoles?: DualRoleView[]
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function ResolvedPlace({ place, dualRoles }: ResolvedPlaceProps): ReactNode {
|
|
20
|
+
return (
|
|
21
|
+
<div className="mw-resolved">
|
|
22
|
+
<h2>Resolved place</h2>
|
|
23
|
+
<dl>
|
|
24
|
+
<dt>name</dt>
|
|
25
|
+
<dd>{place.name}</dd>
|
|
26
|
+
<dt>placetype</dt>
|
|
27
|
+
<dd>{place.placetype}</dd>
|
|
28
|
+
<dt>WOF id</dt>
|
|
29
|
+
<dd>{place.id}</dd>
|
|
30
|
+
<dt>coords</dt>
|
|
31
|
+
<dd>
|
|
32
|
+
{place.lat.toFixed(4)}, {place.lon.toFixed(4)}
|
|
33
|
+
</dd>
|
|
34
|
+
<dt>score</dt>
|
|
35
|
+
<dd>{place.score.toFixed(3)}</dd>
|
|
36
|
+
</dl>
|
|
37
|
+
{dualRoles && dualRoles.length > 0 ? (
|
|
38
|
+
<p className="mw-resolved__dual">
|
|
39
|
+
🏛️ <strong>Dual-role place.</strong> {place.name} also resolves as{" "}
|
|
40
|
+
{dualRoles.map((role, i) => (
|
|
41
|
+
<Fragment key={`${role.role}-${role.id}`}>
|
|
42
|
+
{i > 0 ? ", " : ""}a <strong>{role.role}</strong> ({role.relationshipType.replace(/-/g, " ")})
|
|
43
|
+
</Fragment>
|
|
44
|
+
))}
|
|
45
|
+
.
|
|
46
|
+
</p>
|
|
47
|
+
) : null}
|
|
48
|
+
</div>
|
|
49
|
+
)
|
|
50
|
+
}
|
package/pipeline/copy.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Builds the "Copy JSON" payload for a parse result — the components + the selected resolved place,
|
|
7
|
+
* pretty-printed. Pure; shared by the explorer's copy button.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { ParseResult, ResolvedPlaceView } from "./types.ts"
|
|
11
|
+
|
|
12
|
+
export function buildParsePayload(result: ParseResult, selected: ResolvedPlaceView | null): string {
|
|
13
|
+
return JSON.stringify(
|
|
14
|
+
{
|
|
15
|
+
input: result.input,
|
|
16
|
+
components: result.nodes.map((node) => ({
|
|
17
|
+
tag: node.tag,
|
|
18
|
+
value: node.value ?? null,
|
|
19
|
+
confidence: node.confidence ?? null,
|
|
20
|
+
start: node.start ?? null,
|
|
21
|
+
end: node.end ?? null,
|
|
22
|
+
})),
|
|
23
|
+
resolved: selected
|
|
24
|
+
? {
|
|
25
|
+
name: selected.name,
|
|
26
|
+
placetype: selected.placetype,
|
|
27
|
+
id: selected.id,
|
|
28
|
+
lat: selected.lat,
|
|
29
|
+
lon: selected.lon,
|
|
30
|
+
score: selected.score,
|
|
31
|
+
}
|
|
32
|
+
: null,
|
|
33
|
+
},
|
|
34
|
+
null,
|
|
35
|
+
2
|
|
36
|
+
)
|
|
37
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Default query + example addresses for the pipeline explorer. Hosts can override via props; these
|
|
7
|
+
* mirror the docs demo's set so the extracted component looks identical out of the box.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Preset } from "../common/PresetChips.tsx"
|
|
11
|
+
|
|
12
|
+
export const PIPELINE_DEFAULT_ADDRESS = "1600 Pennsylvania Ave NW, Washington, DC 20500"
|
|
13
|
+
|
|
14
|
+
export const PIPELINE_PRESETS: ReadonlyArray<Preset> = [
|
|
15
|
+
{ label: "White House", value: "1600 Pennsylvania Ave NW, Washington, DC 20500" },
|
|
16
|
+
{ label: "Apple Park", value: "1 Apple Park Way, Cupertino, CA 95014" },
|
|
17
|
+
{ label: "30 Rockefeller Plaza", value: "30 Rockefeller Plaza, New York, NY 10112" },
|
|
18
|
+
{ label: "Pier 39 SF", value: "Pier 39, San Francisco, CA 94133" },
|
|
19
|
+
{ label: "Wrigley Field", value: "1060 W Addison St, Chicago, IL 60613" },
|
|
20
|
+
{ label: "Space Needle", value: "400 Broad St, Seattle, WA 98109" },
|
|
21
|
+
{ label: "ZIP only", value: "90210" },
|
|
22
|
+
{ label: "Berlin (native order)", value: "Straußstraße 27, 12623 Berlin" },
|
|
23
|
+
{ label: "Paris (street fall-through)", value: "181 Rue du Chevaleret, Paris" },
|
|
24
|
+
]
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @copyright Sister Software
|
|
3
|
+
* @license AGPL-3.0
|
|
4
|
+
* @author Teffen Ellis, et al.
|
|
5
|
+
*
|
|
6
|
+
* Types for the pipeline (parse + resolve) explorer. The model/gazetteer runtime is INJECTED as a
|
|
7
|
+
* {@link PipelineRuntime}: this package owns the UI state machine + presentation, while the host
|
|
8
|
+
* (the docs site's DemoEmbed, or any app) owns loading ONNX/WOF and executing a parse. That keeps
|
|
9
|
+
* onnxruntime-web, sql.js-httpvfs, and node builtins entirely out of this package's browser graph.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { ReactNode } from "react"
|
|
13
|
+
|
|
14
|
+
import type { KindBadgeResult } from "../common/KindBadge.tsx"
|
|
15
|
+
|
|
16
|
+
/** One decoded component, as the table + span views render it. Offsets index into `ParseResult.input`. */
|
|
17
|
+
export interface ParsedComponent {
|
|
18
|
+
tag: string
|
|
19
|
+
value?: unknown
|
|
20
|
+
confidence?: number
|
|
21
|
+
start?: number
|
|
22
|
+
end?: number
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** A resolved gazetteer place, projected to what the "Resolved place" panel + copy payload need. */
|
|
26
|
+
export interface ResolvedPlaceView {
|
|
27
|
+
id: number
|
|
28
|
+
name: string
|
|
29
|
+
placetype: string
|
|
30
|
+
lat: number
|
|
31
|
+
lon: number
|
|
32
|
+
score: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** An additional admin role a resolved place also fulfils (the dual-role / city-state relation, #402). */
|
|
36
|
+
export interface DualRoleView {
|
|
37
|
+
id: number
|
|
38
|
+
name: string
|
|
39
|
+
placetype: string
|
|
40
|
+
relationshipType: string
|
|
41
|
+
role: string
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Per-stage wall-clock (ms) for one parse. `resolve` is absent when the lookup is skipped. */
|
|
45
|
+
export interface StageTiming {
|
|
46
|
+
shape: number
|
|
47
|
+
classify: number
|
|
48
|
+
resolve?: number
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** FST gazetteer-prior provenance, as the "FST prior" disclosure renders it. */
|
|
52
|
+
export interface FSTProvenance {
|
|
53
|
+
builtAt: string
|
|
54
|
+
stateCount: number
|
|
55
|
+
placeCount: number
|
|
56
|
+
importanceMatches: number
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** The presentational projection of one parse+resolve. The host's `runParse` produces it. */
|
|
60
|
+
export interface ParseResult {
|
|
61
|
+
input: string
|
|
62
|
+
/** Opaque hierarchy — handed straight to a host-injected tree/visualizer panel. */
|
|
63
|
+
tree: unknown
|
|
64
|
+
nodes: ParsedComponent[]
|
|
65
|
+
kindResult?: KindBadgeResult
|
|
66
|
+
timing?: StageTiming
|
|
67
|
+
resolved: ResolvedPlaceView | null
|
|
68
|
+
candidates: ResolvedPlaceView[]
|
|
69
|
+
fstActive: boolean
|
|
70
|
+
fstProvenance?: FSTProvenance | null
|
|
71
|
+
dualRoles?: DualRoleView[]
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Bundle-load progress surfaced before the runtime is `ready`. */
|
|
75
|
+
export interface PipelineLoadingState {
|
|
76
|
+
progress?: string
|
|
77
|
+
stepLabels: string[]
|
|
78
|
+
stepIndex: number
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* The injected parse runtime. The host implements `runParse` (compute shape → classify → resolve) and reports load
|
|
83
|
+
* progress + errors. This package never imports the model or gazetteer — it only calls this contract.
|
|
84
|
+
*/
|
|
85
|
+
export interface PipelineRuntime {
|
|
86
|
+
/** Whether the model/gazetteer bundle is ready to parse. */
|
|
87
|
+
ready: boolean
|
|
88
|
+
/** Execute a full parse+resolve, reporting stage progress via `onStage` (0-based index into `parseStageLabels`). */
|
|
89
|
+
runParse: (input: string, hooks: { onStage: (stage: number) => void }) => Promise<ParseResult>
|
|
90
|
+
/** Per-parse progress labels (differ by whether a gazetteer lookup is wired). */
|
|
91
|
+
parseStageLabels: string[]
|
|
92
|
+
/** Bundle-load progress (before `ready`). */
|
|
93
|
+
loading?: PipelineLoadingState | null
|
|
94
|
+
/** Error surfaced by the bundle load, distinct from a per-parse error. */
|
|
95
|
+
errorMessage?: string | null
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Optional host-injected panels. Each is a function of the current parse result returning already-rendered content (the
|
|
100
|
+
* docs site's SpanHighlight, TreeView, TimingPanel, …). Kept as `ReactNode` thunks so this package needs neither those
|
|
101
|
+
* components nor their heavy data types.
|
|
102
|
+
*/
|
|
103
|
+
export interface PipelinePanels {
|
|
104
|
+
/** Rendered above the form (e.g. the docs "About this demo"). */
|
|
105
|
+
header?: ReactNode
|
|
106
|
+
/** Rendered below everything (e.g. a guided tour). */
|
|
107
|
+
footer?: ReactNode
|
|
108
|
+
/** Model-version selector, wired to the host's bundle state. */
|
|
109
|
+
versionControl?: ReactNode
|
|
110
|
+
/** Backend indicator + WASM toggle. */
|
|
111
|
+
backendControl?: ReactNode
|
|
112
|
+
/** One-line release blurb. */
|
|
113
|
+
releaseInfo?: ReactNode
|
|
114
|
+
/** Heavy visualizers (span highlight, tree, timing, BIO, …), rendered from the result. */
|
|
115
|
+
extras?: (result: ParseResult) => ReactNode
|
|
116
|
+
/** Rendered in place of the resolved-place panel when nothing resolved (host's FailureDiagnostic). */
|
|
117
|
+
failure?: (result: ParseResult) => ReactNode
|
|
118
|
+
}
|