@semiont/react-ui 0.5.27 → 0.5.29
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 +3 -3
- package/dist/{PdfAnnotationCanvas.client-EVUPTUWZ.js → PdfAnnotationCanvas.client-LSH3PZPY.js} +32 -5
- package/dist/{ar-OU3DQGC6.js → ar-AZBRADEB.js} +43 -23
- package/dist/{bn-PHWCR6AZ.js → bn-CCEW6AOS.js} +43 -23
- package/dist/{chunk-VXASXU4K.js → chunk-3ZBKCWUZ.js} +1 -1
- package/dist/{chunk-5VDTWRXJ.js → chunk-G4VHL4UA.js} +31 -31
- package/dist/{chunk-PCRRBU7H.js → chunk-SDKFZLEX.js} +43 -23
- package/dist/{cs-NEXYZ62I.js → cs-HXPF2CTE.js} +43 -23
- package/dist/{da-FAT43YCU.js → da-K5NTK7LH.js} +43 -23
- package/dist/{de-KRQ3WBCU.js → de-GMRQEDZI.js} +43 -23
- package/dist/{el-Q4IU3EKC.js → el-EUB4CI4G.js} +43 -23
- package/dist/{en-2KADE7N4.js → en-R5XA26NF.js} +2 -2
- package/dist/{es-2IO3R2MK.js → es-Z32DAZ6W.js} +43 -23
- package/dist/{fa-Q5GOQFXX.js → fa-G7HN6B32.js} +43 -23
- package/dist/{fi-R6NMRWBE.js → fi-3YBLZO7I.js} +43 -23
- package/dist/{fr-QY4D5E6J.js → fr-VUBH7BBS.js} +43 -23
- package/dist/{he-X6Y77XHY.js → he-WQQEY26B.js} +43 -23
- package/dist/{hi-XL7IOEQM.js → hi-HJZ6W4JB.js} +43 -23
- package/dist/{id-QZ4ZZS67.js → id-ZOGZ37MI.js} +43 -23
- package/dist/index.css +279 -74
- package/dist/index.d.ts +318 -393
- package/dist/index.js +2619 -2687
- package/dist/{it-ME2Q7OB6.js → it-DT6UW66F.js} +43 -23
- package/dist/{ja-HX7QASRI.js → ja-RUKQCOLO.js} +43 -23
- package/dist/{ko-EQO5AHAV.js → ko-ZH5ADKZM.js} +43 -23
- package/dist/{ms-UILFSF3I.js → ms-Y2AFPZTU.js} +43 -23
- package/dist/{nl-NNI45PMK.js → nl-OQEWIXNA.js} +43 -23
- package/dist/{no-MAL5F6P6.js → no-KPAYJZWY.js} +43 -23
- package/dist/{pl-4RMY6JAJ.js → pl-WSXK2UUD.js} +43 -23
- package/dist/{pt-VQH2X3QQ.js → pt-NWRJRUCK.js} +43 -23
- package/dist/{ro-XXCQO2OG.js → ro-3CGVMOC6.js} +43 -23
- package/dist/{sv-7Z6EJ6U4.js → sv-HGSEBA7S.js} +43 -23
- package/dist/test-utils.js +16 -7
- package/dist/{th-L3DXXYVQ.js → th-NZ4G352Z.js} +43 -23
- package/dist/{tr-P3DFVLFJ.js → tr-LGGS4DLW.js} +43 -23
- package/dist/{uk-7C5D3PXS.js → uk-AIX536L4.js} +43 -23
- package/dist/{vi-ZLTLEO7P.js → vi-V6CVQZLQ.js} +43 -23
- package/dist/{zh-5FHVWDIN.js → zh-AGKPGI3L.js} +43 -23
- package/package.json +16 -15
- package/src/components/AssistProgress.tsx +36 -2
- package/src/components/CodeMirrorRenderer.tsx +8 -8
- package/src/components/StatusDisplay.tsx +13 -13
- package/src/components/__tests__/AssistProgress.test.tsx +59 -0
- package/src/components/__tests__/StatusDisplay.test.tsx +9 -9
- package/src/components/modals/ComposeStep.tsx +233 -0
- package/src/components/modals/ConfigureGatherStep.tsx +10 -18
- package/src/components/modals/ConfigureGenerationStep.tsx +178 -34
- package/src/components/modals/ConfigureSearchStep.tsx +19 -0
- 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 +252 -59
- package/src/components/modals/ResourceGenerateModal.tsx +225 -112
- package/src/components/modals/SearchModal.css +337 -101
- package/src/components/modals/SearchResultsStep.tsx +61 -68
- package/src/components/modals/WizardFooter.tsx +7 -1
- 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 +206 -8
- package/src/components/modals/__tests__/ConfigureSearchStep.test.tsx +24 -1
- package/src/components/modals/__tests__/GatherContextStep.resource.test.tsx +463 -25
- package/src/components/modals/__tests__/ReferenceWizardModal.test.tsx +442 -39
- package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +300 -86
- package/src/components/modals/__tests__/SearchResultsStep.test.tsx +61 -0
- package/src/components/modals/__tests__/WizardFooter.test.tsx +116 -9
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +34 -1
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +38 -1
- package/src/components/resource/AnnotateView.tsx +3 -3
- package/src/components/resource/BrowseView.tsx +8 -8
- package/src/components/resource/DownloadFileLink.tsx +1 -1
- package/src/components/resource/ResourceViewer.tsx +4 -4
- package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +1 -1
- package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +1 -1
- package/src/components/resource/__tests__/BrowseView.reference-hover.test.tsx +1 -1
- package/src/components/resource/__tests__/BrowseView.test.tsx +11 -11
- package/src/components/resource/__tests__/download-link.test.tsx +6 -6
- package/src/components/resource/annotate-renderers.tsx +3 -3
- package/src/components/resource/panels/AssistSection.css +35 -0
- package/src/components/resource/panels/ReferenceEntry.tsx +11 -4
- package/src/components/resource/panels/ReferencesPanel.tsx +9 -1
- package/src/components/resource/panels/ResourceInfoPanel.tsx +89 -20
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +7 -2
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +41 -0
- package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +84 -3
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +149 -7
- package/src/features/auth/__tests__/SignInForm.a11y.test.tsx +12 -12
- package/src/features/auth/components/SignInForm.tsx +53 -53
- package/src/features/auth-welcome/state/__tests__/welcome-state-unit.test.ts +2 -2
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +0 -65
- package/src/features/resource-compose/components/ResourceComposePage.tsx +35 -133
- package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +1 -100
- package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -57
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +1 -1
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +2 -2
- package/src/features/resource-discovery/state/discover-state-unit.ts +1 -1
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +73 -17
- package/src/features/resource-viewer/__tests__/generation-options.test.ts +53 -0
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +148 -83
- package/src/features/resource-viewer/generation-options.ts +29 -0
- package/src/styles/core/forms.css +35 -8
- package/src/styles/features/resource-viewer.css +0 -22
- package/src/styles/features/resource.css +0 -4
- package/src/styles/index.css +0 -1
- package/translations/ar.json +42 -22
- package/translations/bn.json +42 -22
- package/translations/cs.json +42 -22
- package/translations/da.json +42 -22
- package/translations/de.json +42 -22
- package/translations/el.json +42 -22
- package/translations/en.json +42 -22
- package/translations/es.json +42 -22
- package/translations/fa.json +42 -22
- package/translations/fi.json +42 -22
- package/translations/fr.json +42 -22
- package/translations/he.json +42 -22
- package/translations/hi.json +42 -22
- package/translations/id.json +42 -22
- package/translations/it.json +42 -22
- package/translations/ja.json +42 -22
- package/translations/ko.json +42 -22
- package/translations/ms.json +42 -22
- package/translations/nl.json +42 -22
- package/translations/no.json +42 -22
- package/translations/pl.json +42 -22
- package/translations/pt.json +42 -22
- package/translations/ro.json +42 -22
- package/translations/sv.json +42 -22
- package/translations/th.json +42 -22
- package/translations/tr.json +42 -22
- package/translations/uk.json +42 -22
- package/translations/vi.json +42 -22
- package/translations/zh.json +42 -22
- package/dist/PdfAnnotationCanvas.client-EVUPTUWZ.js.map +0 -1
- package/dist/PdfAnnotationCanvas.client-TGDPPGXJ.css.map +0 -1
- package/dist/ar-OU3DQGC6.js.map +0 -1
- package/dist/bn-PHWCR6AZ.js.map +0 -1
- package/dist/chunk-5VDTWRXJ.js.map +0 -1
- package/dist/chunk-BTUG3VJJ.js.map +0 -1
- package/dist/chunk-O23TLLXW.js.map +0 -1
- package/dist/chunk-PCRRBU7H.js.map +0 -1
- package/dist/chunk-QT7LYM72.js.map +0 -1
- package/dist/chunk-VXASXU4K.js.map +0 -1
- package/dist/cs-NEXYZ62I.js.map +0 -1
- package/dist/da-FAT43YCU.js.map +0 -1
- package/dist/de-KRQ3WBCU.js.map +0 -1
- package/dist/el-Q4IU3EKC.js.map +0 -1
- package/dist/en-2KADE7N4.js.map +0 -1
- package/dist/es-2IO3R2MK.js.map +0 -1
- package/dist/fa-Q5GOQFXX.js.map +0 -1
- package/dist/fi-R6NMRWBE.js.map +0 -1
- package/dist/fr-QY4D5E6J.js.map +0 -1
- package/dist/he-X6Y77XHY.js.map +0 -1
- package/dist/hi-XL7IOEQM.js.map +0 -1
- package/dist/id-QZ4ZZS67.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-ME2Q7OB6.js.map +0 -1
- package/dist/ja-HX7QASRI.js.map +0 -1
- package/dist/ko-EQO5AHAV.js.map +0 -1
- package/dist/ms-UILFSF3I.js.map +0 -1
- package/dist/nl-NNI45PMK.js.map +0 -1
- package/dist/no-MAL5F6P6.js.map +0 -1
- package/dist/pl-4RMY6JAJ.js.map +0 -1
- package/dist/pt-VQH2X3QQ.js.map +0 -1
- package/dist/ro-XXCQO2OG.js.map +0 -1
- package/dist/sv-7Z6EJ6U4.js.map +0 -1
- package/dist/test-utils.css.map +0 -1
- package/dist/test-utils.js.map +0 -1
- package/dist/th-L3DXXYVQ.js.map +0 -1
- package/dist/tr-P3DFVLFJ.js.map +0 -1
- package/dist/uk-7C5D3PXS.js.map +0 -1
- package/dist/vi-ZLTLEO7P.js.map +0 -1
- package/dist/zh-5FHVWDIN.js.map +0 -1
- package/src/components/modals/ResourceSearchModal.tsx +0 -224
- package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +0 -163
- package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +0 -137
- package/src/features/admin-exchange/__tests__/ExportCard.test.tsx +0 -41
- package/src/features/admin-exchange/__tests__/ImportCard.test.tsx +0 -148
- package/src/features/admin-exchange/__tests__/ImportProgress.test.tsx +0 -117
- package/src/features/admin-exchange/components/AdminExchangePage.tsx +0 -117
- package/src/features/admin-exchange/components/ExportCard.tsx +0 -35
- package/src/features/admin-exchange/components/ImportCard.tsx +0 -183
- package/src/features/admin-exchange/components/ImportProgress.tsx +0 -80
- package/src/features/admin-exchange/state/__tests__/exchange-state-unit.test.ts +0 -185
- package/src/features/admin-exchange/state/exchange-state-unit.ts +0 -133
- package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +0 -118
- package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +0 -118
- package/src/styles/features/exchange.css +0 -374
|
@@ -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
|
+
}
|
|
@@ -4,43 +4,81 @@ import { useState, useEffect, useRef } from 'react';
|
|
|
4
4
|
import type { GatheredContext } from '@semiont/core';
|
|
5
5
|
import { ContextSummary } from './ContextSummary';
|
|
6
6
|
import type { ContextSummaryTranslations } from './ContextSummary';
|
|
7
|
+
import { CorpusPane } from './CorpusPane';
|
|
8
|
+
import type { CorpusPaneTranslations } from './CorpusPane';
|
|
9
|
+
import { ANNOTATORS, annotatorKeyForMotivation } from '../../lib/annotation-registry';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* The annotation-wizard resolution controls, as ONE optional group: a caller
|
|
13
|
+
* either serves the whole surface (hint + Bind/Generate/Compose, with their
|
|
14
|
+
* labels) or none of it. This is what keeps a display-only caller from having
|
|
15
|
+
* to pass `''` for keys it will never render (GENERATE-FROM-RESOURCE D2).
|
|
16
|
+
*/
|
|
17
|
+
export interface GatherContextStepAnnotate {
|
|
18
|
+
userHint: string;
|
|
19
|
+
onUserHintChange: (value: string) => void;
|
|
20
|
+
onBind: () => void;
|
|
21
|
+
onGenerate: () => void;
|
|
22
|
+
onCompose: () => void;
|
|
23
|
+
translations: {
|
|
24
|
+
search: string;
|
|
25
|
+
generate: string;
|
|
26
|
+
compose: string;
|
|
27
|
+
resolutionStrategyLabel: string;
|
|
28
|
+
userHintLabel: string;
|
|
29
|
+
/** The label states its effect — "steers Search and Generate" (GEP D5). */
|
|
30
|
+
userHintEffect: string;
|
|
31
|
+
userHintPlaceholder: string;
|
|
32
|
+
};
|
|
33
|
+
}
|
|
7
34
|
|
|
8
35
|
export interface GatherContextStepProps {
|
|
9
36
|
context: GatheredContext | null;
|
|
10
37
|
contextLoading: boolean;
|
|
11
38
|
contextError: Error | null;
|
|
12
|
-
/**
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
39
|
+
/**
|
|
40
|
+
* What the host already knows about the annotation BEFORE the gather
|
|
41
|
+
* answers — its exact text and entity types are wizard props, no wire
|
|
42
|
+
* round-trip needed. When present, the loading state renders them with
|
|
43
|
+
* skeleton panes so the loading screen is the loaded screen minus the
|
|
44
|
+
* data, not a blank modal with dots. Omit (the resource-gather path) for
|
|
45
|
+
* the plain loading block.
|
|
46
|
+
*/
|
|
47
|
+
pending?: { exact: string; entityTypes: string[]; resourceName?: string };
|
|
48
|
+
/** Omit for a display-only (e.g. resource-focus) render. */
|
|
49
|
+
annotate?: GatherContextStepAnnotate;
|
|
50
|
+
/**
|
|
51
|
+
* Display-only callers on a STRATEGY step (the chooser already clicked):
|
|
52
|
+
* keep the RESOLUTION STRATEGY band for continuity, collapsed from three
|
|
53
|
+
* choices to the chosen one — a passive echo, never a second chooser.
|
|
54
|
+
* Ignored when `annotate` is present (the live chooser owns the band).
|
|
55
|
+
*/
|
|
56
|
+
chosenStrategy?: { label: string; value: string };
|
|
18
57
|
translations: {
|
|
19
|
-
title: string;
|
|
20
58
|
loadingContext: string;
|
|
21
59
|
failedContext: string;
|
|
22
|
-
|
|
23
|
-
generate: string;
|
|
24
|
-
compose: string;
|
|
25
|
-
resolutionStrategyLabel: string;
|
|
26
|
-
} & ContextSummaryTranslations;
|
|
60
|
+
} & ContextSummaryTranslations & CorpusPaneTranslations;
|
|
27
61
|
}
|
|
28
62
|
|
|
29
63
|
export function GatherContextStep({
|
|
30
64
|
context,
|
|
31
65
|
contextLoading,
|
|
32
66
|
contextError,
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
onGenerate,
|
|
37
|
-
onCompose,
|
|
67
|
+
pending,
|
|
68
|
+
annotate,
|
|
69
|
+
chosenStrategy,
|
|
38
70
|
translations: t,
|
|
39
71
|
}: GatherContextStepProps) {
|
|
40
72
|
const [sourceExpanded, setSourceExpanded] = useState(false);
|
|
41
73
|
const contextReady = !contextLoading && !contextError && !!context;
|
|
42
74
|
const focus = context?.focus.kind === 'annotation' ? context.focus : null;
|
|
43
75
|
const resourceFocus = context?.focus.kind === 'resource' ? context.focus : null;
|
|
76
|
+
// GEP D6: the focal span WEARS its motivation — the viewer's own registry
|
|
77
|
+
// class (the same one the document applies), never a copy or a chip.
|
|
78
|
+
const focalKey = focus?.annotation.motivation
|
|
79
|
+
? annotatorKeyForMotivation(focus.annotation.motivation)
|
|
80
|
+
: undefined;
|
|
81
|
+
const focalMotivationClass = focalKey ? ANNOTATORS[focalKey].className : undefined;
|
|
44
82
|
const highlightRef = useRef<HTMLSpanElement>(null);
|
|
45
83
|
|
|
46
84
|
// Scroll the highlighted term into view when context loads
|
|
@@ -50,10 +88,116 @@ export function GatherContextStep({
|
|
|
50
88
|
}
|
|
51
89
|
}, [context]);
|
|
52
90
|
|
|
91
|
+
// Shared by the loaded view and the loading skeleton (the Hint is wizard
|
|
92
|
+
// state — safely typed while the gather runs — and the chooser's buttons
|
|
93
|
+
// already gate on contextReady). One JSX, two hosts: never two copies.
|
|
94
|
+
const hintRow = annotate && (
|
|
95
|
+
<div className="semiont-gather__hint-row">
|
|
96
|
+
<div className="semiont-form__field">
|
|
97
|
+
<label className="semiont-form__label">
|
|
98
|
+
{annotate.translations.userHintLabel}
|
|
99
|
+
<span className="semiont-gather__hint-effect"> — {annotate.translations.userHintEffect}</span>
|
|
100
|
+
</label>
|
|
101
|
+
<textarea
|
|
102
|
+
value={annotate.userHint}
|
|
103
|
+
onChange={(e) => annotate.onUserHintChange(e.target.value)}
|
|
104
|
+
placeholder={annotate.translations.userHintPlaceholder}
|
|
105
|
+
className="semiont-search-modal__search-input semiont-gather__hint-textarea"
|
|
106
|
+
style={{ resize: 'vertical', fontFamily: 'inherit' }}
|
|
107
|
+
/>
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
const strategyFooter = annotate && (
|
|
113
|
+
<div className="semiont-gather__footer">
|
|
114
|
+
<div className="semiont-gather__footer-label">{annotate.translations.resolutionStrategyLabel}</div>
|
|
115
|
+
<div className="semiont-gather__actions">
|
|
116
|
+
<button
|
|
117
|
+
type="button"
|
|
118
|
+
onClick={annotate.onBind}
|
|
119
|
+
disabled={!contextReady}
|
|
120
|
+
className="semiont-button--primary semiont-button--flex"
|
|
121
|
+
>
|
|
122
|
+
🔍 {annotate.translations.search}…
|
|
123
|
+
</button>
|
|
124
|
+
<button
|
|
125
|
+
type="button"
|
|
126
|
+
onClick={annotate.onGenerate}
|
|
127
|
+
disabled={!contextReady}
|
|
128
|
+
className="semiont-button--primary semiont-button--flex"
|
|
129
|
+
>
|
|
130
|
+
✨ {annotate.translations.generate}…
|
|
131
|
+
</button>
|
|
132
|
+
<button
|
|
133
|
+
type="button"
|
|
134
|
+
onClick={annotate.onCompose}
|
|
135
|
+
disabled={!contextReady}
|
|
136
|
+
className="semiont-button--secondary semiont-button--flex"
|
|
137
|
+
>
|
|
138
|
+
✍️ {annotate.translations.compose}
|
|
139
|
+
</button>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
);
|
|
143
|
+
|
|
53
144
|
return (
|
|
54
145
|
<div className="semiont-gather__outer">
|
|
55
|
-
{/* Loading / error states
|
|
56
|
-
|
|
146
|
+
{/* Loading / error states. With a `pending` preview the loading screen
|
|
147
|
+
is the loaded screen minus the data: the annotation's own facts and
|
|
148
|
+
the pane headers hold the layout, and one small dots animation sits
|
|
149
|
+
in EACH zone — source excerpt, Neighborhood, Similar passages —
|
|
150
|
+
exactly where its content will land. No central block, no dead
|
|
151
|
+
space; the sentence survives for screen readers. */}
|
|
152
|
+
{contextLoading && pending && (
|
|
153
|
+
<div className="semiont-gather__skeleton" role="status">
|
|
154
|
+
<span className="semiont-sr-only">{t.loadingContext}</span>
|
|
155
|
+
{pending.resourceName && (
|
|
156
|
+
<div className="semiont-gather__skeleton-source">
|
|
157
|
+
{t.sourceContextLabel}{` "${pending.resourceName}"`}
|
|
158
|
+
</div>
|
|
159
|
+
)}
|
|
160
|
+
<div className="semiont-gather__skeleton-dots" aria-hidden="true">
|
|
161
|
+
<span className="semiont-gather__loading-dot" />
|
|
162
|
+
<span className="semiont-gather__loading-dot" />
|
|
163
|
+
<span className="semiont-gather__loading-dot" />
|
|
164
|
+
</div>
|
|
165
|
+
<div className="semiont-gather__skeleton-header">
|
|
166
|
+
{/* Straight quotes — the same presentation the panel entries use. */}
|
|
167
|
+
<span className="semiont-gather__skeleton-exact">"{pending.exact}"</span>
|
|
168
|
+
{pending.entityTypes.length > 0 && (
|
|
169
|
+
<span style={{ display: 'flex', gap: '0.25rem', flexWrap: 'wrap' }}>
|
|
170
|
+
{pending.entityTypes.map(et => (
|
|
171
|
+
<span key={et} className="semiont-chip" style={{ fontSize: 'var(--semiont-text-xs)', padding: '0.125rem 0.375rem', fontWeight: 400 }}>
|
|
172
|
+
{et}
|
|
173
|
+
</span>
|
|
174
|
+
))}
|
|
175
|
+
</span>
|
|
176
|
+
)}
|
|
177
|
+
</div>
|
|
178
|
+
<div className="semiont-gather__skeleton-panes">
|
|
179
|
+
<div className="semiont-gather__skeleton-pane">
|
|
180
|
+
<div className="semiont-gather-pane__title">{t.graphPaneTitle}</div>
|
|
181
|
+
<div className="semiont-gather__skeleton-dots" aria-hidden="true">
|
|
182
|
+
<span className="semiont-gather__loading-dot" />
|
|
183
|
+
<span className="semiont-gather__loading-dot" />
|
|
184
|
+
<span className="semiont-gather__loading-dot" />
|
|
185
|
+
</div>
|
|
186
|
+
</div>
|
|
187
|
+
<div className="semiont-gather__skeleton-pane">
|
|
188
|
+
<div className="semiont-gather-pane__title">{t.corpusPaneTitle}</div>
|
|
189
|
+
<div className="semiont-gather__skeleton-dots" aria-hidden="true">
|
|
190
|
+
<span className="semiont-gather__loading-dot" />
|
|
191
|
+
<span className="semiont-gather__loading-dot" />
|
|
192
|
+
<span className="semiont-gather__loading-dot" />
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
</div>
|
|
196
|
+
{hintRow}
|
|
197
|
+
{strategyFooter}
|
|
198
|
+
</div>
|
|
199
|
+
)}
|
|
200
|
+
{contextLoading && !pending && (
|
|
57
201
|
<div className="semiont-gather__loading">
|
|
58
202
|
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
|
59
203
|
<span className="semiont-gather__loading-dot" />
|
|
@@ -71,37 +215,36 @@ export function GatherContextStep({
|
|
|
71
215
|
|
|
72
216
|
{context && (
|
|
73
217
|
<>
|
|
74
|
-
{/* Full-width source context strip — annotation focus
|
|
218
|
+
{/* Full-width source context strip — annotation focus.
|
|
219
|
+
GEP P1 (D6/D7): the quotation is contiguous prose — anchor
|
|
220
|
+
metadata never interrupts it. Entity chips are tokens and live on
|
|
221
|
+
the label row; the motivation is never labeled, the focal span
|
|
222
|
+
WEARS it via the viewer's own registry class (same class the
|
|
223
|
+
document applies, themes included). Only annotations the context
|
|
224
|
+
can PLACE render here — today exactly `focus.annotation`. */}
|
|
75
225
|
{focus?.selected && (
|
|
76
226
|
<div className="semiont-gather__source-strip">
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
227
|
+
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: '0.5rem', marginBottom: '0.375rem' }}>
|
|
228
|
+
<label className="semiont-form__label" style={{ marginBottom: 0 }}>
|
|
229
|
+
{t.sourceContextLabel}{focus.sourceResource.name ? ` "${focus.sourceResource.name}"` : ''}
|
|
230
|
+
</label>
|
|
231
|
+
{(context.metadata?.entityTypes ?? []).length > 0 && (
|
|
232
|
+
<span style={{ display: 'flex', gap: '0.25rem', flexWrap: 'wrap' }}>
|
|
233
|
+
{(context.metadata?.entityTypes ?? []).map(et => (
|
|
234
|
+
<span key={et} className="semiont-chip" style={{ fontSize: 'var(--semiont-text-xs)', padding: '0.125rem 0.375rem', fontWeight: 400 }}>
|
|
235
|
+
{et}
|
|
236
|
+
</span>
|
|
237
|
+
))}
|
|
238
|
+
</span>
|
|
239
|
+
)}
|
|
240
|
+
</div>
|
|
80
241
|
<div className={`semiont-gather__source-box${sourceExpanded ? ' semiont-gather__source-box--expanded' : ''}`}>
|
|
81
242
|
<div className="semiont-gather__source-context">
|
|
82
|
-
<div style={{ fontSize: 'var(--semiont-text-sm)',
|
|
243
|
+
<div style={{ fontSize: 'var(--semiont-text-sm)', whiteSpace: 'pre-wrap', color: 'var(--semiont-text-secondary)' }}>
|
|
83
244
|
{focus.selected.before && <span>{focus.selected.before}</span>}
|
|
84
|
-
<span
|
|
85
|
-
ref={highlightRef}
|
|
86
|
-
style={{
|
|
87
|
-
backgroundColor: 'var(--semiont-color-primary-100)',
|
|
88
|
-
padding: '0 0.25rem',
|
|
89
|
-
fontWeight: 600,
|
|
90
|
-
color: 'var(--semiont-color-primary-900)',
|
|
91
|
-
}}
|
|
92
|
-
>
|
|
245
|
+
<span ref={highlightRef} {...(focalMotivationClass ? { className: focalMotivationClass } : {})}>
|
|
93
246
|
{focus.selected.text}
|
|
94
247
|
</span>
|
|
95
|
-
{(context.metadata?.entityTypes ?? []).map(et => (
|
|
96
|
-
<span key={et} className="semiont-chip" style={{ fontSize: 'var(--semiont-text-xs)', padding: '0.125rem 0.375rem', fontWeight: 400, verticalAlign: 'middle', marginLeft: '0.25rem' }}>
|
|
97
|
-
{et}
|
|
98
|
-
</span>
|
|
99
|
-
))}
|
|
100
|
-
{focus.annotation.motivation && (
|
|
101
|
-
<span className="semiont-chip" style={{ fontSize: 'var(--semiont-text-xs)', padding: '0.125rem 0.375rem', fontWeight: 400, verticalAlign: 'middle', marginLeft: '0.25rem' }}>
|
|
102
|
-
{focus.annotation.motivation}
|
|
103
|
-
</span>
|
|
104
|
-
)}
|
|
105
248
|
{focus.selected.after && <span>{focus.selected.after}</span>}
|
|
106
249
|
</div>
|
|
107
250
|
</div>
|
|
@@ -116,22 +259,30 @@ export function GatherContextStep({
|
|
|
116
259
|
</div>
|
|
117
260
|
)}
|
|
118
261
|
|
|
119
|
-
{/* Full-width source context strip — resource focus
|
|
262
|
+
{/* Full-width source context strip — resource focus. Same D6 rule as
|
|
263
|
+
the annotation strip: chips are metadata about the anchor and
|
|
264
|
+
live on the label row, never inside the prose. */}
|
|
120
265
|
{resourceFocus && (
|
|
121
266
|
<div className="semiont-gather__source-strip">
|
|
122
|
-
<
|
|
123
|
-
|
|
124
|
-
|
|
267
|
+
<div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', gap: '0.5rem', marginBottom: '0.375rem' }}>
|
|
268
|
+
<label className="semiont-form__label" style={{ marginBottom: 0 }}>
|
|
269
|
+
{t.sourceContextLabel}{resourceFocus.resource.name ? ` "${resourceFocus.resource.name}"` : ''}
|
|
270
|
+
</label>
|
|
271
|
+
{(context.metadata?.entityTypes ?? []).length > 0 && (
|
|
272
|
+
<span style={{ display: 'flex', gap: '0.25rem', flexWrap: 'wrap' }}>
|
|
273
|
+
{(context.metadata?.entityTypes ?? []).map(et => (
|
|
274
|
+
<span key={et} className="semiont-chip" style={{ fontSize: 'var(--semiont-text-xs)', padding: '0.125rem 0.375rem', fontWeight: 400 }}>
|
|
275
|
+
{et}
|
|
276
|
+
</span>
|
|
277
|
+
))}
|
|
278
|
+
</span>
|
|
279
|
+
)}
|
|
280
|
+
</div>
|
|
125
281
|
{(resourceFocus.summary || resourceFocus.content?.main) && (
|
|
126
282
|
<div className={`semiont-gather__source-box${sourceExpanded ? ' semiont-gather__source-box--expanded' : ''}`}>
|
|
127
283
|
<div className="semiont-gather__source-context">
|
|
128
284
|
<div style={{ fontSize: 'var(--semiont-text-sm)', whiteSpace: 'pre-wrap', color: 'var(--semiont-text-secondary)' }}>
|
|
129
285
|
{resourceFocus.summary ?? resourceFocus.content?.main}
|
|
130
|
-
{(context.metadata?.entityTypes ?? []).map(et => (
|
|
131
|
-
<span key={et} className="semiont-chip" style={{ fontSize: 'var(--semiont-text-xs)', padding: '0.125rem 0.375rem', fontWeight: 400, verticalAlign: 'middle', marginLeft: '0.25rem' }}>
|
|
132
|
-
{et}
|
|
133
|
-
</span>
|
|
134
|
-
))}
|
|
135
286
|
</div>
|
|
136
287
|
</div>
|
|
137
288
|
<button
|
|
@@ -144,73 +295,52 @@ export function GatherContextStep({
|
|
|
144
295
|
</div>
|
|
145
296
|
)}
|
|
146
297
|
{resourceFocus.suggestedReferences && resourceFocus.suggestedReferences.length > 0 && (
|
|
147
|
-
|
|
298
|
+
// Prose, not chips (D3): the live values are full research
|
|
299
|
+
// prompts — sentences. The chip vocabulary stays for tokens.
|
|
300
|
+
<ul className="semiont-gather__suggested">
|
|
148
301
|
{resourceFocus.suggestedReferences.map(ref => (
|
|
149
|
-
<
|
|
150
|
-
{ref}
|
|
151
|
-
</span>
|
|
302
|
+
<li key={ref}>{ref}</li>
|
|
152
303
|
))}
|
|
153
|
-
</
|
|
304
|
+
</ul>
|
|
154
305
|
)}
|
|
155
306
|
</div>
|
|
156
307
|
)}
|
|
157
308
|
|
|
158
|
-
{/*
|
|
309
|
+
{/* The evidence panes (GEP D1): curated knowledge beside latent
|
|
310
|
+
knowledge — the fork reads off them. Both render for every
|
|
311
|
+
caller; emptiness is evidence, never blankness. */}
|
|
159
312
|
<div className="semiont-gather__body">
|
|
160
|
-
{/* Left: context summary (graph views) */}
|
|
161
313
|
<div className="semiont-gather__left">
|
|
162
314
|
<ContextSummary context={context} translations={t} />
|
|
163
315
|
</div>
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
<div className="semiont-gather__right">
|
|
168
|
-
<div className="semiont-form__field">
|
|
169
|
-
<label className="semiont-form__label">
|
|
170
|
-
{t.userHintLabel}
|
|
171
|
-
</label>
|
|
172
|
-
<textarea
|
|
173
|
-
value={userHint}
|
|
174
|
-
onChange={(e) => onUserHintChange?.(e.target.value)}
|
|
175
|
-
placeholder={t.userHintPlaceholder}
|
|
176
|
-
className="semiont-search-modal__search-input semiont-gather__hint-textarea"
|
|
177
|
-
style={{ resize: 'vertical', fontFamily: 'inherit' }}
|
|
178
|
-
/>
|
|
179
|
-
</div>
|
|
180
|
-
</div>
|
|
181
|
-
)}
|
|
316
|
+
<div className="semiont-gather__right">
|
|
317
|
+
<CorpusPane semanticContext={context.semanticContext} translations={t} />
|
|
318
|
+
</div>
|
|
182
319
|
</div>
|
|
183
320
|
|
|
184
|
-
{/*
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
onClick={onCompose}
|
|
208
|
-
disabled={!contextReady}
|
|
209
|
-
className="semiont-button--secondary semiont-button--flex"
|
|
210
|
-
>
|
|
211
|
-
✍️ {t.compose}
|
|
212
|
-
</button>
|
|
213
|
-
</div>
|
|
321
|
+
{/* Hint: full width, input-then-act adjacency above the footer
|
|
322
|
+
(GEP D5). The label states its effect. Typing does NOT re-run
|
|
323
|
+
recall — the panes are the at-gather evidence. */}
|
|
324
|
+
{focus && hintRow}
|
|
325
|
+
|
|
326
|
+
{/* Full-width footer: resolution strategy (annotation-wizard callers only).
|
|
327
|
+
The THIRD footer species — a CHOOSER — and the deliberate exception to
|
|
328
|
+
the WizardFooter grammar, which is one-retreat-plus-one-advance and
|
|
329
|
+
cannot express a fork. Its own grammar, pinned in WizardFooter.test:
|
|
330
|
+
• three mutually exclusive strategies, equal width (`--flex` is honest
|
|
331
|
+
here: these ARE peers — the same class the shared footer banned for
|
|
332
|
+
falsely making retreat a peer of advance);
|
|
333
|
+
• no dismissal (corner ✕/Esc, as everywhere);
|
|
334
|
+
• all gated on contextReady;
|
|
335
|
+
• the AI paths are primary, the manual path secondary — Compose's
|
|
336
|
+
demotion is the recorded convention, not drift;
|
|
337
|
+
• ellipses are component-owned and mark step-vs-act: Search…/Generate…
|
|
338
|
+
lead to another step; Compose acts immediately (navigates away). */}
|
|
339
|
+
{focus && strategyFooter}
|
|
340
|
+
{!annotate && chosenStrategy && (
|
|
341
|
+
<div className="semiont-gather__footer semiont-gather__footer--echo">
|
|
342
|
+
<div className="semiont-gather__footer-label">{chosenStrategy.label}</div>
|
|
343
|
+
<span className="semiont-gather__chosen-strategy">{chosenStrategy.value}</span>
|
|
214
344
|
</div>
|
|
215
345
|
)}
|
|
216
346
|
</>
|