@semiont/react-ui 0.5.26 → 0.5.28
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 +28 -4
- package/dist/{PdfAnnotationCanvas.client-6ZSF6ZTJ.js → PdfAnnotationCanvas.client-C2IGG4SW.js} +33 -6
- package/dist/{PdfAnnotationCanvas.client-JCJMXWWJ.css → PdfAnnotationCanvas.client-TGDPPGXJ.css} +6 -1
- package/dist/{ar-YTUUGK2O.js → ar-FABPTJVB.js} +88 -26
- package/dist/{bn-ASGNGPMD.js → bn-LKP6BDMH.js} +88 -26
- package/dist/{chunk-L3WKRWP3.js → chunk-2GOHTTHW.js} +88 -26
- package/dist/{chunk-OBQOZCJE.js → chunk-2YHEE63S.js} +31 -31
- package/dist/{cs-WMYVAOZY.js → cs-YQYNWHRG.js} +88 -26
- package/dist/{da-FSHHN4D6.js → da-QHZOYKRY.js} +88 -26
- package/dist/{de-FJFO3TPT.js → de-GKA7RD7J.js} +88 -26
- package/dist/{el-XOH4QIAG.js → el-45T257AL.js} +88 -26
- package/dist/{en-3FCBW3YD.js → en-RVW5EQ66.js} +4 -2
- package/dist/{es-HDX4QI7E.js → es-AVER7RU4.js} +88 -26
- package/dist/{fa-DJ6WTFTJ.js → fa-K3VPK3O2.js} +88 -26
- package/dist/{fi-4S7H7W7U.js → fi-5NEKF7EB.js} +88 -26
- package/dist/{fr-6UJSYHXC.js → fr-FUMNDLBZ.js} +88 -26
- package/dist/{he-ZZJOHJX2.js → he-3JGNA3PR.js} +88 -26
- package/dist/{hi-HZJTAW7U.js → hi-IJY375DF.js} +88 -26
- package/dist/{id-E3I6ILEM.js → id-LWHKJ7ZD.js} +88 -26
- package/dist/index.css +380 -524
- package/dist/index.d.ts +318 -192
- package/dist/index.js +2960 -2421
- package/dist/integrations/css-modules-helper.js +1 -3
- package/dist/{it-ULAPAD7U.js → it-EBVOV4RW.js} +88 -26
- package/dist/{ja-SAHZQ4CZ.js → ja-KWRIX2NF.js} +88 -26
- package/dist/{ko-DFU2ADQ4.js → ko-L5CJMFDS.js} +88 -26
- package/dist/{ms-NWL76XNT.js → ms-LSNMJDR2.js} +88 -26
- package/dist/{nl-ATCM76WN.js → nl-ZB32MB7U.js} +88 -26
- package/dist/{no-UIAQ5RJF.js → no-4GTNY7M5.js} +88 -26
- package/dist/{pl-NOECQDXD.js → pl-LUHBLTHB.js} +88 -26
- package/dist/{pt-SVWIIUPC.js → pt-C4H3UNQ6.js} +88 -26
- package/dist/{ro-NR2DRTZG.js → ro-GGITS5V4.js} +88 -26
- package/dist/{sv-STVJ37JP.js → sv-T56RTLBP.js} +88 -26
- package/dist/test-utils.css +1 -1
- package/dist/test-utils.d.ts +1 -1
- package/dist/test-utils.js +2 -2
- package/dist/{th-UWQR4K54.js → th-22X2IWCB.js} +88 -26
- package/dist/{tr-WNXZSD27.js → tr-77TFG62H.js} +88 -26
- package/dist/{uk-SJRMB47Y.js → uk-MIIL6NXT.js} +88 -26
- package/dist/{vi-TMRQFES2.js → vi-7JYOIGNV.js} +88 -26
- package/dist/{zh-QOL3LYEM.js → zh-ID6DRYU4.js} +88 -26
- package/package.json +11 -10
- package/src/components/AssistProgress.tsx +178 -134
- package/src/components/CodeMirrorRenderer.tsx +8 -8
- package/src/components/{EntityFoundLog.tsx → ItemFoundLog.tsx} +12 -7
- package/src/components/ProtectedErrorBoundary.css +3 -1
- package/src/components/StatusDisplay.css +1 -1
- package/src/components/Toast.css +3 -1
- package/src/components/__tests__/AssistProgress.test.tsx +268 -141
- package/src/components/annotation/annotation-entries.css +0 -4
- package/src/components/annotation/references.css +0 -4
- package/src/components/image-annotation/AnnotationOverlay.tsx +6 -6
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +1 -1
- package/src/components/image-annotation/__tests__/AnnotationOverlay.click.test.tsx +7 -9
- package/src/components/image-annotation/__tests__/SvgDrawingCanvas.click.test.tsx +1 -2
- package/src/components/layout/Header.css +0 -4
- package/src/components/layout/LeftSidebar.css +0 -4
- package/src/components/modals/ComposeStep.tsx +218 -0
- package/src/components/modals/ConfigureGatherStep.tsx +10 -18
- package/src/components/modals/ConfigureGenerationStep.tsx +131 -54
- package/src/components/modals/ConfigureSearchStep.tsx +42 -40
- package/src/components/modals/ContextSummary.tsx +185 -72
- package/src/components/modals/CorpusPane.tsx +75 -0
- package/src/components/modals/DiscardPrompt.tsx +38 -0
- package/src/components/modals/GatherContextStep.tsx +237 -107
- package/src/components/modals/ReferenceWizardModal.tsx +287 -60
- package/src/components/modals/ResourceGenerateModal.tsx +233 -109
- package/src/components/modals/SearchModal.css +335 -111
- package/src/components/modals/SearchModal.tsx +1 -1
- package/src/components/modals/SearchResultsStep.tsx +63 -88
- package/src/components/modals/WizardFooter.tsx +69 -0
- package/src/components/modals/__tests__/ComposeStep.test.tsx +152 -0
- package/src/components/modals/__tests__/ConfigureGatherStep.test.tsx +14 -26
- package/src/components/modals/__tests__/ConfigureGenerationStep.test.tsx +290 -0
- package/src/components/modals/__tests__/ConfigureSearchStep.test.tsx +95 -0
- package/src/components/modals/__tests__/GatherContextStep.resource.test.tsx +463 -25
- package/src/components/modals/__tests__/ReferenceWizardModal.test.tsx +635 -0
- package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +277 -85
- package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +6 -2
- package/src/components/modals/__tests__/SearchResultsStep.test.tsx +61 -0
- package/src/components/modals/__tests__/WizardFooter.test.tsx +344 -0
- package/src/components/modals/modals.css +16 -42
- package/src/components/navigation/NavigationTabs.css +0 -20
- package/src/components/pdf-annotation/PdfAnnotationCanvas.css +21 -2
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +36 -3
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +41 -6
- package/src/components/resource/AnnotateView.tsx +19 -8
- package/src/components/resource/AnnotationHistory.tsx +2 -0
- package/src/components/resource/BrowseView.tsx +20 -9
- package/src/components/resource/ResourceViewer.tsx +27 -28
- package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +1 -1
- package/src/components/resource/__tests__/AnnotateView.focus.test.tsx +50 -0
- package/src/components/resource/__tests__/AnnotationHistory.test.tsx +2 -0
- package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +1 -1
- package/src/components/resource/__tests__/BrowseView.test.tsx +38 -12
- package/src/components/resource/__tests__/ResourceViewer.embeddable.test.tsx +47 -3
- package/src/components/resource/annotate-renderers.tsx +3 -3
- package/src/components/resource/event-formatting.ts +0 -3
- package/src/components/resource/panels/AssessmentEntry.tsx +1 -1
- package/src/components/resource/panels/AssessmentPanel.css +5 -6
- package/src/components/resource/panels/AssistSection.css +95 -263
- package/src/components/resource/panels/AssistSection.tsx +4 -4
- package/src/components/resource/panels/AssistShell.tsx +12 -4
- package/src/components/resource/panels/CollaborationPanel.css +47 -22
- package/src/components/resource/panels/CollaborationPanel.tsx +93 -2
- package/src/components/resource/panels/CommentEntry.tsx +1 -1
- package/src/components/resource/panels/CommentsPanel.css +0 -16
- package/src/components/resource/panels/HighlightEntry.tsx +1 -1
- package/src/components/resource/panels/HighlightPanel.css +0 -7
- package/src/components/resource/panels/JsonLdPanel.css +0 -4
- package/src/components/resource/panels/ReferenceEntry.tsx +10 -3
- package/src/components/resource/panels/ReferencesPanel.css +0 -12
- package/src/components/resource/panels/ReferencesPanel.tsx +16 -20
- package/src/components/resource/panels/ResourceInfoPanel.css +0 -32
- package/src/components/resource/panels/ResourceInfoPanel.tsx +89 -20
- package/src/components/resource/panels/StatisticsPanel.css +0 -20
- package/src/components/resource/panels/TagEntry.tsx +1 -1
- package/src/components/resource/panels/TaggingPanel.css +0 -4
- package/src/components/resource/panels/TaggingPanel.tsx +2 -10
- package/src/components/resource/panels/UnifiedAnnotationsPanel.css +0 -4
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +7 -2
- package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +0 -1
- package/src/components/resource/panels/__tests__/AssistSection.test.tsx +48 -50
- package/src/components/resource/panels/__tests__/AssistShell.test.tsx +11 -6
- package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +89 -0
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +1 -2
- package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +0 -1
- package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +44 -36
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +41 -1
- package/src/components/resource/panels/__tests__/ReferencesPanel.headless.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +67 -54
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +185 -7
- package/src/components/resource/panels/__tests__/TagEntry.test.tsx +0 -1
- package/src/components/settings/SettingsPanel.css +0 -4
- package/src/features/auth/auth.css +2 -26
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +0 -65
- package/src/features/resource-compose/components/ResourceComposePage.tsx +17 -130
- package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +1 -76
- package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -48
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +55 -0
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +29 -0
- package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +53 -3
- package/src/features/resource-discovery/state/discover-state-unit.ts +29 -8
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +71 -15
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +166 -71
- package/src/features/resource-viewer/hooks/__tests__/useResourceViewedReport.test.tsx +63 -0
- package/src/features/resource-viewer/hooks/useResourceViewedReport.ts +33 -0
- package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +2 -2
- package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +1 -1
- package/src/styles/core/buttons.css +3 -1
- package/src/styles/core/forms.css +35 -29
- package/src/styles/core/indicators.css +0 -4
- package/src/styles/core/inputs.css +0 -4
- package/src/styles/core/progress.css +2 -34
- package/src/styles/core/sliders.css +0 -4
- package/src/styles/core/toggles.css +0 -4
- package/src/styles/features/resource-discovery.css +13 -0
- package/src/styles/features/resource-viewer.css +0 -42
- package/src/styles/features/resource.css +0 -4
- package/src/styles/motivations/motivation-assessment.css +2 -2
- package/src/styles/panels/history-panel.css +0 -40
- package/src/styles/panels/user-panel.css +0 -3
- package/src/styles/patterns/panel-helpers.css +0 -27
- package/src/styles/patterns/panel-sections.css +0 -59
- package/src/styles/patterns/panels-base.css +0 -32
- package/translations/ar.json +84 -24
- package/translations/bn.json +84 -24
- package/translations/cs.json +84 -24
- package/translations/da.json +84 -24
- package/translations/de.json +84 -24
- package/translations/el.json +84 -24
- package/translations/en.json +84 -24
- package/translations/es.json +84 -24
- package/translations/fa.json +84 -24
- package/translations/fi.json +84 -24
- package/translations/fr.json +84 -24
- package/translations/he.json +84 -24
- package/translations/hi.json +84 -24
- package/translations/id.json +84 -24
- package/translations/it.json +84 -24
- package/translations/ja.json +84 -24
- package/translations/ko.json +84 -24
- package/translations/ms.json +84 -24
- package/translations/nl.json +84 -24
- package/translations/no.json +84 -24
- package/translations/pl.json +84 -24
- package/translations/pt.json +84 -24
- package/translations/ro.json +84 -24
- package/translations/sv.json +84 -24
- package/translations/th.json +84 -24
- package/translations/tr.json +84 -24
- package/translations/uk.json +84 -24
- package/translations/vi.json +84 -24
- package/translations/zh.json +84 -24
- package/dist/PdfAnnotationCanvas.client-6ZSF6ZTJ.js.map +0 -1
- package/dist/PdfAnnotationCanvas.client-JCJMXWWJ.css.map +0 -1
- package/dist/ar-YTUUGK2O.js.map +0 -1
- package/dist/bn-ASGNGPMD.js.map +0 -1
- package/dist/chunk-BTUG3VJJ.js.map +0 -1
- package/dist/chunk-L3WKRWP3.js.map +0 -1
- package/dist/chunk-O23TLLXW.js.map +0 -1
- package/dist/chunk-OBQOZCJE.js.map +0 -1
- package/dist/chunk-QT7LYM72.js.map +0 -1
- package/dist/chunk-VXASXU4K.js.map +0 -1
- package/dist/chunk-XUDKYAVC.js +0 -21
- package/dist/chunk-XUDKYAVC.js.map +0 -1
- package/dist/cs-WMYVAOZY.js.map +0 -1
- package/dist/da-FSHHN4D6.js.map +0 -1
- package/dist/de-FJFO3TPT.js.map +0 -1
- package/dist/el-XOH4QIAG.js.map +0 -1
- package/dist/en-3FCBW3YD.js.map +0 -1
- package/dist/es-HDX4QI7E.js.map +0 -1
- package/dist/fa-DJ6WTFTJ.js.map +0 -1
- package/dist/fi-4S7H7W7U.js.map +0 -1
- package/dist/fr-6UJSYHXC.js.map +0 -1
- package/dist/he-ZZJOHJX2.js.map +0 -1
- package/dist/hi-HZJTAW7U.js.map +0 -1
- package/dist/id-E3I6ILEM.js.map +0 -1
- package/dist/index.css.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/integrations/css-modules-helper.js.map +0 -1
- package/dist/integrations/styled-components-theme.js.map +0 -1
- package/dist/it-ULAPAD7U.js.map +0 -1
- package/dist/ja-SAHZQ4CZ.js.map +0 -1
- package/dist/ko-DFU2ADQ4.js.map +0 -1
- package/dist/ms-NWL76XNT.js.map +0 -1
- package/dist/nl-ATCM76WN.js.map +0 -1
- package/dist/no-UIAQ5RJF.js.map +0 -1
- package/dist/pl-NOECQDXD.js.map +0 -1
- package/dist/pt-SVWIIUPC.js.map +0 -1
- package/dist/ro-NR2DRTZG.js.map +0 -1
- package/dist/sv-STVJ37JP.js.map +0 -1
- package/dist/test-utils.css.map +0 -1
- package/dist/test-utils.js.map +0 -1
- package/dist/th-UWQR4K54.js.map +0 -1
- package/dist/tr-WNXZSD27.js.map +0 -1
- package/dist/uk-SJRMB47Y.js.map +0 -1
- package/dist/vi-TMRQFES2.js.map +0 -1
- package/dist/zh-QOL3LYEM.js.map +0 -1
- package/src/components/modals/ResourceSearchModal.tsx +0 -224
- package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +0 -159
- package/src/integrations/tailwind-plugin.cjs +0 -224
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import React
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { WizardFooter } from './WizardFooter';
|
|
4
5
|
|
|
5
6
|
export interface SearchConfig {
|
|
6
7
|
limit: number;
|
|
@@ -8,38 +9,55 @@ export interface SearchConfig {
|
|
|
8
9
|
}
|
|
9
10
|
|
|
10
11
|
export interface ConfigureSearchStepProps {
|
|
12
|
+
/**
|
|
13
|
+
* CONTROLLED (WIZARD-NAVIGATION D3). This used to be local `useState`, so stepping
|
|
14
|
+
* Back unmounted the component and silently discarded what the user had chosen —
|
|
15
|
+
* a Back button that costs you work is worse than no Back button, because it
|
|
16
|
+
* invites the press. The wizard owns it for the modal's lifetime instead.
|
|
17
|
+
*/
|
|
18
|
+
config: SearchConfig;
|
|
19
|
+
/** Echo of the gather step's hint — the thing being steered stays visible (GEP D8). */
|
|
20
|
+
hintEcho?: { label: string; value: string };
|
|
21
|
+
onConfigChange: (config: SearchConfig) => void;
|
|
11
22
|
isSearching?: boolean;
|
|
23
|
+
/** A settled failure (emit refused, matcher error, timeout) — the spinner
|
|
24
|
+
* must never outlive the request that fed it. */
|
|
25
|
+
searchError?: string | null;
|
|
12
26
|
onBack: () => void;
|
|
13
|
-
onCancel: () => void;
|
|
14
27
|
onSearch: (config: SearchConfig) => void;
|
|
15
28
|
translations: {
|
|
16
29
|
maxResults: string;
|
|
17
30
|
semanticScoring: string;
|
|
18
31
|
semanticScoringHelp: string;
|
|
19
|
-
cancel: string;
|
|
20
32
|
back: string;
|
|
21
33
|
search: string;
|
|
22
34
|
searching: string;
|
|
35
|
+
searchFailed: string;
|
|
23
36
|
};
|
|
24
37
|
}
|
|
25
38
|
|
|
26
39
|
export function ConfigureSearchStep({
|
|
40
|
+
config,
|
|
41
|
+
hintEcho,
|
|
42
|
+
onConfigChange,
|
|
27
43
|
isSearching = false,
|
|
44
|
+
searchError = null,
|
|
28
45
|
onBack,
|
|
29
|
-
onCancel,
|
|
30
46
|
onSearch,
|
|
31
47
|
translations: t,
|
|
32
48
|
}: ConfigureSearchStepProps) {
|
|
33
|
-
const [limit, setLimit] = useState(10);
|
|
34
|
-
const [useSemanticScoring, setUseSemanticScoring] = useState(true);
|
|
35
|
-
|
|
36
49
|
const handleSubmit = (e: React.FormEvent) => {
|
|
37
50
|
e.preventDefault();
|
|
38
|
-
onSearch(
|
|
51
|
+
onSearch(config);
|
|
39
52
|
};
|
|
40
53
|
|
|
41
54
|
return (
|
|
42
55
|
<form onSubmit={handleSubmit} className="semiont-form">
|
|
56
|
+
{hintEcho && (
|
|
57
|
+
<p className="semiont-wizard__hint-echo">
|
|
58
|
+
{hintEcho.label}: {hintEcho.value}
|
|
59
|
+
</p>
|
|
60
|
+
)}
|
|
43
61
|
{/* Max Results */}
|
|
44
62
|
<div className="semiont-form__field">
|
|
45
63
|
<label htmlFor="wizard-limit" className="semiont-form__label">
|
|
@@ -47,8 +65,8 @@ export function ConfigureSearchStep({
|
|
|
47
65
|
</label>
|
|
48
66
|
<select
|
|
49
67
|
id="wizard-limit"
|
|
50
|
-
value={limit}
|
|
51
|
-
onChange={(e) =>
|
|
68
|
+
value={config.limit}
|
|
69
|
+
onChange={(e) => onConfigChange({ ...config, limit: parseInt(e.target.value) })}
|
|
52
70
|
className="semiont-select"
|
|
53
71
|
>
|
|
54
72
|
<option value={1}>1</option>
|
|
@@ -60,11 +78,11 @@ export function ConfigureSearchStep({
|
|
|
60
78
|
|
|
61
79
|
{/* Semantic Scoring Toggle */}
|
|
62
80
|
<div className="semiont-form__field">
|
|
63
|
-
<label className="semiont-form__label"
|
|
81
|
+
<label className="semiont-form__label semiont-form__label--inline">
|
|
64
82
|
<input
|
|
65
83
|
type="checkbox"
|
|
66
|
-
checked={useSemanticScoring}
|
|
67
|
-
onChange={(e) =>
|
|
84
|
+
checked={config.useSemanticScoring}
|
|
85
|
+
onChange={(e) => onConfigChange({ ...config, useSemanticScoring: e.target.checked })}
|
|
68
86
|
/>
|
|
69
87
|
{t.semanticScoring}
|
|
70
88
|
</label>
|
|
@@ -73,33 +91,17 @@ export function ConfigureSearchStep({
|
|
|
73
91
|
</p>
|
|
74
92
|
</div>
|
|
75
93
|
|
|
76
|
-
{
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
type="button"
|
|
88
|
-
onClick={onBack}
|
|
89
|
-
className="semiont-button--secondary semiont-button--flex"
|
|
90
|
-
disabled={isSearching}
|
|
91
|
-
>
|
|
92
|
-
◀ {t.back}
|
|
93
|
-
</button>
|
|
94
|
-
<button
|
|
95
|
-
type="submit"
|
|
96
|
-
className="semiont-button--primary semiont-button--flex"
|
|
97
|
-
disabled={isSearching}
|
|
98
|
-
data-generating={isSearching ? 'true' : 'false'}
|
|
99
|
-
>
|
|
100
|
-
{isSearching ? `✨ ${t.searching}` : `🔍 ${t.search}`}
|
|
101
|
-
</button>
|
|
102
|
-
</div>
|
|
94
|
+
{searchError && (
|
|
95
|
+
<div role="alert" style={{ textAlign: 'center', padding: '0.5rem 0', color: 'var(--semiont-color-red-600)' }}>
|
|
96
|
+
{t.searchFailed}: {searchError}
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
|
|
100
|
+
<WizardFooter
|
|
101
|
+
backLabel={t.back}
|
|
102
|
+
onBack={onBack}
|
|
103
|
+
primary={{ label: t.search, pendingLabel: t.searching, pending: isSearching, type: 'submit' }}
|
|
104
|
+
/>
|
|
103
105
|
</form>
|
|
104
106
|
);
|
|
105
107
|
}
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import { deriveViews, type GatheredContext } from '@semiont/core';
|
|
3
|
+
import { deriveViews, getBodySource, getExactText, getTargetSelector, type GatheredContext } from '@semiont/core';
|
|
4
4
|
|
|
5
5
|
export interface ContextSummaryTranslations {
|
|
6
6
|
sourceContextLabel: string;
|
|
7
7
|
connectionsLabel: string;
|
|
8
8
|
citedByLabel: string;
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
/** GEP P2: the graph pane's chrome travels with this component (D10). */
|
|
10
|
+
graphPaneTitle: string;
|
|
11
|
+
/** Strategy-relevant empty state — emptiness is evidence (GEP D1). */
|
|
12
|
+
graphEmpty: string;
|
|
13
|
+
/** Resource-level linking annotation whose target the graph can't name. */
|
|
14
|
+
resourceLinkLabel: string;
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
export interface ContextSummaryProps {
|
|
@@ -18,85 +22,194 @@ export interface ContextSummaryProps {
|
|
|
18
22
|
export function ContextSummary({ context, translations: t }: ContextSummaryProps) {
|
|
19
23
|
// Graph views are focus-agnostic — only the focal resource id (and an optional
|
|
20
24
|
// focal annotation id) differ by kind.
|
|
25
|
+
// ResourceDescriptor's identifier is `@id` (JSON-LD) — its open index
|
|
26
|
+
// signature makes a bare `.id` deref compile and silently yield undefined.
|
|
21
27
|
const mainResourceId = context.focus.kind === 'annotation'
|
|
22
|
-
?
|
|
23
|
-
:
|
|
28
|
+
? context.focus.sourceResource['@id']
|
|
29
|
+
: context.focus.resource['@id'];
|
|
24
30
|
const focalAnnotationId = context.focus.kind === 'annotation'
|
|
25
31
|
? context.focus.annotation.id
|
|
26
32
|
: undefined;
|
|
27
|
-
const { connections, citedBy
|
|
33
|
+
const { connections, citedBy } = deriveViews(
|
|
28
34
|
context.graph,
|
|
29
35
|
mainResourceId,
|
|
30
36
|
focalAnnotationId,
|
|
31
37
|
);
|
|
32
38
|
|
|
39
|
+
// Sibling NODES — the same structural annotation-of walk core uses for its
|
|
40
|
+
// entity-type set, collected at node granularity because the viz draws them
|
|
41
|
+
// (GEP P4; core's `siblingEntityTypes` stays the prompt-side view).
|
|
42
|
+
const annotationOf = new Map<string, string>();
|
|
43
|
+
for (const edge of context.graph.edges) {
|
|
44
|
+
if (edge.type === 'annotation-of') annotationOf.set(edge.source, edge.target);
|
|
45
|
+
}
|
|
46
|
+
const siblings = context.graph.nodes.filter(
|
|
47
|
+
(n) => n.type === 'annotation' && n.id !== focalAnnotationId && annotationOf.get(n.id) === mainResourceId,
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
// Rarity-ranked entity types for hover-text — the IDF semantics
|
|
51
|
+
// `entityTypeFrequencies` was stored for (D3).
|
|
52
|
+
const freq: Record<string, number> =
|
|
53
|
+
(context.metadata?.entityTypeFrequencies as Record<string, number> | undefined) ?? {};
|
|
54
|
+
const rank = (types: string[]) => [...types].sort((a, b) => (freq[a] ?? 0) - (freq[b] ?? 0));
|
|
55
|
+
|
|
56
|
+
const firstBodyValue = (body: unknown): string | undefined => {
|
|
57
|
+
// Tagging-purpose bodies are the entity-type tag — already shown via
|
|
58
|
+
// entityTypes, so skip them rather than repeat the type in the hover.
|
|
59
|
+
for (const item of Array.isArray(body) ? body : [body]) {
|
|
60
|
+
if (item && typeof item === 'object' && 'value' in item
|
|
61
|
+
&& (item as { purpose?: string }).purpose !== 'tagging') {
|
|
62
|
+
return (item as { value?: string }).value;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return undefined;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// ── Deterministic layered layout (D3): citers | focal + siblings | peers ──
|
|
69
|
+
// Positions derive from array order alone — no physics, no randomness.
|
|
70
|
+
// Geometry adapts to what's populated (no-clipping principle): an absent
|
|
71
|
+
// citer/peer layer frees its column for the sibling band to wrap into, and
|
|
72
|
+
// the viewBox shrinks to the columns actually used. Everything always draws
|
|
73
|
+
// — overflow rides the wizard pane's scroll, never a cap.
|
|
74
|
+
const NODE_W = 180;
|
|
75
|
+
const NODE_H = 34;
|
|
76
|
+
const ROW = 52;
|
|
77
|
+
const TOP = 8;
|
|
78
|
+
const COL_PITCH = 216;
|
|
79
|
+
const colX = (col: number) => 8 + col * COL_PITCH;
|
|
80
|
+
|
|
81
|
+
type DrawnNode = {
|
|
82
|
+
id: string; label: string; kind: 'focal' | 'resource' | 'annotation';
|
|
83
|
+
hover: string; x: number; y: number;
|
|
84
|
+
};
|
|
85
|
+
type DrawnEdge = { key: string; kind: 'cites' | 'peer' | 'annotation-of'; hover: string;
|
|
86
|
+
x1: number; y1: number; x2: number; y2: number };
|
|
87
|
+
|
|
88
|
+
const focalNode = context.graph.nodes.find((n) => n.id === mainResourceId);
|
|
89
|
+
const focalLabel = focalNode?.label
|
|
90
|
+
?? (context.focus.kind === 'annotation' ? context.focus.sourceResource.name : context.focus.resource.name)
|
|
91
|
+
?? mainResourceId;
|
|
92
|
+
|
|
93
|
+
const drawn: DrawnNode[] = [];
|
|
94
|
+
const drawnEdges: DrawnEdge[] = [];
|
|
95
|
+
|
|
96
|
+
// The sibling band wraps across the columns absent layers free (3 total).
|
|
97
|
+
const hasCiters = citedBy.length > 0;
|
|
98
|
+
const hasPeers = connections.length > 0;
|
|
99
|
+
const bandCols = Math.max(1, Math.min(
|
|
100
|
+
3 - (hasCiters ? 1 : 0) - (hasPeers ? 1 : 0),
|
|
101
|
+
siblings.length,
|
|
102
|
+
));
|
|
103
|
+
const bandCol0 = hasCiters ? 1 : 0;
|
|
104
|
+
const numCols = (hasCiters ? 1 : 0) + bandCols + (hasPeers ? 1 : 0);
|
|
105
|
+
|
|
106
|
+
const focal: DrawnNode = { id: mainResourceId, label: focalLabel, kind: 'focal', hover: focalLabel, x: colX(bandCol0), y: TOP };
|
|
107
|
+
drawn.push(focal);
|
|
108
|
+
|
|
109
|
+
citedBy.forEach((c, i) => {
|
|
110
|
+
const node: DrawnNode = { id: c.resourceId, label: c.resourceName, kind: 'resource',
|
|
111
|
+
hover: `${c.resourceName}\n${t.citedByLabel}`, x: colX(0), y: TOP + i * ROW };
|
|
112
|
+
drawn.push(node);
|
|
113
|
+
drawnEdges.push({ key: `cites-${c.resourceId}`, kind: 'cites', hover: 'cites',
|
|
114
|
+
x1: node.x + NODE_W, y1: node.y + NODE_H / 2, x2: focal.x, y2: focal.y + NODE_H / 2 });
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
connections.forEach((c, i) => {
|
|
118
|
+
const types = rank(c.entityTypes);
|
|
119
|
+
const node: DrawnNode = { id: c.resourceId, label: c.resourceName, kind: 'resource',
|
|
120
|
+
hover: types.length ? `${c.resourceName}\n${types.join(', ')}` : c.resourceName,
|
|
121
|
+
x: colX(bandCol0 + bandCols), y: TOP + i * ROW };
|
|
122
|
+
drawn.push(node);
|
|
123
|
+
// With a multi-column band the line crosses the band's row 0, which only
|
|
124
|
+
// ever holds the focal node in the band's FIRST column — so it stays clear.
|
|
125
|
+
drawnEdges.push({ key: `peer-${c.resourceId}`, kind: 'peer',
|
|
126
|
+
hover: c.bidirectional ? `${t.connectionsLabel} · mutual` : t.connectionsLabel,
|
|
127
|
+
x1: focal.x + NODE_W, y1: focal.y + NODE_H / 2, x2: node.x, y2: node.y + NODE_H / 2 });
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
siblings.forEach((s, i) => {
|
|
131
|
+
// Discriminated: type === 'annotation' guarantees the embedded W3C
|
|
132
|
+
// annotation (P3/D11). The node's identity is the text it wraps — a
|
|
133
|
+
// column of motivation labels is uninterpretable; motivation shows as
|
|
134
|
+
// the node's styling and hover instead.
|
|
135
|
+
const ann = s.type === 'annotation' ? s.annotation : undefined;
|
|
136
|
+
const quote = ann ? getExactText(getTargetSelector(ann.target)) : '';
|
|
137
|
+
const motivation = ann?.motivation ?? s.label;
|
|
138
|
+
const body = ann ? firstBodyValue(ann.body) : undefined;
|
|
139
|
+
const types = rank(s.entityTypes ?? []);
|
|
140
|
+
// A resource-level annotation has no selector — nothing to quote — but is
|
|
141
|
+
// ABOUT a target resource: label it by what it links to. Raw motivation
|
|
142
|
+
// ("linking") is internal vocabulary and stays hover-only.
|
|
143
|
+
const linkTarget = !quote && ann ? getBodySource(ann.body) : null;
|
|
144
|
+
const linkedName = linkTarget
|
|
145
|
+
? context.graph.nodes.find((n) => n.id === linkTarget)?.label
|
|
146
|
+
: undefined;
|
|
147
|
+
const label = quote
|
|
148
|
+
|| (linkedName ? `→ ${linkedName}` : undefined)
|
|
149
|
+
|| (linkTarget ? t.resourceLinkLabel : undefined)
|
|
150
|
+
|| body
|
|
151
|
+
|| s.label;
|
|
152
|
+
// Hover leads with the full label — the drawn one truncates (like the
|
|
153
|
+
// resource nodes, whose hover carries the full name). Deduped: a label
|
|
154
|
+
// that fell through to the body or motivation must not repeat below it.
|
|
155
|
+
const hover = Array.from(new Set(
|
|
156
|
+
[label, motivation, body, types.length ? types.join(', ') : undefined].filter(Boolean),
|
|
157
|
+
)).join('\n');
|
|
158
|
+
const node: DrawnNode = { id: s.id, label, kind: 'annotation',
|
|
159
|
+
hover, x: colX(bandCol0 + (i % bandCols)), y: TOP + (1 + Math.floor(i / bandCols)) * ROW };
|
|
160
|
+
drawn.push(node);
|
|
161
|
+
drawnEdges.push({ key: `annof-${s.id}`, kind: 'annotation-of', hover: 'annotation-of',
|
|
162
|
+
x1: focal.x + NODE_W / 2, y1: focal.y + NODE_H, x2: node.x + NODE_W / 2, y2: node.y });
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
const rows = Math.max(citedBy.length, 1 + Math.ceil(siblings.length / bandCols), connections.length);
|
|
166
|
+
const width = numCols * COL_PITCH - 20;
|
|
167
|
+
const height = TOP + rows * ROW + 8;
|
|
168
|
+
const truncate = (s: string) => (s.length > 22 ? `${s.slice(0, 21)}…` : s);
|
|
169
|
+
const hasBody = connections.length > 0 || citedBy.length > 0 || siblings.length > 0;
|
|
170
|
+
|
|
171
|
+
// GEP P4: this component IS the graph pane, and its body IS a graph — a
|
|
172
|
+
// hand-rolled deterministic SVG (D3: no physics, no library, no pan-zoom;
|
|
173
|
+
// a decision surface, not an explorer). Chrome travels with the component,
|
|
174
|
+
// so every consumer inherits it (D10). Emptiness is evidence (D1).
|
|
33
175
|
return (
|
|
34
|
-
|
|
35
|
-
{
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
</ul>
|
|
71
|
-
</div>
|
|
72
|
-
)}
|
|
73
|
-
|
|
74
|
-
{citedByCount > 0 && (
|
|
75
|
-
<div>
|
|
76
|
-
<div style={{
|
|
77
|
-
fontSize: 'var(--semiont-text-xs)',
|
|
78
|
-
fontWeight: 700,
|
|
79
|
-
textTransform: 'uppercase',
|
|
80
|
-
letterSpacing: '0.06em',
|
|
81
|
-
color: 'var(--semiont-text-tertiary)',
|
|
82
|
-
marginBottom: '0.375rem',
|
|
83
|
-
}}>
|
|
84
|
-
{t.citedByLabel} ({citedByCount})
|
|
85
|
-
</div>
|
|
86
|
-
{citedBy.length > 0 && (
|
|
87
|
-
<ul style={{ display: 'flex', flexDirection: 'column', gap: '0.25rem', listStyle: 'none', padding: 0, margin: 0 }}>
|
|
88
|
-
{citedBy.map(ref => (
|
|
89
|
-
<li key={ref.resourceId} style={{ fontSize: 'var(--semiont-text-sm)', color: 'var(--semiont-text-primary)', fontWeight: 500 }}>
|
|
90
|
-
{ref.resourceName}
|
|
91
|
-
</li>
|
|
92
|
-
))}
|
|
93
|
-
</ul>
|
|
94
|
-
)}
|
|
95
|
-
</div>
|
|
96
|
-
)}
|
|
97
|
-
|
|
98
|
-
</div>
|
|
176
|
+
<div className="semiont-gather-pane semiont-gather-pane--graph">
|
|
177
|
+
<div className="semiont-gather-pane__title">{t.graphPaneTitle}</div>
|
|
178
|
+
{context.inferredRelationshipSummary && (
|
|
179
|
+
<p className="semiont-gather-pane__summary">{context.inferredRelationshipSummary}</p>
|
|
180
|
+
)}
|
|
181
|
+
{!hasBody && (
|
|
182
|
+
<p className="semiont-gather-pane__empty">{t.graphEmpty}</p>
|
|
183
|
+
)}
|
|
184
|
+
{hasBody && (
|
|
185
|
+
<svg
|
|
186
|
+
className="semiont-graph"
|
|
187
|
+
viewBox={`0 0 ${width} ${height}`}
|
|
188
|
+
// Natural-size cap: 1 viewBox unit never exceeds 1px, so a compact
|
|
189
|
+
// graph renders small instead of stretching to fill the pane.
|
|
190
|
+
style={{ maxWidth: `${width}px` }}
|
|
191
|
+
role="img"
|
|
192
|
+
aria-label={t.graphPaneTitle}
|
|
193
|
+
>
|
|
194
|
+
{drawnEdges.map((e) => (
|
|
195
|
+
<g key={e.key} className={`semiont-graph__edge semiont-graph__edge--${e.kind}`}>
|
|
196
|
+
<line x1={e.x1} y1={e.y1} x2={e.x2} y2={e.y2} />
|
|
197
|
+
<title>{e.hover}</title>
|
|
198
|
+
</g>
|
|
199
|
+
))}
|
|
200
|
+
{drawn.map((n) => (
|
|
201
|
+
<g
|
|
202
|
+
key={n.id}
|
|
203
|
+
className={`semiont-graph__node semiont-graph__node--${n.kind}`}
|
|
204
|
+
data-node-id={n.id}
|
|
205
|
+
>
|
|
206
|
+
<rect x={n.x} y={n.y} width={NODE_W} height={NODE_H} rx={6} />
|
|
207
|
+
<text x={n.x + 10} y={n.y + 22}>{truncate(n.label)}</text>
|
|
208
|
+
<title>{n.hover}</title>
|
|
209
|
+
</g>
|
|
210
|
+
))}
|
|
211
|
+
</svg>
|
|
99
212
|
)}
|
|
100
|
-
|
|
213
|
+
</div>
|
|
101
214
|
);
|
|
102
215
|
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import type { GatheredContext } from '@semiont/core';
|
|
4
|
+
|
|
5
|
+
export interface CorpusPaneTranslations {
|
|
6
|
+
corpusPaneTitle: string;
|
|
7
|
+
/** Strategy-relevant empty state — emptiness is evidence (GEP D1). */
|
|
8
|
+
corpusEmpty: string;
|
|
9
|
+
/** Label for the match's cosine score. */
|
|
10
|
+
score: string;
|
|
11
|
+
/** The filter receipt; interpolates `{{types}}`. */
|
|
12
|
+
excludedReceipt: string;
|
|
13
|
+
/** The trust marker for machine-read (OCR) sources. */
|
|
14
|
+
machineRead: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface CorpusPaneProps {
|
|
18
|
+
semanticContext: GatheredContext['semanticContext'];
|
|
19
|
+
translations: CorpusPaneTranslations;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The corpus pane (GEP D4): latent knowledge — ranked `SemanticMatch` cards,
|
|
24
|
+
* read-only. THE datum for Search-vs-Generate ("does the KB already discuss
|
|
25
|
+
* this?"). This is recall-at-gather evidence, not a preview of Search: the
|
|
26
|
+
* matcher re-runs fresh (selected text + hint, scoring) and may rank
|
|
27
|
+
* differently.
|
|
28
|
+
*
|
|
29
|
+
* Read-only for now by decision, not forever — when per-match actions arrive
|
|
30
|
+
* they route through the footer's fork or a designed extension of it, never
|
|
31
|
+
* ad-hoc buttons here.
|
|
32
|
+
*/
|
|
33
|
+
export function CorpusPane({ semanticContext, translations: t }: CorpusPaneProps) {
|
|
34
|
+
// Descending by score, whatever order the wire delivered.
|
|
35
|
+
const matches = [...(semanticContext?.similar ?? [])].sort((a, b) => b.score - a.score);
|
|
36
|
+
const excluded = semanticContext?.excludedEntityTypes ?? [];
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="semiont-gather-pane semiont-gather-pane--corpus">
|
|
40
|
+
<div className="semiont-gather-pane__title">{t.corpusPaneTitle}</div>
|
|
41
|
+
{matches.length === 0 ? (
|
|
42
|
+
<p className="semiont-gather-pane__empty">{t.corpusEmpty}</p>
|
|
43
|
+
) : (
|
|
44
|
+
<ul className="semiont-corpus__list">
|
|
45
|
+
{matches.map((m, i) => (
|
|
46
|
+
<li key={`${m.resourceId}-${i}`} className="semiont-corpus__card">
|
|
47
|
+
<div className="semiont-corpus__card-head">
|
|
48
|
+
<span className="semiont-corpus__source">{m.resourceName}</span>
|
|
49
|
+
{(m.entityTypes ?? []).map((et) => (
|
|
50
|
+
<span key={et} className="semiont-chip" style={{ fontSize: 'var(--semiont-text-xs)', padding: '0.125rem 0.375rem', fontWeight: 400 }}>
|
|
51
|
+
{et}
|
|
52
|
+
</span>
|
|
53
|
+
))}
|
|
54
|
+
{m.machineRead && (
|
|
55
|
+
<span className="semiont-chip semiont-corpus__trust" style={{ fontSize: 'var(--semiont-text-xs)', padding: '0.125rem 0.375rem', fontWeight: 400 }}>
|
|
56
|
+
{t.machineRead}
|
|
57
|
+
</span>
|
|
58
|
+
)}
|
|
59
|
+
{/* 0–1 cosine to two places — deliberately NOT the search step's
|
|
60
|
+
raw render, which is a different scale (logged deviation). */}
|
|
61
|
+
<span className="semiont-corpus__score">{t.score}: {m.score.toFixed(2)}</span>
|
|
62
|
+
</div>
|
|
63
|
+
<p className="semiont-corpus__snippet">{m.text}</p>
|
|
64
|
+
</li>
|
|
65
|
+
))}
|
|
66
|
+
</ul>
|
|
67
|
+
)}
|
|
68
|
+
{excluded.length > 0 && (
|
|
69
|
+
<p className="semiont-corpus__receipt">
|
|
70
|
+
{t.excludedReceipt.replace('{{types}}', excluded.join(', '))}
|
|
71
|
+
</p>
|
|
72
|
+
)}
|
|
73
|
+
</div>
|
|
74
|
+
);
|
|
75
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The inline discard band (COMPOSE-IN-MODAL D4, shared by GATHER-AT-THE-TOP
|
|
5
|
+
* P1): a modal dies on ✕/Escape/backdrop, but typed work must not die with
|
|
6
|
+
* it. Hosts route ALL dismissal through one handler; when the draft is dirty
|
|
7
|
+
* that handler shows this band instead of closing. Success paths never come
|
|
8
|
+
* here — the guard protects dismissal, not completion.
|
|
9
|
+
*/
|
|
10
|
+
interface DiscardPromptProps {
|
|
11
|
+
prompt: string;
|
|
12
|
+
discardLabel: string;
|
|
13
|
+
keepLabel: string;
|
|
14
|
+
onDiscard: () => void;
|
|
15
|
+
onKeepEditing: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function DiscardPrompt({ prompt, discardLabel, keepLabel, onDiscard, onKeepEditing }: DiscardPromptProps) {
|
|
19
|
+
return (
|
|
20
|
+
<div className="semiont-wizard__discard-prompt" role="alert">
|
|
21
|
+
<span className="semiont-wizard__discard-prompt-text">{prompt}</span>
|
|
22
|
+
<button
|
|
23
|
+
type="button"
|
|
24
|
+
className="semiont-button--danger"
|
|
25
|
+
onClick={onDiscard}
|
|
26
|
+
>
|
|
27
|
+
{discardLabel}
|
|
28
|
+
</button>
|
|
29
|
+
<button
|
|
30
|
+
type="button"
|
|
31
|
+
className="semiont-button--secondary"
|
|
32
|
+
onClick={onKeepEditing}
|
|
33
|
+
>
|
|
34
|
+
{keepLabel}
|
|
35
|
+
</button>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
}
|