@semiont/react-ui 0.5.1 → 0.5.3
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 +13 -0
- package/dist/{ar-3W37O3R3.mjs → ar-UUMMNQKF.mjs} +2 -17
- package/dist/ar-UUMMNQKF.mjs.map +1 -0
- package/dist/{bn-JZTJLMVE.mjs → bn-AL5BJSR3.mjs} +2 -17
- package/dist/bn-AL5BJSR3.mjs.map +1 -0
- package/dist/{chunk-4NOUO3W6.mjs → chunk-EBBL3VJI.mjs} +5062 -2906
- package/dist/chunk-EBBL3VJI.mjs.map +1 -0
- package/dist/{chunk-NOD3NCXE.mjs → chunk-OJSRLEER.mjs} +2 -17
- package/dist/chunk-OJSRLEER.mjs.map +1 -0
- package/dist/{cs-XYHH7HNE.mjs → cs-UMINALSU.mjs} +2 -17
- package/dist/cs-UMINALSU.mjs.map +1 -0
- package/dist/{da-MZKIECVT.mjs → da-FKUX6CDL.mjs} +2 -17
- package/dist/da-FKUX6CDL.mjs.map +1 -0
- package/dist/{de-AYXTMRQW.mjs → de-XSJ3E25S.mjs} +2 -17
- package/dist/de-XSJ3E25S.mjs.map +1 -0
- package/dist/{el-A6CVQWAW.mjs → el-UJXNRCBP.mjs} +2 -17
- package/dist/el-UJXNRCBP.mjs.map +1 -0
- package/dist/{en-YPQQBI4T.mjs → en-J5DHKLQ5.mjs} +2 -2
- package/dist/{es-M2HXLJGT.mjs → es-VURP62BU.mjs} +2 -17
- package/dist/es-VURP62BU.mjs.map +1 -0
- package/dist/{fa-V6JZJDYP.mjs → fa-TIT5ZPZY.mjs} +2 -17
- package/dist/fa-TIT5ZPZY.mjs.map +1 -0
- package/dist/{fi-ONDTZ5H7.mjs → fi-F7VTGT4H.mjs} +2 -17
- package/dist/fi-F7VTGT4H.mjs.map +1 -0
- package/dist/{fr-PAPV4H4G.mjs → fr-2ZR26VF7.mjs} +2 -17
- package/dist/fr-2ZR26VF7.mjs.map +1 -0
- package/dist/{he-F6VTLJLW.mjs → he-BXP2KYVZ.mjs} +2 -17
- package/dist/he-BXP2KYVZ.mjs.map +1 -0
- package/dist/{hi-CFUAV4BF.mjs → hi-PSWTP3NC.mjs} +2 -17
- package/dist/hi-PSWTP3NC.mjs.map +1 -0
- package/dist/{id-NBKLCCI7.mjs → id-HO6TXGTO.mjs} +2 -17
- package/dist/id-HO6TXGTO.mjs.map +1 -0
- package/dist/index.d.mts +292 -27
- package/dist/index.mjs +1134 -592
- package/dist/index.mjs.map +1 -1
- package/dist/{it-SLSOWVVU.mjs → it-AGTDMBL3.mjs} +2 -17
- package/dist/it-AGTDMBL3.mjs.map +1 -0
- package/dist/{ja-L5IG4ECE.mjs → ja-TTGOVF5K.mjs} +2 -17
- package/dist/ja-TTGOVF5K.mjs.map +1 -0
- package/dist/{ko-QYMTULKK.mjs → ko-FF77IQ7N.mjs} +2 -17
- package/dist/ko-FF77IQ7N.mjs.map +1 -0
- package/dist/{ms-5DGSFKM2.mjs → ms-UPQWWIL4.mjs} +2 -17
- package/dist/ms-UPQWWIL4.mjs.map +1 -0
- package/dist/{nl-VZPCGONO.mjs → nl-W75HEPFL.mjs} +2 -17
- package/dist/nl-W75HEPFL.mjs.map +1 -0
- package/dist/{no-MF6F352I.mjs → no-R4W7W7ZU.mjs} +2 -17
- package/dist/no-R4W7W7ZU.mjs.map +1 -0
- package/dist/{pl-WIK72JUO.mjs → pl-GQC2ELWO.mjs} +2 -17
- package/dist/pl-GQC2ELWO.mjs.map +1 -0
- package/dist/{pt-RRP5ZF6A.mjs → pt-YGVT62RU.mjs} +2 -17
- package/dist/pt-YGVT62RU.mjs.map +1 -0
- package/dist/{ro-XHQLC3T7.mjs → ro-TST6XS6X.mjs} +2 -17
- package/dist/ro-TST6XS6X.mjs.map +1 -0
- package/dist/{sv-EWULDN6E.mjs → sv-TQLF6HV7.mjs} +2 -17
- package/dist/sv-TQLF6HV7.mjs.map +1 -0
- package/dist/test-utils.d.mts +1 -1
- package/dist/test-utils.mjs +5 -2353
- package/dist/test-utils.mjs.map +1 -1
- package/dist/{th-TGOBHFG4.mjs → th-HJUIETVR.mjs} +2 -17
- package/dist/th-HJUIETVR.mjs.map +1 -0
- package/dist/{tr-LMMPBMV7.mjs → tr-CW3C46TW.mjs} +2 -17
- package/dist/tr-CW3C46TW.mjs.map +1 -0
- package/dist/{uk-IPGRRJY6.mjs → uk-WTHZQB2U.mjs} +2 -17
- package/dist/uk-WTHZQB2U.mjs.map +1 -0
- package/dist/{vi-Q676OJQS.mjs → vi-PHWHJLKP.mjs} +2 -17
- package/dist/vi-PHWHJLKP.mjs.map +1 -0
- package/dist/{zh-F3MTWQDX.mjs → zh-MO3FCUD6.mjs} +2 -17
- package/dist/zh-MO3FCUD6.mjs.map +1 -0
- package/package.json +1 -1
- package/src/components/StatusDisplay.tsx +1 -1
- package/src/components/modals/PermissionDeniedModal.tsx +2 -2
- package/src/components/modals/SessionExpiredModal.tsx +4 -4
- package/src/components/resource/panels/AssessmentPanel.tsx +4 -0
- package/src/components/resource/panels/AssistSection.tsx +10 -1
- package/src/components/resource/panels/CollaborationPanel.tsx +1 -1
- package/src/components/resource/panels/CommentsPanel.tsx +4 -0
- package/src/components/resource/panels/HighlightPanel.tsx +4 -0
- package/src/components/resource/panels/ReferencesPanel.tsx +11 -0
- package/src/components/resource/panels/TagEntry.tsx +13 -2
- package/src/components/resource/panels/TaggingPanel.tsx +93 -41
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +11 -1
- package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +2 -2
- package/src/components/resource/panels/__tests__/TagEntry.test.tsx +26 -19
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +128 -38
- package/src/features/admin-devops/components/AdminDevOpsPage.tsx +1 -1
- package/src/features/admin-exchange/components/AdminExchangePage.tsx +1 -1
- package/src/features/admin-exchange/components/ImportCard.tsx +1 -1
- package/src/features/admin-exchange/state/__tests__/exchange-state-unit.test.ts +171 -0
- package/src/features/admin-exchange/state/exchange-state-unit.ts +131 -0
- package/src/features/admin-security/components/AdminSecurityPage.tsx +1 -1
- package/src/features/admin-security/state/__tests__/admin-security-state-unit.test.ts +68 -0
- package/src/features/admin-security/state/admin-security-state-unit.ts +46 -0
- package/src/features/admin-users/components/AdminUsersPage.tsx +1 -1
- package/src/features/admin-users/state/__tests__/admin-users-state-unit.test.ts +86 -0
- package/src/features/admin-users/state/admin-users-state-unit.ts +73 -0
- package/src/features/auth-welcome/state/__tests__/welcome-state-unit.test.ts +86 -0
- package/src/features/auth-welcome/state/welcome-state-unit.ts +44 -0
- package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -1
- package/src/features/moderate-entity-tags/state/__tests__/entity-tags-state-unit.test.ts +102 -0
- package/src/features/moderate-entity-tags/state/entity-tags-state-unit.ts +64 -0
- package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -1
- package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +4 -4
- package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +1 -1
- package/src/features/resource-compose/__tests__/UploadProgressBar.test.tsx +225 -0
- package/src/features/resource-compose/components/ResourceComposePage.tsx +19 -4
- package/src/features/resource-compose/components/UploadProgressBar.tsx +94 -0
- package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +187 -0
- package/src/features/resource-compose/state/compose-page-state-unit.ts +209 -0
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +1 -1
- package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +76 -0
- package/src/features/resource-discovery/state/discover-state-unit.ts +54 -0
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +4 -2
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +36 -32
- package/src/features/resource-viewer/state/__tests__/resource-loader-state-unit.test.ts +46 -0
- package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +203 -0
- package/src/features/resource-viewer/state/resource-loader-state-unit.ts +26 -0
- package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +180 -0
- package/translations/ar.json +1 -16
- package/translations/bn.json +1 -16
- package/translations/cs.json +1 -16
- package/translations/da.json +1 -16
- package/translations/de.json +1 -16
- package/translations/el.json +1 -16
- package/translations/en.json +1 -16
- package/translations/es.json +1 -16
- package/translations/fa.json +1 -16
- package/translations/fi.json +1 -16
- package/translations/fr.json +1 -16
- package/translations/he.json +1 -16
- package/translations/hi.json +1 -16
- package/translations/id.json +1 -16
- package/translations/it.json +1 -16
- package/translations/ja.json +1 -16
- package/translations/ko.json +1 -16
- package/translations/ms.json +1 -16
- package/translations/nl.json +1 -16
- package/translations/no.json +1 -16
- package/translations/pl.json +1 -16
- package/translations/pt.json +1 -16
- package/translations/ro.json +1 -16
- package/translations/sv.json +1 -16
- package/translations/th.json +1 -16
- package/translations/tr.json +1 -16
- package/translations/uk.json +1 -16
- package/translations/vi.json +1 -16
- package/translations/zh.json +1 -16
- package/dist/ar-3W37O3R3.mjs.map +0 -1
- package/dist/bn-JZTJLMVE.mjs.map +0 -1
- package/dist/chunk-4NOUO3W6.mjs.map +0 -1
- package/dist/chunk-NOD3NCXE.mjs.map +0 -1
- package/dist/cs-XYHH7HNE.mjs.map +0 -1
- package/dist/da-MZKIECVT.mjs.map +0 -1
- package/dist/de-AYXTMRQW.mjs.map +0 -1
- package/dist/el-A6CVQWAW.mjs.map +0 -1
- package/dist/es-M2HXLJGT.mjs.map +0 -1
- package/dist/fa-V6JZJDYP.mjs.map +0 -1
- package/dist/fi-ONDTZ5H7.mjs.map +0 -1
- package/dist/fr-PAPV4H4G.mjs.map +0 -1
- package/dist/he-F6VTLJLW.mjs.map +0 -1
- package/dist/hi-CFUAV4BF.mjs.map +0 -1
- package/dist/id-NBKLCCI7.mjs.map +0 -1
- package/dist/it-SLSOWVVU.mjs.map +0 -1
- package/dist/ja-L5IG4ECE.mjs.map +0 -1
- package/dist/ko-QYMTULKK.mjs.map +0 -1
- package/dist/ms-5DGSFKM2.mjs.map +0 -1
- package/dist/nl-VZPCGONO.mjs.map +0 -1
- package/dist/no-MF6F352I.mjs.map +0 -1
- package/dist/pl-WIK72JUO.mjs.map +0 -1
- package/dist/pt-RRP5ZF6A.mjs.map +0 -1
- package/dist/ro-XHQLC3T7.mjs.map +0 -1
- package/dist/sv-EWULDN6E.mjs.map +0 -1
- package/dist/th-TGOBHFG4.mjs.map +0 -1
- package/dist/tr-LMMPBMV7.mjs.map +0 -1
- package/dist/uk-IPGRRJY6.mjs.map +0 -1
- package/dist/vi-Q676OJQS.mjs.map +0 -1
- package/dist/zh-F3MTWQDX.mjs.map +0 -1
- /package/dist/{en-YPQQBI4T.mjs.map → en-J5DHKLQ5.mjs.map} +0 -0
|
@@ -5,11 +5,11 @@ import { useSemiont } from '../../session/SemiontProvider';
|
|
|
5
5
|
import { useObservable } from '../../hooks/useObservable';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Modal that surfaces when the active KB's session expires (a 401
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* Modal that surfaces when the active KB's session expires (a 401
|
|
9
|
+
* surfaced by the session's own JWT validation or by the host's
|
|
10
|
+
* error-routing path).
|
|
11
11
|
*
|
|
12
|
-
* Reads `sessionExpiredAt$` from the active `
|
|
12
|
+
* Reads `sessionExpiredAt$` from the active `SessionSignals`.
|
|
13
13
|
* When the user dismisses the modal, the signals instance clears the
|
|
14
14
|
* flag. Modal state lives on signals (not the session itself) so
|
|
15
15
|
* headless sessions (workers/CLIs) don't carry dead observables.
|
|
@@ -45,6 +45,8 @@ interface AssessmentPanelProps {
|
|
|
45
45
|
isAssisting?: boolean;
|
|
46
46
|
progress?: JobProgress | null;
|
|
47
47
|
locale?: string;
|
|
48
|
+
/** BCP-47 tag of the resource being analyzed — forwarded to the assist call. */
|
|
49
|
+
sourceLanguage?: string;
|
|
48
50
|
annotateMode?: boolean;
|
|
49
51
|
scrollToAnnotationId?: string | null;
|
|
50
52
|
onScrollCompleted?: () => void;
|
|
@@ -64,6 +66,7 @@ export function AssessmentPanel({
|
|
|
64
66
|
isAssisting = false,
|
|
65
67
|
progress,
|
|
66
68
|
locale,
|
|
69
|
+
sourceLanguage,
|
|
67
70
|
annotateMode = true,
|
|
68
71
|
scrollToAnnotationId,
|
|
69
72
|
onScrollCompleted,
|
|
@@ -243,6 +246,7 @@ export function AssessmentPanel({
|
|
|
243
246
|
annotationType="assessment"
|
|
244
247
|
isAssisting={isAssisting}
|
|
245
248
|
locale={locale}
|
|
249
|
+
sourceLanguage={sourceLanguage}
|
|
246
250
|
progress={progress}
|
|
247
251
|
/>
|
|
248
252
|
)}
|
|
@@ -12,7 +12,10 @@ type JobProgress = components['schemas']['JobProgress'];
|
|
|
12
12
|
interface AssistSectionProps {
|
|
13
13
|
annotationType: 'highlight' | 'assessment' | 'comment';
|
|
14
14
|
isAssisting: boolean;
|
|
15
|
+
/** User UI locale — written into the annotation body's `language` field for comment/assessment. */
|
|
15
16
|
locale?: string;
|
|
17
|
+
/** BCP-47 tag of the resource being analyzed. Forwarded to the prompt so the LLM analyzes non-English source correctly. */
|
|
18
|
+
sourceLanguage?: string;
|
|
16
19
|
progress?: JobProgress | null | undefined;
|
|
17
20
|
}
|
|
18
21
|
|
|
@@ -34,6 +37,7 @@ export function AssistSection({
|
|
|
34
37
|
annotationType,
|
|
35
38
|
isAssisting,
|
|
36
39
|
locale,
|
|
40
|
+
sourceLanguage,
|
|
37
41
|
progress,
|
|
38
42
|
}: AssistSectionProps) {
|
|
39
43
|
|
|
@@ -74,13 +78,18 @@ export function AssistSection({
|
|
|
74
78
|
instructions: instructions.trim() || undefined,
|
|
75
79
|
tone: (annotationType === 'comment' || annotationType === 'assessment') && tone ? tone : undefined,
|
|
76
80
|
density: (annotationType === 'comment' || annotationType === 'assessment' || annotationType === 'highlight') && useDensity ? density : undefined,
|
|
81
|
+
// Body locale only applies where the LLM writes natural-language text:
|
|
82
|
+
// comment/assessment have a body, highlight does not.
|
|
77
83
|
language: (annotationType === 'comment' || annotationType === 'assessment') ? locale : undefined,
|
|
84
|
+
// Source locale applies to all three — affects analysis quality on
|
|
85
|
+
// non-English source, regardless of whether a body is produced.
|
|
86
|
+
sourceLanguage,
|
|
78
87
|
});
|
|
79
88
|
|
|
80
89
|
setInstructions('');
|
|
81
90
|
setTone('');
|
|
82
91
|
// Don't reset density/useDensity - persist across assists
|
|
83
|
-
}, [annotationType, instructions, tone, useDensity, density, locale, session]);
|
|
92
|
+
}, [annotationType, instructions, tone, useDensity, density, locale, sourceLanguage, session]);
|
|
84
93
|
|
|
85
94
|
const handleDismissProgress = useCallback(() => {
|
|
86
95
|
session?.client.mark.dismissProgress();
|
|
@@ -7,7 +7,7 @@ import './CollaborationPanel.css';
|
|
|
7
7
|
interface Props {
|
|
8
8
|
/**
|
|
9
9
|
* Connection state from `client.actor.state$`. See
|
|
10
|
-
* `packages/api-client/src/
|
|
10
|
+
* `packages/api-client/src/state/domain/actor-state-unit.ts`.
|
|
11
11
|
*
|
|
12
12
|
* UI mapping:
|
|
13
13
|
* `open` | `reconnecting` | `initial` | `connecting`
|
|
@@ -46,6 +46,8 @@ interface CommentsPanelProps {
|
|
|
46
46
|
isAssisting?: boolean;
|
|
47
47
|
progress?: JobProgress | null;
|
|
48
48
|
locale?: string;
|
|
49
|
+
/** BCP-47 tag of the resource being analyzed — forwarded to the assist call. */
|
|
50
|
+
sourceLanguage?: string;
|
|
49
51
|
scrollToAnnotationId?: string | null;
|
|
50
52
|
onScrollCompleted?: () => void;
|
|
51
53
|
hoveredAnnotationId?: string | null;
|
|
@@ -65,6 +67,7 @@ export function CommentsPanel({
|
|
|
65
67
|
isAssisting = false,
|
|
66
68
|
progress,
|
|
67
69
|
locale,
|
|
70
|
+
sourceLanguage,
|
|
68
71
|
scrollToAnnotationId,
|
|
69
72
|
onScrollCompleted,
|
|
70
73
|
hoveredAnnotationId,
|
|
@@ -253,6 +256,7 @@ export function CommentsPanel({
|
|
|
253
256
|
annotationType="comment"
|
|
254
257
|
isAssisting={isAssisting}
|
|
255
258
|
locale={locale}
|
|
259
|
+
sourceLanguage={sourceLanguage}
|
|
256
260
|
progress={progress}
|
|
257
261
|
/>
|
|
258
262
|
)}
|
|
@@ -31,6 +31,8 @@ interface HighlightPanelProps {
|
|
|
31
31
|
scrollToAnnotationId?: string | null;
|
|
32
32
|
onScrollCompleted?: () => void;
|
|
33
33
|
hoveredAnnotationId?: string | null;
|
|
34
|
+
/** BCP-47 tag of the resource being analyzed — forwarded to the assist call so the LLM analyzes non-English source correctly. */
|
|
35
|
+
sourceLanguage?: string;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
/**
|
|
@@ -48,6 +50,7 @@ export function HighlightPanel({
|
|
|
48
50
|
scrollToAnnotationId,
|
|
49
51
|
onScrollCompleted,
|
|
50
52
|
hoveredAnnotationId,
|
|
53
|
+
sourceLanguage,
|
|
51
54
|
}: HighlightPanelProps) {
|
|
52
55
|
|
|
53
56
|
const t = useTranslations('HighlightPanel');
|
|
@@ -151,6 +154,7 @@ export function HighlightPanel({
|
|
|
151
154
|
annotationType="highlight"
|
|
152
155
|
isAssisting={isAssisting}
|
|
153
156
|
progress={progress}
|
|
157
|
+
sourceLanguage={sourceLanguage}
|
|
154
158
|
/>
|
|
155
159
|
)}
|
|
156
160
|
|
|
@@ -60,6 +60,11 @@ interface Props {
|
|
|
60
60
|
scrollToAnnotationId?: string | null;
|
|
61
61
|
onScrollCompleted?: () => void;
|
|
62
62
|
hoveredAnnotationId?: string | null;
|
|
63
|
+
|
|
64
|
+
/** User UI locale — stamped on the unresolved-reference body's `language` field. */
|
|
65
|
+
locale?: string;
|
|
66
|
+
/** BCP-47 tag of the resource being analyzed — fed into the prompt for source-aware analysis. */
|
|
67
|
+
sourceLanguage?: string;
|
|
63
68
|
}
|
|
64
69
|
|
|
65
70
|
/**
|
|
@@ -85,6 +90,8 @@ export function ReferencesPanel({
|
|
|
85
90
|
scrollToAnnotationId,
|
|
86
91
|
onScrollCompleted,
|
|
87
92
|
hoveredAnnotationId,
|
|
93
|
+
locale,
|
|
94
|
+
sourceLanguage,
|
|
88
95
|
}: Props) {
|
|
89
96
|
const t = useTranslations('ReferencesPanel');
|
|
90
97
|
const session = useObservable(useSemiont().activeSession$);
|
|
@@ -206,6 +213,10 @@ export function ReferencesPanel({
|
|
|
206
213
|
session?.client.mark.requestAssist('linking', {
|
|
207
214
|
entityTypes: selectedEntityTypes,
|
|
208
215
|
includeDescriptiveReferences,
|
|
216
|
+
// Body locale stamps the unresolved-reference body's `language`;
|
|
217
|
+
// sourceLanguage tunes the prompt for non-English source content.
|
|
218
|
+
language: locale,
|
|
219
|
+
sourceLanguage,
|
|
209
220
|
});
|
|
210
221
|
};
|
|
211
222
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import { useMemo } from 'react';
|
|
3
4
|
import type { Ref } from 'react';
|
|
4
5
|
import type { Annotation } from '@semiont/core';
|
|
5
6
|
import { getAnnotationExactText } from '@semiont/core';
|
|
6
7
|
import { getTagCategory, getTagSchemaId } from '@semiont/ontology';
|
|
7
|
-
import { getTagSchema } from '../../../lib/tag-schemas';
|
|
8
8
|
import { useSemiont } from '../../../session/SemiontProvider';
|
|
9
9
|
import { useObservable } from '../../../hooks/useObservable';
|
|
10
10
|
import { useHoverEmitter } from '../../../hooks/useHoverEmitter';
|
|
@@ -28,7 +28,18 @@ export function TagEntry({
|
|
|
28
28
|
const selectedText = getAnnotationExactText(tag);
|
|
29
29
|
const category = getTagCategory(tag);
|
|
30
30
|
const schemaId = getTagSchemaId(tag);
|
|
31
|
-
|
|
31
|
+
|
|
32
|
+
// Resolve the schema's display name from the per-KB tag-schema registry.
|
|
33
|
+
// The registry is runtime-populated (frame.addTagSchema); during the
|
|
34
|
+
// initial fetch the observable yields `undefined`, which we treat as
|
|
35
|
+
// "no schema name available yet" — render the category badge alone
|
|
36
|
+
// until the registry resolves.
|
|
37
|
+
const tagSchemas$ = useMemo(
|
|
38
|
+
() => session?.client.browse.tagSchemas() ?? null,
|
|
39
|
+
[session],
|
|
40
|
+
);
|
|
41
|
+
const schemas = useObservable(tagSchemas$);
|
|
42
|
+
const schema = schemaId && schemas ? schemas.find((s) => s.id === schemaId) ?? null : null;
|
|
32
43
|
|
|
33
44
|
return (
|
|
34
45
|
<div
|
|
@@ -9,7 +9,6 @@ import type { components, Selector } from '@semiont/core';
|
|
|
9
9
|
import { getTextPositionSelector, getTargetSelector } from '@semiont/core';
|
|
10
10
|
import { TagEntry } from './TagEntry';
|
|
11
11
|
import { PanelHeader } from './PanelHeader';
|
|
12
|
-
import { getAllTagSchemas } from '../../../lib/tag-schemas';
|
|
13
12
|
import './TaggingPanel.css';
|
|
14
13
|
|
|
15
14
|
import type { Annotation } from '@semiont/core';
|
|
@@ -48,6 +47,10 @@ interface TaggingPanelProps {
|
|
|
48
47
|
scrollToAnnotationId?: string | null;
|
|
49
48
|
onScrollCompleted?: () => void;
|
|
50
49
|
hoveredAnnotationId?: string | null;
|
|
50
|
+
/** User UI locale — stamped on the tagging body's `language` field. */
|
|
51
|
+
locale?: string;
|
|
52
|
+
/** BCP-47 tag of the resource being analyzed — fed into the prompt for source-aware analysis. */
|
|
53
|
+
sourceLanguage?: string;
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
/**
|
|
@@ -67,11 +70,39 @@ export function TaggingPanel({
|
|
|
67
70
|
scrollToAnnotationId,
|
|
68
71
|
onScrollCompleted,
|
|
69
72
|
hoveredAnnotationId,
|
|
73
|
+
locale,
|
|
74
|
+
sourceLanguage,
|
|
70
75
|
}: TaggingPanelProps) {
|
|
71
76
|
const t = useTranslations('TaggingPanel');
|
|
72
77
|
const session = useObservable(useSemiont().activeSession$);
|
|
73
|
-
|
|
78
|
+
|
|
79
|
+
// Subscribe to the per-KB tag-schema registry. Schemas are runtime-
|
|
80
|
+
// registered by the KB at session start (see frame.addTagSchema).
|
|
81
|
+
// During the initial load the observable yields `undefined` — render an
|
|
82
|
+
// empty schemas list and let the picker render no options until the
|
|
83
|
+
// first emission lands.
|
|
84
|
+
const tagSchemas$ = useMemo(
|
|
85
|
+
() => session?.client.browse.tagSchemas() ?? null,
|
|
86
|
+
[session],
|
|
87
|
+
);
|
|
88
|
+
const schemasObserved = useObservable(tagSchemas$);
|
|
89
|
+
const schemas = schemasObserved ?? [];
|
|
90
|
+
// True only AFTER the registry has resolved AND it's empty — distinct
|
|
91
|
+
// from the initial-loading state (`schemasObserved === undefined`),
|
|
92
|
+
// which renders nothing rather than an empty-state message.
|
|
93
|
+
const noSchemasRegistered = schemasObserved !== undefined && schemasObserved.length === 0;
|
|
94
|
+
|
|
95
|
+
const [selectedSchemaId, setSelectedSchemaId] = useState<string>('');
|
|
74
96
|
const [selectedCategories, setSelectedCategories] = useState<Set<string>>(new Set());
|
|
97
|
+
|
|
98
|
+
// Default the schema selection to the first registered schema once
|
|
99
|
+
// the registry resolves. We don't reset on schemas changing to avoid
|
|
100
|
+
// clobbering an explicit user choice.
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
if (!selectedSchemaId && schemas.length > 0) {
|
|
103
|
+
setSelectedSchemaId(schemas[0]!.id);
|
|
104
|
+
}
|
|
105
|
+
}, [schemas, selectedSchemaId]);
|
|
75
106
|
const [focusedAnnotationId, setFocusedAnnotationId] = useState<string | null>(null);
|
|
76
107
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
77
108
|
|
|
@@ -159,7 +190,6 @@ export function TaggingPanel({
|
|
|
159
190
|
// Pulse effect is handled by isHovered prop on TagEntry
|
|
160
191
|
}, [hoveredAnnotationId]);
|
|
161
192
|
|
|
162
|
-
const schemas = getAllTagSchemas();
|
|
163
193
|
const selectedSchema = schemas.find(s => s.id === selectedSchemaId);
|
|
164
194
|
|
|
165
195
|
const handleSchemaChange = (schemaId: string) => {
|
|
@@ -192,6 +222,10 @@ export function TaggingPanel({
|
|
|
192
222
|
session?.client.mark.requestAssist('tagging', {
|
|
193
223
|
schemaId: selectedSchemaId,
|
|
194
224
|
categories: Array.from(selectedCategories),
|
|
225
|
+
// Body locale stamps the tagging body's `language`; sourceLanguage
|
|
226
|
+
// tunes the prompt for non-English source content.
|
|
227
|
+
language: locale,
|
|
228
|
+
sourceLanguage,
|
|
195
229
|
});
|
|
196
230
|
setSelectedCategories(new Set()); // Reset after annotation
|
|
197
231
|
}
|
|
@@ -238,23 +272,32 @@ export function TaggingPanel({
|
|
|
238
272
|
</p>
|
|
239
273
|
</div>
|
|
240
274
|
|
|
275
|
+
{/* Empty-state — registry has resolved with no schemas. */}
|
|
276
|
+
{noSchemasRegistered && (
|
|
277
|
+
<p className="semiont-form__help" data-type="tag-no-schemas">
|
|
278
|
+
{t('noSchemas')}
|
|
279
|
+
</p>
|
|
280
|
+
)}
|
|
281
|
+
|
|
241
282
|
{/* Schema and Category Selection for Manual Tag */}
|
|
242
|
-
|
|
243
|
-
<
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
{
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
283
|
+
{!noSchemasRegistered && (
|
|
284
|
+
<div className="semiont-form-field">
|
|
285
|
+
<label className="semiont-form-field__label">
|
|
286
|
+
{t('selectSchema')}
|
|
287
|
+
</label>
|
|
288
|
+
<select
|
|
289
|
+
value={selectedSchemaId}
|
|
290
|
+
onChange={(e) => handleSchemaChange(e.target.value)}
|
|
291
|
+
className="semiont-select"
|
|
292
|
+
>
|
|
293
|
+
{schemas.map(schema => (
|
|
294
|
+
<option key={schema.id} value={schema.id}>
|
|
295
|
+
{schema.name}
|
|
296
|
+
</option>
|
|
297
|
+
))}
|
|
298
|
+
</select>
|
|
299
|
+
</div>
|
|
300
|
+
)}
|
|
258
301
|
|
|
259
302
|
{selectedSchema && (
|
|
260
303
|
<div className="semiont-form-field">
|
|
@@ -324,28 +367,37 @@ export function TaggingPanel({
|
|
|
324
367
|
<div className="semiont-assist-widget" data-assisting={isAssisting && progress ? 'true' : 'false'} data-type="tag">
|
|
325
368
|
{!isAssisting && !progress && (
|
|
326
369
|
<>
|
|
370
|
+
{/* Empty-state — registry has resolved with no schemas. */}
|
|
371
|
+
{noSchemasRegistered && (
|
|
372
|
+
<p className="semiont-form__help" data-type="tag-no-schemas">
|
|
373
|
+
{t('noSchemas')}
|
|
374
|
+
</p>
|
|
375
|
+
)}
|
|
376
|
+
|
|
327
377
|
{/* Schema Selector */}
|
|
328
|
-
|
|
329
|
-
<
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
{
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
378
|
+
{!noSchemasRegistered && (
|
|
379
|
+
<div className="semiont-form-field">
|
|
380
|
+
<label className="semiont-form-field__label">
|
|
381
|
+
{t('selectSchema')}
|
|
382
|
+
</label>
|
|
383
|
+
<select
|
|
384
|
+
value={selectedSchemaId}
|
|
385
|
+
onChange={(e) => handleSchemaChange(e.target.value)}
|
|
386
|
+
className="semiont-select"
|
|
387
|
+
>
|
|
388
|
+
{schemas.map(schema => (
|
|
389
|
+
<option key={schema.id} value={schema.id}>
|
|
390
|
+
{schema.name}
|
|
391
|
+
</option>
|
|
392
|
+
))}
|
|
393
|
+
</select>
|
|
394
|
+
{selectedSchema && (
|
|
395
|
+
<p className="semiont-form__help">
|
|
396
|
+
{selectedSchema.description}
|
|
397
|
+
</p>
|
|
398
|
+
)}
|
|
399
|
+
</div>
|
|
400
|
+
)}
|
|
349
401
|
|
|
350
402
|
{/* Category Selector */}
|
|
351
403
|
{selectedSchema && (
|
|
@@ -388,7 +440,7 @@ export function TaggingPanel({
|
|
|
388
440
|
style={{ display: 'flex', flexDirection: 'column', gap: '0.25rem' }}
|
|
389
441
|
>
|
|
390
442
|
<span style={{ fontWeight: 500 }}>
|
|
391
|
-
{
|
|
443
|
+
{category.name}
|
|
392
444
|
</span>
|
|
393
445
|
<span style={{ fontSize: 'var(--semiont-text-xs)', color: 'var(--semiont-text-secondary)' }}>
|
|
394
446
|
{category.description}
|
|
@@ -71,9 +71,16 @@ interface UnifiedAnnotationsPanelProps {
|
|
|
71
71
|
// Hover coordination (for bidirectional hover highlighting)
|
|
72
72
|
hoveredAnnotationId?: string | null;
|
|
73
73
|
|
|
74
|
-
// Locale for AI-generated text language
|
|
74
|
+
// Locale for AI-generated text language (annotation body locale)
|
|
75
75
|
locale?: string;
|
|
76
76
|
|
|
77
|
+
/**
|
|
78
|
+
* BCP-47 tag of the resource being analyzed (source-resource locale).
|
|
79
|
+
* Independent from `locale` — a German user can analyze a French source
|
|
80
|
+
* and get German bodies back. Fed into detection prompts.
|
|
81
|
+
*/
|
|
82
|
+
sourceLanguage?: string;
|
|
83
|
+
|
|
77
84
|
// Routing
|
|
78
85
|
Link: React.ComponentType<LinkComponentProps>;
|
|
79
86
|
routes: RouteBuilder;
|
|
@@ -243,6 +250,7 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
243
250
|
progress,
|
|
244
251
|
annotateMode: props.annotateMode,
|
|
245
252
|
locale: props.locale,
|
|
253
|
+
sourceLanguage: props.sourceLanguage,
|
|
246
254
|
scrollToAnnotationId: props.scrollToAnnotationId,
|
|
247
255
|
onScrollCompleted: props.onScrollCompleted,
|
|
248
256
|
hoveredAnnotationId: props.hoveredAnnotationId
|
|
@@ -268,6 +276,8 @@ export function UnifiedAnnotationsPanel(props: UnifiedAnnotationsPanelProps) {
|
|
|
268
276
|
scrollToAnnotationId={commonProps.scrollToAnnotationId}
|
|
269
277
|
onScrollCompleted={commonProps.onScrollCompleted}
|
|
270
278
|
hoveredAnnotationId={commonProps.hoveredAnnotationId}
|
|
279
|
+
locale={commonProps.locale}
|
|
280
|
+
sourceLanguage={commonProps.sourceLanguage}
|
|
271
281
|
allEntityTypes={props.allEntityTypes || []}
|
|
272
282
|
generatingReferenceId={props.generatingReferenceId}
|
|
273
283
|
referencedBy={props.referencedBy}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Triangulation test for the
|
|
2
|
+
* Triangulation test for the state unit → useObservable → prop → chip render chain.
|
|
3
3
|
*
|
|
4
4
|
* Written after an e2e failure (test 05) where `ReferencesPanel` rendered
|
|
5
5
|
* "No entity types available" even though the client provably received a
|
|
@@ -90,7 +90,7 @@ const renderWithBus = (ui: React.ReactElement) => {
|
|
|
90
90
|
return render(<SemiontWrapper>{ui}</SemiontWrapper>);
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
-
describe('Layer 5-6 —
|
|
93
|
+
describe('Layer 5-6 — state-unit observable → useObservable → ReferencesPanel chips', () => {
|
|
94
94
|
it('an observable seeded with [9 strings] renders 9 pending-reference chips', async () => {
|
|
95
95
|
const source$ = new BehaviorSubject<string[]>(NINE_TYPES);
|
|
96
96
|
renderWithBus(<ObservableHarness source$={source$} />);
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { screen } from '@testing-library/react';
|
|
3
|
+
import { render, screen } from '@testing-library/react';
|
|
4
4
|
import '@testing-library/jest-dom';
|
|
5
|
-
import {
|
|
5
|
+
import { of } from 'rxjs';
|
|
6
|
+
import { CacheObservable } from '@semiont/sdk';
|
|
7
|
+
import { renderWithProviders, createTestSemiontWrapper } from '../../../../test-utils';
|
|
6
8
|
import userEvent from '@testing-library/user-event';
|
|
7
|
-
import type { components } from '@semiont/core';
|
|
9
|
+
import type { components, TagSchema } from '@semiont/core';
|
|
8
10
|
|
|
9
11
|
import type { Annotation } from '@semiont/core';
|
|
10
12
|
|
|
@@ -23,21 +25,15 @@ vi.mock('@semiont/ontology', () => ({
|
|
|
23
25
|
getTagSchemaId: vi.fn(),
|
|
24
26
|
}));
|
|
25
27
|
|
|
26
|
-
// Mock tag-schemas
|
|
27
|
-
vi.mock('../../../../lib/tag-schemas', () => ({
|
|
28
|
-
getTagSchema: vi.fn(),
|
|
29
|
-
}));
|
|
30
|
-
|
|
31
28
|
import { getAnnotationExactText } from '@semiont/core';
|
|
32
29
|
import { getTagCategory, getTagSchemaId } from '@semiont/ontology';
|
|
33
|
-
import { getTagSchema } from '../../../../lib/tag-schemas';
|
|
34
30
|
import type { MockedFunction } from 'vitest';
|
|
35
31
|
import { TagEntry } from '../TagEntry';
|
|
36
32
|
|
|
37
33
|
const mockGetAnnotationExactText = getAnnotationExactText as MockedFunction<typeof getAnnotationExactText>;
|
|
38
34
|
const mockGetTagCategory = getTagCategory as MockedFunction<typeof getTagCategory>;
|
|
39
35
|
const mockGetTagSchemaId = getTagSchemaId as MockedFunction<typeof getTagSchemaId>;
|
|
40
|
-
|
|
36
|
+
|
|
41
37
|
|
|
42
38
|
const createMockTag = (overrides?: Partial<Annotation>): Annotation => ({
|
|
43
39
|
'@context': 'http://www.w3.org/ns/anno.jsonld',
|
|
@@ -76,7 +72,6 @@ describe('TagEntry', () => {
|
|
|
76
72
|
mockGetAnnotationExactText.mockReturnValue('Tagged text content');
|
|
77
73
|
mockGetTagCategory.mockReturnValue('Entity');
|
|
78
74
|
mockGetTagSchemaId.mockReturnValue(null);
|
|
79
|
-
mockGetTagSchema.mockReturnValue(null);
|
|
80
75
|
});
|
|
81
76
|
|
|
82
77
|
describe('Rendering', () => {
|
|
@@ -115,24 +110,36 @@ describe('TagEntry', () => {
|
|
|
115
110
|
|
|
116
111
|
it('should render schema name when available', () => {
|
|
117
112
|
mockGetTagSchemaId.mockReturnValue('schema-ner-v1');
|
|
118
|
-
|
|
113
|
+
const NER_SCHEMA: TagSchema = {
|
|
119
114
|
id: 'schema-ner-v1',
|
|
120
115
|
name: 'Named Entity Recognition',
|
|
116
|
+
description: 'NER',
|
|
121
117
|
domain: 'nlp',
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
118
|
+
tags: [],
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// Stub the cache to resolve immediately with the test schema —
|
|
122
|
+
// exercises the rendering path without round-tripping through the
|
|
123
|
+
// transport's HTTP plumbing.
|
|
124
|
+
const { SemiontWrapper, client } = createTestSemiontWrapper();
|
|
125
|
+
vi.spyOn(client.browse, 'tagSchemas').mockReturnValue(
|
|
126
|
+
CacheObservable.from(of([NER_SCHEMA]))
|
|
127
|
+
);
|
|
128
|
+
render(<TagEntry {...defaultProps} />, { wrapper: SemiontWrapper });
|
|
127
129
|
|
|
128
130
|
expect(screen.getByText('Named Entity Recognition')).toBeInTheDocument();
|
|
129
131
|
});
|
|
130
132
|
|
|
131
133
|
it('should not render schema name when schema is not found', () => {
|
|
132
134
|
mockGetTagSchemaId.mockReturnValue('unknown-schema');
|
|
133
|
-
mockGetTagSchema.mockReturnValue(null);
|
|
134
135
|
|
|
135
|
-
|
|
136
|
+
// Stub the cache to resolve to an empty list — the schema lookup
|
|
137
|
+
// misses, the schema-name `<span>` is not rendered.
|
|
138
|
+
const { SemiontWrapper, client } = createTestSemiontWrapper();
|
|
139
|
+
vi.spyOn(client.browse, 'tagSchemas').mockReturnValue(
|
|
140
|
+
CacheObservable.from(of([]))
|
|
141
|
+
);
|
|
142
|
+
const { container } = render(<TagEntry {...defaultProps} />, { wrapper: SemiontWrapper });
|
|
136
143
|
|
|
137
144
|
expect(container.querySelector('.semiont-annotation-entry__meta')).not.toBeInTheDocument();
|
|
138
145
|
});
|