@semiont/react-ui 0.4.20 → 0.4.21
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 +8 -5
- package/dist/{PdfAnnotationCanvas.client-CHDCGQBR.mjs → PdfAnnotationCanvas.client-6ZGFEN2N.mjs} +9 -13
- package/dist/PdfAnnotationCanvas.client-6ZGFEN2N.mjs.map +1 -0
- package/dist/TranslationManager-9Xj3MIWQ.d.mts +16 -0
- package/dist/chunk-KEDFYI6N.mjs +7788 -0
- package/dist/chunk-KEDFYI6N.mjs.map +1 -0
- package/dist/index.d.mts +171 -1140
- package/dist/index.mjs +3263 -13644
- package/dist/index.mjs.map +1 -1
- package/dist/test-utils.d.mts +46 -21
- package/dist/test-utils.mjs +2499 -87
- package/dist/test-utils.mjs.map +1 -1
- package/package.json +1 -2
- package/src/components/AnnotateReferencesProgressWidget.tsx +21 -28
- package/src/components/CodeMirrorRenderer.tsx +9 -11
- package/src/components/StatusDisplay.tsx +42 -16
- package/src/components/Toolbar.tsx +4 -4
- package/src/components/__tests__/AnnotateReferencesProgressWidget.test.tsx +34 -20
- package/src/components/__tests__/StatusDisplay.test.tsx +47 -64
- package/src/components/__tests__/Toolbar.test.tsx +4 -4
- package/src/components/annotation/AnnotateToolbar.tsx +8 -7
- package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +31 -77
- package/src/components/annotation-popups/__tests__/JsonLdView.test.tsx +0 -1
- package/src/components/image-annotation/AnnotationOverlay.tsx +12 -13
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +7 -7
- package/src/components/modals/PermissionDeniedModal.tsx +11 -11
- package/src/components/modals/ReferenceWizardModal.tsx +14 -18
- package/src/components/modals/ResourceSearchModal.tsx +10 -6
- package/src/components/modals/SearchModal.tsx +10 -6
- package/src/components/modals/SessionExpiredModal.tsx +11 -11
- package/src/components/modals/__tests__/PermissionDeniedModal.test.tsx +7 -7
- package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +10 -8
- package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +10 -7
- package/src/components/modals/__tests__/SessionExpiredModal.test.tsx +5 -5
- package/src/components/navigation/CollapsibleResourceNavigation.tsx +10 -10
- package/src/components/navigation/ObservableLink.tsx +6 -6
- package/src/components/navigation/SimpleNavigation.tsx +4 -4
- package/src/components/navigation/__tests__/ObservableLink.test.tsx +4 -4
- package/src/components/navigation/__tests__/SimpleNavigation.test.tsx +4 -4
- package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +9 -11
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +0 -1
- package/src/components/resource/AnnotateView.tsx +7 -6
- package/src/components/resource/AnnotationHistory.tsx +9 -12
- package/src/components/resource/BrowseView.tsx +8 -7
- package/src/components/resource/ResourceViewer.tsx +17 -25
- package/src/components/resource/__tests__/AnnotationHistory.test.tsx +54 -192
- package/src/components/resource/__tests__/BrowseView.test.tsx +34 -83
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +40 -31
- package/src/components/resource/panels/AssessmentEntry.tsx +5 -4
- package/src/components/resource/panels/AssessmentPanel.tsx +19 -15
- package/src/components/resource/panels/AssistSection.tsx +11 -13
- package/src/components/resource/panels/CollaborationPanel.tsx +29 -7
- package/src/components/resource/panels/CommentEntry.tsx +5 -4
- package/src/components/resource/panels/CommentsPanel.tsx +9 -11
- package/src/components/resource/panels/HighlightEntry.tsx +5 -4
- package/src/components/resource/panels/HighlightPanel.tsx +10 -11
- package/src/components/resource/panels/ReferenceEntry.tsx +8 -8
- package/src/components/resource/panels/ReferencesPanel.tsx +13 -12
- package/src/components/resource/panels/ResourceInfoPanel.tsx +7 -6
- package/src/components/resource/panels/TagEntry.tsx +5 -4
- package/src/components/resource/panels/TaggingPanel.tsx +10 -16
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +3 -2
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +18 -52
- package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +51 -20
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +18 -56
- package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +0 -1
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +4 -5
- package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +153 -0
- package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +51 -106
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +15 -47
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +15 -47
- package/src/components/settings/SettingsPanel.tsx +8 -8
- package/src/components/settings/__tests__/SettingsPanel.test.tsx +12 -12
- 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 +2 -6
- package/src/features/admin-security/components/AdminSecurityPage.tsx +1 -1
- package/src/features/admin-users/components/AdminUsersPage.tsx +1 -1
- package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -1
- package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -1
- package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +1 -1
- package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +1 -1
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +5 -3
- package/src/features/resource-compose/components/ResourceComposePage.tsx +5 -22
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +4 -3
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +1 -1
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +38 -45
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +123 -192
- package/dist/KnowledgeBaseSessionContext-BNNunwzO.d.mts +0 -175
- package/dist/PdfAnnotationCanvas.client-CHDCGQBR.mjs.map +0 -1
- package/dist/chunk-OZICDVH7.mjs +0 -62
- package/dist/chunk-OZICDVH7.mjs.map +0 -1
- package/dist/chunk-R4CCMFJH.mjs +0 -877
- package/dist/chunk-R4CCMFJH.mjs.map +0 -1
- package/dist/chunk-VN5NY4SN.mjs +0 -200
- package/dist/chunk-VN5NY4SN.mjs.map +0 -1
- package/src/components/modals/ProposeEntitiesModal.tsx +0 -179
- package/src/components/modals/__tests__/ProposeEntitiesModal.test.tsx +0 -129
- package/src/features/resource-viewer/__tests__/AnnotationCreationPending.test.tsx +0 -323
- package/src/features/resource-viewer/__tests__/AnnotationDeletionIntegration.test.tsx +0 -245
- package/src/features/resource-viewer/__tests__/AnnotationProgressDismissal.test.tsx +0 -303
- package/src/features/resource-viewer/__tests__/BindFlowIntegration.test.tsx +0 -150
- package/src/features/resource-viewer/__tests__/DetectionFlowBug.test.tsx +0 -243
- package/src/features/resource-viewer/__tests__/DetectionFlowIntegration.test.tsx +0 -383
- package/src/features/resource-viewer/__tests__/ResourceMutations.test.tsx +0 -299
- package/src/features/resource-viewer/__tests__/ToastNotifications.test.tsx +0 -186
- package/src/features/resource-viewer/__tests__/YieldFlowIntegration.test.tsx +0 -429
- package/src/features/resource-viewer/__tests__/annotation-progress-flow.test.tsx +0 -348
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@semiont/react-ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.21",
|
|
4
4
|
"description": "React components and hooks for Semiont",
|
|
5
5
|
"main": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.mts",
|
|
@@ -73,7 +73,6 @@
|
|
|
73
73
|
"test:split:coverage": "npm run test:coverage"
|
|
74
74
|
},
|
|
75
75
|
"peerDependencies": {
|
|
76
|
-
"@tanstack/react-query": "^5.0.0",
|
|
77
76
|
"react": "^18.0.0 || ^19.0.0",
|
|
78
77
|
"react-dom": "^18.0.0 || ^19.0.0"
|
|
79
78
|
},
|
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useTranslations } from '../contexts/TranslationContext';
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
4
|
+
import { useSemiont } from '../session/SemiontProvider';
|
|
5
|
+
import { useObservable } from '../hooks/useObservable';
|
|
6
6
|
import type { components } from '@semiont/core';
|
|
7
7
|
|
|
8
8
|
type Motivation = components['schemas']['Motivation'];
|
|
9
|
-
|
|
10
|
-
// Extended MarkProgress with optional request parameters
|
|
11
|
-
interface EnrichedMarkProgress extends MarkProgress {
|
|
12
|
-
requestParams?: Array<{ label: string; value: string }>;
|
|
13
|
-
}
|
|
9
|
+
type JobProgress = components['schemas']['JobProgress'];
|
|
14
10
|
|
|
15
11
|
interface AnnotateReferencesProgressWidgetProps {
|
|
16
|
-
progress:
|
|
12
|
+
progress: JobProgress | null;
|
|
17
13
|
annotationType?: Motivation | 'reference';
|
|
18
14
|
}
|
|
19
15
|
|
|
@@ -24,11 +20,11 @@ interface AnnotateReferencesProgressWidgetProps {
|
|
|
24
20
|
*/
|
|
25
21
|
export function AnnotateReferencesProgressWidget({ progress, annotationType = 'reference' }: AnnotateReferencesProgressWidgetProps) {
|
|
26
22
|
const t = useTranslations('ReferencesPanel');
|
|
27
|
-
const
|
|
23
|
+
const session = useObservable(useSemiont().activeSession$);
|
|
28
24
|
|
|
29
25
|
const handleCancel = () => {
|
|
30
26
|
// Emit event for job cancellation
|
|
31
|
-
|
|
27
|
+
session?.client.emit('job:cancel-requested', { jobType: 'annotation' });
|
|
32
28
|
};
|
|
33
29
|
|
|
34
30
|
if (!progress) return null;
|
|
@@ -36,7 +32,7 @@ export function AnnotateReferencesProgressWidget({ progress, annotationType = 'r
|
|
|
36
32
|
return (
|
|
37
33
|
<div
|
|
38
34
|
className="semiont-annotation-progress"
|
|
39
|
-
data-status={progress.
|
|
35
|
+
data-status={progress.stage}
|
|
40
36
|
data-type={annotationType}
|
|
41
37
|
>
|
|
42
38
|
{/* Header with pulsing sparkle */}
|
|
@@ -45,7 +41,7 @@ export function AnnotateReferencesProgressWidget({ progress, annotationType = 'r
|
|
|
45
41
|
<span className="semiont-annotation-sparkle">✨</span>
|
|
46
42
|
{t('annotationProgressTitle')}
|
|
47
43
|
</h3>
|
|
48
|
-
{progress.
|
|
44
|
+
{progress.stage !== 'complete' && (
|
|
49
45
|
<button
|
|
50
46
|
onClick={handleCancel}
|
|
51
47
|
className="semiont-annotation-cancel"
|
|
@@ -57,19 +53,16 @@ export function AnnotateReferencesProgressWidget({ progress, annotationType = 'r
|
|
|
57
53
|
</div>
|
|
58
54
|
|
|
59
55
|
{/* Request Parameters */}
|
|
60
|
-
{
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
<div className="semiont-annotation-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
</div>
|
|
71
|
-
);
|
|
72
|
-
})()}
|
|
56
|
+
{progress.requestParams && progress.requestParams.length > 0 && (
|
|
57
|
+
<div className="semiont-annotation-progress__params">
|
|
58
|
+
<div className="semiont-annotation-progress__params-title">Request Parameters:</div>
|
|
59
|
+
{progress.requestParams.map((param, idx) => (
|
|
60
|
+
<div key={idx} className="semiont-annotation-progress__param">
|
|
61
|
+
<span className="semiont-annotation-progress__param-label">{param.label}:</span> {param.value}
|
|
62
|
+
</div>
|
|
63
|
+
))}
|
|
64
|
+
</div>
|
|
65
|
+
)}
|
|
73
66
|
|
|
74
67
|
{/* Completed entity types log */}
|
|
75
68
|
{progress.completedEntityTypes && progress.completedEntityTypes.length > 0 && (
|
|
@@ -86,12 +79,12 @@ export function AnnotateReferencesProgressWidget({ progress, annotationType = 'r
|
|
|
86
79
|
|
|
87
80
|
{/* Status display with pulsing animation */}
|
|
88
81
|
<div className="semiont-annotation-progress__status">
|
|
89
|
-
{progress.
|
|
82
|
+
{progress.stage === 'complete' ? (
|
|
90
83
|
<div className="semiont-annotation-progress__message">
|
|
91
84
|
<span className="semiont-annotation-progress__icon">✅</span>
|
|
92
85
|
<span>{t('complete')}</span>
|
|
93
86
|
</div>
|
|
94
|
-
) : progress.
|
|
87
|
+
) : progress.stage === 'error' ? (
|
|
95
88
|
<div className="semiont-annotation-progress__message">
|
|
96
89
|
<span className="semiont-annotation-progress__icon">❌</span>
|
|
97
90
|
<span>{progress.message || t('failed')}</span>
|
|
@@ -102,7 +95,7 @@ export function AnnotateReferencesProgressWidget({ progress, annotationType = 'r
|
|
|
102
95
|
<span>{progress.message || (progress.currentEntityType ? t('current', { entityType: progress.currentEntityType }) : t('annotating'))}</span>
|
|
103
96
|
</div>
|
|
104
97
|
)}
|
|
105
|
-
{progress.currentEntityType && progress.
|
|
98
|
+
{progress.currentEntityType && progress.stage !== 'complete' && progress.stage !== 'error' && (
|
|
106
99
|
<div className="semiont-annotation-progress__details">
|
|
107
100
|
Processing: {progress.currentEntityType}
|
|
108
101
|
</div>
|
|
@@ -6,9 +6,7 @@ import { EditorState, RangeSetBuilder, StateField, StateEffect, Compartment } fr
|
|
|
6
6
|
import { markdown } from '@codemirror/lang-markdown';
|
|
7
7
|
import { ReferenceResolutionWidget, showWidgetPreview, hideWidgetPreview } from '../lib/codemirror-widgets';
|
|
8
8
|
import { scrollAnnotationIntoView } from '../lib/scroll-utils';
|
|
9
|
-
import { isReference } from '@semiont/api-client';
|
|
10
|
-
import type { EventBus } from "@semiont/core";
|
|
11
|
-
import { createHoverHandlers } from '../hooks/useBeckonFlow';
|
|
9
|
+
import { isReference, createHoverHandlers, type SemiontSession } from '@semiont/api-client';
|
|
12
10
|
import {
|
|
13
11
|
convertSegmentPositions,
|
|
14
12
|
computeAnnotationDecorations,
|
|
@@ -43,7 +41,7 @@ interface Props {
|
|
|
43
41
|
sourceView?: boolean; // If true, show raw source (no markdown rendering)
|
|
44
42
|
showLineNumbers?: boolean; // If true, show line numbers
|
|
45
43
|
enableWidgets?: boolean; // If true, show inline widgets (reference previews, entity badges)
|
|
46
|
-
|
|
44
|
+
session?: SemiontSession | null | undefined;
|
|
47
45
|
getTargetResourceName?: (resourceId: string) => string | undefined;
|
|
48
46
|
generatingReferenceId?: string | null; // ID of reference currently generating a document
|
|
49
47
|
hoverDelayMs: number; // Hover delay in milliseconds for accessibility
|
|
@@ -179,7 +177,7 @@ export function CodeMirrorRenderer({
|
|
|
179
177
|
sourceView = false,
|
|
180
178
|
showLineNumbers = false,
|
|
181
179
|
enableWidgets = false,
|
|
182
|
-
|
|
180
|
+
session,
|
|
183
181
|
getTargetResourceName,
|
|
184
182
|
generatingReferenceId,
|
|
185
183
|
hoverDelayMs
|
|
@@ -196,7 +194,7 @@ export function CodeMirrorRenderer({
|
|
|
196
194
|
// Index segments by annotation ID for O(1) click lookups
|
|
197
195
|
const segmentsByIdRef = useRef(new Map<string, TextSegment>());
|
|
198
196
|
const lineNumbersCompartment = useRef(new Compartment());
|
|
199
|
-
const
|
|
197
|
+
const sessionRef = useRef(session);
|
|
200
198
|
const getTargetResourceNameRef = useRef(getTargetResourceName);
|
|
201
199
|
|
|
202
200
|
// Update refs when they change
|
|
@@ -206,7 +204,7 @@ export function CodeMirrorRenderer({
|
|
|
206
204
|
if (s.annotation) segmentsById.set(s.annotation.id, s);
|
|
207
205
|
}
|
|
208
206
|
segmentsByIdRef.current = segmentsById;
|
|
209
|
-
|
|
207
|
+
sessionRef.current = session;
|
|
210
208
|
getTargetResourceNameRef.current = getTargetResourceName;
|
|
211
209
|
|
|
212
210
|
// Initialize CodeMirror view once
|
|
@@ -238,7 +236,7 @@ export function CodeMirrorRenderer({
|
|
|
238
236
|
EditorView.domEventHandlers({
|
|
239
237
|
click: (event, _view) => {
|
|
240
238
|
const target = event.target as HTMLElement;
|
|
241
|
-
if (
|
|
239
|
+
if (sessionRef.current && handleAnnotationClick(target, segmentsByIdRef.current, sessionRef.current)) {
|
|
242
240
|
event.preventDefault();
|
|
243
241
|
return true;
|
|
244
242
|
}
|
|
@@ -303,7 +301,7 @@ export function CodeMirrorRenderer({
|
|
|
303
301
|
const container = view.dom;
|
|
304
302
|
|
|
305
303
|
const { handleMouseEnter, handleMouseLeave, cleanup: cleanupHover } = createHoverHandlers(
|
|
306
|
-
(annotationId) =>
|
|
304
|
+
(annotationId) => sessionRef.current?.client.emit('beckon:hover', { annotationId }),
|
|
307
305
|
hoverDelayMs
|
|
308
306
|
);
|
|
309
307
|
|
|
@@ -329,8 +327,8 @@ export function CodeMirrorRenderer({
|
|
|
329
327
|
e.preventDefault();
|
|
330
328
|
e.stopPropagation();
|
|
331
329
|
|
|
332
|
-
if (
|
|
333
|
-
dispatchWidgetClick(result,
|
|
330
|
+
if (sessionRef.current) {
|
|
331
|
+
dispatchWidgetClick(result, sessionRef.current);
|
|
334
332
|
}
|
|
335
333
|
};
|
|
336
334
|
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { useSemiont } from '../session/SemiontProvider';
|
|
5
|
+
import { useObservable } from '../hooks/useObservable';
|
|
4
6
|
import './StatusDisplay.css';
|
|
5
7
|
|
|
6
8
|
interface StatusDisplayProps {
|
|
@@ -9,36 +11,61 @@ interface StatusDisplayProps {
|
|
|
9
11
|
hasValidBackendToken?: boolean;
|
|
10
12
|
}
|
|
11
13
|
|
|
14
|
+
interface StatusData {
|
|
15
|
+
status: string;
|
|
16
|
+
version: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
12
19
|
export function StatusDisplay({
|
|
13
20
|
isFullyAuthenticated = false,
|
|
14
21
|
isAuthenticated = false,
|
|
15
22
|
hasValidBackendToken = false
|
|
16
23
|
}: StatusDisplayProps) {
|
|
17
|
-
const
|
|
18
|
-
const
|
|
24
|
+
const semiont = useObservable(useSemiont().activeSession$)?.client;
|
|
25
|
+
const [data, setData] = useState<StatusData | null>(null);
|
|
26
|
+
const [loading, setLoading] = useState(true);
|
|
27
|
+
const [error, setError] = useState<Error | null>(null);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
if (!semiont) { setLoading(false); return; }
|
|
31
|
+
|
|
32
|
+
const fetchStatus = () => {
|
|
33
|
+
semiont.getStatus()
|
|
34
|
+
.then((result) => {
|
|
35
|
+
setData(result as StatusData);
|
|
36
|
+
setError(null);
|
|
37
|
+
setLoading(false);
|
|
38
|
+
})
|
|
39
|
+
.catch((err) => {
|
|
40
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
41
|
+
setLoading(false);
|
|
42
|
+
});
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
fetchStatus();
|
|
46
|
+
const interval = setInterval(fetchStatus, 30000);
|
|
47
|
+
return () => clearInterval(interval);
|
|
48
|
+
}, [semiont]);
|
|
19
49
|
|
|
20
50
|
const getStatusContent = () => {
|
|
21
|
-
// Check for users who are logged in but missing backend token (old sessions)
|
|
22
51
|
if (isAuthenticated && !hasValidBackendToken) {
|
|
23
52
|
return '🚀 Frontend Status: Ready • Backend: Please sign out and sign in again to reconnect';
|
|
24
53
|
}
|
|
25
54
|
|
|
26
|
-
// If user is not authenticated at all, show appropriate message
|
|
27
55
|
if (!isFullyAuthenticated) {
|
|
28
56
|
return '🚀 Frontend Status: Ready • Backend: Authentication required';
|
|
29
57
|
}
|
|
30
58
|
|
|
31
|
-
if (
|
|
32
|
-
return `🚀 Frontend Status: Ready • Backend: ${
|
|
59
|
+
if (data) {
|
|
60
|
+
return `🚀 Frontend Status: Ready • Backend: ${data.status} (v${data.version})`;
|
|
33
61
|
}
|
|
34
62
|
|
|
35
|
-
if (
|
|
63
|
+
if (loading) {
|
|
36
64
|
return '🚀 Frontend Status: Ready • Backend: Connecting...';
|
|
37
65
|
}
|
|
38
66
|
|
|
39
|
-
if (
|
|
40
|
-
|
|
41
|
-
const errorMessage = status.error instanceof Error ? status.error.message : String(status.error);
|
|
67
|
+
if (error) {
|
|
68
|
+
const errorMessage = error.message;
|
|
42
69
|
if (errorMessage.includes('401') || errorMessage.includes('Unauthorized')) {
|
|
43
70
|
return '🚀 Frontend Status: Ready • Backend: Please sign out and sign in again';
|
|
44
71
|
}
|
|
@@ -49,7 +76,6 @@ export function StatusDisplay({
|
|
|
49
76
|
};
|
|
50
77
|
|
|
51
78
|
const getStatusType = (): 'warning' | 'info' | 'success' | 'loading' | 'error' => {
|
|
52
|
-
// Check for users who need to re-authenticate
|
|
53
79
|
if (isAuthenticated && !hasValidBackendToken) {
|
|
54
80
|
return 'warning';
|
|
55
81
|
}
|
|
@@ -58,11 +84,11 @@ export function StatusDisplay({
|
|
|
58
84
|
return 'info';
|
|
59
85
|
}
|
|
60
86
|
|
|
61
|
-
if (
|
|
87
|
+
if (data) {
|
|
62
88
|
return 'success';
|
|
63
89
|
}
|
|
64
90
|
|
|
65
|
-
if (
|
|
91
|
+
if (loading) {
|
|
66
92
|
return 'loading';
|
|
67
93
|
}
|
|
68
94
|
|
|
@@ -85,7 +111,7 @@ export function StatusDisplay({
|
|
|
85
111
|
<p className="semiont-status-hint">
|
|
86
112
|
Sign in to view backend status
|
|
87
113
|
</p>
|
|
88
|
-
) :
|
|
114
|
+
) : error ? (
|
|
89
115
|
<p className="semiont-status-hint semiont-status-error-hint" role="alert">
|
|
90
116
|
<span className="sr-only">Error: </span>
|
|
91
117
|
Check that the backend server is running and accessible
|
|
@@ -93,4 +119,4 @@ export function StatusDisplay({
|
|
|
93
119
|
) : null}
|
|
94
120
|
</section>
|
|
95
121
|
);
|
|
96
|
-
}
|
|
122
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import { useTranslations } from '../contexts/TranslationContext';
|
|
4
|
-
import {
|
|
4
|
+
import { useSemiont } from '../session/SemiontProvider';
|
|
5
5
|
import './toolbar/Toolbar.css';
|
|
6
6
|
|
|
7
7
|
type ToolbarContext = 'document' | 'simple';
|
|
@@ -17,7 +17,7 @@ interface Props<T extends string = string> {
|
|
|
17
17
|
/**
|
|
18
18
|
* Toolbar component for panel navigation
|
|
19
19
|
*
|
|
20
|
-
* @emits
|
|
20
|
+
* @emits panel:toggle - Toggle panel visibility. Payload: { panel: string }
|
|
21
21
|
*/
|
|
22
22
|
export function Toolbar<T extends string = string>({
|
|
23
23
|
context,
|
|
@@ -25,10 +25,10 @@ export function Toolbar<T extends string = string>({
|
|
|
25
25
|
isArchived = false
|
|
26
26
|
}: Props<T>) {
|
|
27
27
|
const t = useTranslations('Toolbar');
|
|
28
|
-
const
|
|
28
|
+
const semiont = useSemiont();
|
|
29
29
|
|
|
30
30
|
const handlePanelToggle = (panel: string) => {
|
|
31
|
-
|
|
31
|
+
semiont.emit('panel:toggle', { panel });
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
return (
|
|
@@ -4,7 +4,9 @@ import { screen, fireEvent } from '@testing-library/react';
|
|
|
4
4
|
import '@testing-library/jest-dom';
|
|
5
5
|
import { renderWithProviders } from '../../test-utils';
|
|
6
6
|
import { AnnotateReferencesProgressWidget } from '../AnnotateReferencesProgressWidget';
|
|
7
|
-
import type {
|
|
7
|
+
import type { components } from '@semiont/core';
|
|
8
|
+
|
|
9
|
+
type JobProgress = components['schemas']['JobProgress'];
|
|
8
10
|
|
|
9
11
|
describe('AnnotateReferencesProgressWidget', () => {
|
|
10
12
|
it('returns null when progress is null', () => {
|
|
@@ -15,9 +17,10 @@ describe('AnnotateReferencesProgressWidget', () => {
|
|
|
15
17
|
expect(container.firstChild).toBeNull();
|
|
16
18
|
});
|
|
17
19
|
|
|
18
|
-
it('renders progress with
|
|
19
|
-
const progress:
|
|
20
|
-
|
|
20
|
+
it('renders progress with stage message', () => {
|
|
21
|
+
const progress: JobProgress = {
|
|
22
|
+
stage: 'in-progress',
|
|
23
|
+
percentage: 50,
|
|
21
24
|
message: 'Processing entities...',
|
|
22
25
|
};
|
|
23
26
|
|
|
@@ -29,8 +32,9 @@ describe('AnnotateReferencesProgressWidget', () => {
|
|
|
29
32
|
});
|
|
30
33
|
|
|
31
34
|
it('shows cancel button when not complete', () => {
|
|
32
|
-
const progress:
|
|
33
|
-
|
|
35
|
+
const progress: JobProgress = {
|
|
36
|
+
stage: 'in-progress',
|
|
37
|
+
percentage: 30,
|
|
34
38
|
message: 'Working...',
|
|
35
39
|
};
|
|
36
40
|
|
|
@@ -43,8 +47,10 @@ describe('AnnotateReferencesProgressWidget', () => {
|
|
|
43
47
|
});
|
|
44
48
|
|
|
45
49
|
it('hides cancel button when complete', () => {
|
|
46
|
-
const progress:
|
|
47
|
-
|
|
50
|
+
const progress: JobProgress = {
|
|
51
|
+
stage: 'complete',
|
|
52
|
+
percentage: 100,
|
|
53
|
+
message: '',
|
|
48
54
|
};
|
|
49
55
|
|
|
50
56
|
renderWithProviders(
|
|
@@ -56,8 +62,9 @@ describe('AnnotateReferencesProgressWidget', () => {
|
|
|
56
62
|
|
|
57
63
|
it('emits job:cancel-requested on cancel click', () => {
|
|
58
64
|
const handler = vi.fn();
|
|
59
|
-
const progress:
|
|
60
|
-
|
|
65
|
+
const progress: JobProgress = {
|
|
66
|
+
stage: 'in-progress',
|
|
67
|
+
percentage: 40,
|
|
61
68
|
message: 'Working...',
|
|
62
69
|
};
|
|
63
70
|
|
|
@@ -77,8 +84,10 @@ describe('AnnotateReferencesProgressWidget', () => {
|
|
|
77
84
|
});
|
|
78
85
|
|
|
79
86
|
it('renders completed entity types', () => {
|
|
80
|
-
const progress:
|
|
81
|
-
|
|
87
|
+
const progress: JobProgress = {
|
|
88
|
+
stage: 'in-progress',
|
|
89
|
+
percentage: 60,
|
|
90
|
+
message: '',
|
|
82
91
|
completedEntityTypes: [
|
|
83
92
|
{ entityType: 'Person', foundCount: 5 },
|
|
84
93
|
{ entityType: 'Organization', foundCount: 3 },
|
|
@@ -96,9 +105,11 @@ describe('AnnotateReferencesProgressWidget', () => {
|
|
|
96
105
|
expect(foundLabels).toHaveLength(2);
|
|
97
106
|
});
|
|
98
107
|
|
|
99
|
-
it('shows complete icon for complete
|
|
100
|
-
const progress:
|
|
101
|
-
|
|
108
|
+
it('shows complete icon for complete stage', () => {
|
|
109
|
+
const progress: JobProgress = {
|
|
110
|
+
stage: 'complete',
|
|
111
|
+
percentage: 100,
|
|
112
|
+
message: '',
|
|
102
113
|
};
|
|
103
114
|
|
|
104
115
|
const { container } = renderWithProviders(
|
|
@@ -109,9 +120,10 @@ describe('AnnotateReferencesProgressWidget', () => {
|
|
|
109
120
|
expect(screen.getByText('ReferencesPanel.complete')).toBeInTheDocument();
|
|
110
121
|
});
|
|
111
122
|
|
|
112
|
-
it('shows error message for error
|
|
113
|
-
const progress:
|
|
114
|
-
|
|
123
|
+
it('shows error message for error stage', () => {
|
|
124
|
+
const progress: JobProgress = {
|
|
125
|
+
stage: 'error',
|
|
126
|
+
percentage: 0,
|
|
115
127
|
message: 'Something went wrong',
|
|
116
128
|
};
|
|
117
129
|
|
|
@@ -124,8 +136,10 @@ describe('AnnotateReferencesProgressWidget', () => {
|
|
|
124
136
|
});
|
|
125
137
|
|
|
126
138
|
it('shows current entity type processing details', () => {
|
|
127
|
-
const progress:
|
|
128
|
-
|
|
139
|
+
const progress: JobProgress = {
|
|
140
|
+
stage: 'in-progress',
|
|
141
|
+
percentage: 50,
|
|
142
|
+
message: '',
|
|
129
143
|
currentEntityType: 'Location',
|
|
130
144
|
};
|
|
131
145
|
|
|
@@ -1,42 +1,35 @@
|
|
|
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 { screen, waitFor } from '@testing-library/react';
|
|
4
|
+
import { BehaviorSubject } from 'rxjs';
|
|
4
5
|
import '@testing-library/jest-dom';
|
|
5
6
|
import { renderWithProviders } from '../../test-utils';
|
|
6
|
-
|
|
7
|
-
// Mock api-hooks
|
|
8
|
-
vi.mock('../../lib/api-hooks', () => ({
|
|
9
|
-
useHealth: vi.fn(),
|
|
10
|
-
}));
|
|
11
|
-
|
|
12
|
-
import { useHealth } from '../../lib/api-hooks';
|
|
13
|
-
import type { MockedFunction } from 'vitest';
|
|
14
7
|
import { StatusDisplay } from '../StatusDisplay';
|
|
15
8
|
|
|
16
|
-
|
|
9
|
+
let mockGetStatus: ReturnType<typeof vi.fn>;
|
|
10
|
+
const stableMockClient = {
|
|
11
|
+
get getStatus() { return mockGetStatus; },
|
|
12
|
+
};
|
|
13
|
+
const stableMockSession = { client: stableMockClient };
|
|
14
|
+
const stableActiveSession$ = new BehaviorSubject<any>(stableMockSession);
|
|
15
|
+
const stableMockBrowser = { activeSession$: stableActiveSession$ };
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
vi.mock('../../session/SemiontProvider', async (importOriginal) => {
|
|
18
|
+
const actual = await importOriginal<typeof import('../../session/SemiontProvider')>();
|
|
19
19
|
return {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
},
|
|
23
|
-
status: {
|
|
24
|
-
useQuery: vi.fn().mockReturnValue({
|
|
25
|
-
data: queryResult.data ?? undefined,
|
|
26
|
-
isLoading: queryResult.isLoading ?? false,
|
|
27
|
-
error: queryResult.error ?? undefined,
|
|
28
|
-
}),
|
|
29
|
-
},
|
|
20
|
+
...actual,
|
|
21
|
+
useSemiont: () => stableMockBrowser,
|
|
30
22
|
};
|
|
31
|
-
}
|
|
23
|
+
});
|
|
32
24
|
|
|
33
25
|
describe('StatusDisplay', () => {
|
|
34
26
|
beforeEach(() => {
|
|
35
27
|
vi.clearAllMocks();
|
|
28
|
+
mockGetStatus = vi.fn();
|
|
36
29
|
});
|
|
37
30
|
|
|
38
31
|
it('should show "Authentication required" when not fully authenticated', () => {
|
|
39
|
-
|
|
32
|
+
mockGetStatus.mockResolvedValue({ status: 'healthy', version: '1.0.0' });
|
|
40
33
|
|
|
41
34
|
renderWithProviders(
|
|
42
35
|
<StatusDisplay isFullyAuthenticated={false} isAuthenticated={false} />
|
|
@@ -45,55 +38,45 @@ describe('StatusDisplay', () => {
|
|
|
45
38
|
expect(screen.getByText(/Authentication required/)).toBeInTheDocument();
|
|
46
39
|
});
|
|
47
40
|
|
|
48
|
-
it('should show
|
|
49
|
-
|
|
41
|
+
it('should show status when data is available', async () => {
|
|
42
|
+
mockGetStatus.mockResolvedValue({ status: 'healthy', version: '1.2.3' });
|
|
50
43
|
|
|
51
44
|
renderWithProviders(
|
|
52
45
|
<StatusDisplay isFullyAuthenticated={true} isAuthenticated={true} hasValidBackendToken={true} />
|
|
53
46
|
);
|
|
54
47
|
|
|
55
|
-
|
|
48
|
+
await waitFor(() => {
|
|
49
|
+
expect(screen.getByText(/healthy/)).toBeInTheDocument();
|
|
50
|
+
expect(screen.getByText(/v1\.2\.3/)).toBeInTheDocument();
|
|
51
|
+
});
|
|
56
52
|
});
|
|
57
53
|
|
|
58
|
-
it('should show
|
|
59
|
-
|
|
60
|
-
data: { status: 'healthy', version: '1.2.3' },
|
|
61
|
-
}) as ReturnType<typeof useHealth>);
|
|
54
|
+
it('should show "Connection failed" on error', async () => {
|
|
55
|
+
mockGetStatus.mockRejectedValue(new Error('Network error'));
|
|
62
56
|
|
|
63
57
|
renderWithProviders(
|
|
64
58
|
<StatusDisplay isFullyAuthenticated={true} isAuthenticated={true} hasValidBackendToken={true} />
|
|
65
59
|
);
|
|
66
60
|
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
await waitFor(() => {
|
|
62
|
+
expect(screen.getByText(/Connection failed/)).toBeInTheDocument();
|
|
63
|
+
});
|
|
69
64
|
});
|
|
70
65
|
|
|
71
|
-
it('should show
|
|
72
|
-
|
|
73
|
-
error: new Error('Network error'),
|
|
74
|
-
}) as ReturnType<typeof useHealth>);
|
|
66
|
+
it('should show re-login message for 401 errors', async () => {
|
|
67
|
+
mockGetStatus.mockRejectedValue(new Error('401 Unauthorized'));
|
|
75
68
|
|
|
76
69
|
renderWithProviders(
|
|
77
70
|
<StatusDisplay isFullyAuthenticated={true} isAuthenticated={true} hasValidBackendToken={true} />
|
|
78
71
|
);
|
|
79
72
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
it('should show re-login message for 401 errors', () => {
|
|
84
|
-
mockUseHealth.mockReturnValue(createMockHealth({
|
|
85
|
-
error: new Error('401 Unauthorized'),
|
|
86
|
-
}) as ReturnType<typeof useHealth>);
|
|
87
|
-
|
|
88
|
-
renderWithProviders(
|
|
89
|
-
<StatusDisplay isFullyAuthenticated={true} isAuthenticated={true} hasValidBackendToken={true} />
|
|
90
|
-
);
|
|
91
|
-
|
|
92
|
-
expect(screen.getByText(/sign out and sign in again/i)).toBeInTheDocument();
|
|
73
|
+
await waitFor(() => {
|
|
74
|
+
expect(screen.getByText(/sign out and sign in again/i)).toBeInTheDocument();
|
|
75
|
+
});
|
|
93
76
|
});
|
|
94
77
|
|
|
95
78
|
it('should show warning for authenticated users missing backend token', () => {
|
|
96
|
-
|
|
79
|
+
mockGetStatus.mockResolvedValue(undefined);
|
|
97
80
|
|
|
98
81
|
renderWithProviders(
|
|
99
82
|
<StatusDisplay isFullyAuthenticated={false} isAuthenticated={true} hasValidBackendToken={false} />
|
|
@@ -103,7 +86,7 @@ describe('StatusDisplay', () => {
|
|
|
103
86
|
});
|
|
104
87
|
|
|
105
88
|
it('should have role="status"', () => {
|
|
106
|
-
|
|
89
|
+
mockGetStatus.mockResolvedValue(undefined);
|
|
107
90
|
|
|
108
91
|
renderWithProviders(
|
|
109
92
|
<StatusDisplay isFullyAuthenticated={false} isAuthenticated={false} />
|
|
@@ -113,7 +96,7 @@ describe('StatusDisplay', () => {
|
|
|
113
96
|
});
|
|
114
97
|
|
|
115
98
|
it('should have aria-live="polite"', () => {
|
|
116
|
-
|
|
99
|
+
mockGetStatus.mockResolvedValue(undefined);
|
|
117
100
|
|
|
118
101
|
renderWithProviders(
|
|
119
102
|
<StatusDisplay isFullyAuthenticated={false} isAuthenticated={false} />
|
|
@@ -124,7 +107,7 @@ describe('StatusDisplay', () => {
|
|
|
124
107
|
});
|
|
125
108
|
|
|
126
109
|
it('should show sign-in hint when not authenticated', () => {
|
|
127
|
-
|
|
110
|
+
mockGetStatus.mockResolvedValue(undefined);
|
|
128
111
|
|
|
129
112
|
renderWithProviders(
|
|
130
113
|
<StatusDisplay isFullyAuthenticated={false} isAuthenticated={false} />
|
|
@@ -133,28 +116,28 @@ describe('StatusDisplay', () => {
|
|
|
133
116
|
expect(screen.getByText('Sign in to view backend status')).toBeInTheDocument();
|
|
134
117
|
});
|
|
135
118
|
|
|
136
|
-
it('should show error hint when there is an error', () => {
|
|
137
|
-
|
|
138
|
-
error: new Error('Connection refused'),
|
|
139
|
-
}) as ReturnType<typeof useHealth>);
|
|
119
|
+
it('should show error hint when there is an error', async () => {
|
|
120
|
+
mockGetStatus.mockRejectedValue(new Error('Connection refused'));
|
|
140
121
|
|
|
141
122
|
renderWithProviders(
|
|
142
123
|
<StatusDisplay isFullyAuthenticated={true} isAuthenticated={true} hasValidBackendToken={true} />
|
|
143
124
|
);
|
|
144
125
|
|
|
145
|
-
|
|
126
|
+
await waitFor(() => {
|
|
127
|
+
expect(screen.getByText(/Check that the backend server is running/)).toBeInTheDocument();
|
|
128
|
+
});
|
|
146
129
|
});
|
|
147
130
|
|
|
148
|
-
it('should set data-status attribute based on state', () => {
|
|
149
|
-
|
|
150
|
-
data: { status: 'healthy', version: '1.0.0' },
|
|
151
|
-
}) as ReturnType<typeof useHealth>);
|
|
131
|
+
it('should set data-status attribute based on state', async () => {
|
|
132
|
+
mockGetStatus.mockResolvedValue({ status: 'healthy', version: '1.0.0' });
|
|
152
133
|
|
|
153
134
|
renderWithProviders(
|
|
154
135
|
<StatusDisplay isFullyAuthenticated={true} isAuthenticated={true} hasValidBackendToken={true} />
|
|
155
136
|
);
|
|
156
137
|
|
|
157
|
-
|
|
158
|
-
|
|
138
|
+
await waitFor(() => {
|
|
139
|
+
const status = screen.getByRole('status');
|
|
140
|
+
expect(status).toHaveAttribute('data-status', 'success');
|
|
141
|
+
});
|
|
159
142
|
});
|
|
160
143
|
});
|