@semiont/react-ui 0.4.20 → 0.4.22
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-5QESNO5H.mjs} +13 -16
- package/dist/PdfAnnotationCanvas.client-5QESNO5H.mjs.map +1 -0
- package/dist/TranslationManager-9Xj3MIWQ.d.mts +16 -0
- package/dist/chunk-4NOUO3W6.mjs +7788 -0
- package/dist/chunk-4NOUO3W6.mjs.map +1 -0
- package/dist/index.d.mts +212 -1206
- package/dist/index.mjs +3332 -13712
- package/dist/index.mjs.map +1 -1
- package/dist/test-utils.d.mts +48 -21
- package/dist/test-utils.mjs +2505 -87
- package/dist/test-utils.mjs.map +1 -1
- package/package.json +2 -2
- package/src/components/AnnotateReferencesProgressWidget.tsx +21 -28
- package/src/components/CodeMirrorRenderer.tsx +12 -12
- package/src/components/LiveRegion.tsx +1 -2
- 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 +50 -65
- package/src/components/__tests__/Toolbar.test.tsx +4 -4
- package/src/components/annotation/AnnotateToolbar.tsx +8 -9
- package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +31 -77
- package/src/components/annotation-popups/JsonLdView.tsx +1 -2
- package/src/components/annotation-popups/__tests__/JsonLdView.test.tsx +1 -2
- package/src/components/image-annotation/AnnotationOverlay.tsx +15 -18
- package/src/components/image-annotation/SvgDrawingCanvas.tsx +12 -17
- package/src/components/modals/ConfigureGenerationStep.tsx +1 -2
- package/src/components/modals/PermissionDeniedModal.tsx +11 -11
- package/src/components/modals/ReferenceWizardModal.tsx +14 -18
- package/src/components/modals/ResourceSearchModal.tsx +12 -8
- package/src/components/modals/SearchModal.tsx +11 -6
- package/src/components/modals/SearchResultsStep.tsx +1 -3
- 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.accessibility.test.tsx +6 -2
- package/src/components/modals/__tests__/SearchModal.basic.test.tsx +6 -2
- package/src/components/modals/__tests__/SearchModal.keyboard.test.tsx +6 -2
- package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +10 -7
- package/src/components/modals/__tests__/SearchModal.visual.test.tsx +6 -2
- 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 +15 -18
- package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +1 -2
- package/src/components/resource/AnnotateView.tsx +8 -10
- package/src/components/resource/AnnotationHistory.tsx +9 -12
- package/src/components/resource/BrowseView.tsx +11 -8
- package/src/components/resource/ResourceViewer.tsx +22 -34
- package/src/components/resource/__tests__/AnnotationHistory.test.tsx +54 -192
- package/src/components/resource/__tests__/BrowseView.test.tsx +38 -87
- package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +41 -31
- package/src/components/resource/__tests__/event-formatting.test.ts +6 -2
- package/src/components/resource/event-formatting.ts +2 -3
- package/src/components/resource/panels/AssessmentEntry.tsx +7 -8
- package/src/components/resource/panels/AssessmentPanel.tsx +21 -17
- package/src/components/resource/panels/AssistSection.tsx +15 -21
- package/src/components/resource/panels/CollaborationPanel.tsx +29 -7
- package/src/components/resource/panels/CommentEntry.tsx +7 -8
- package/src/components/resource/panels/CommentsPanel.tsx +11 -13
- package/src/components/resource/panels/HighlightEntry.tsx +7 -8
- package/src/components/resource/panels/HighlightPanel.tsx +12 -13
- package/src/components/resource/panels/ReferenceEntry.tsx +13 -15
- package/src/components/resource/panels/ReferencesPanel.tsx +17 -19
- package/src/components/resource/panels/ResourceInfoPanel.tsx +8 -7
- package/src/components/resource/panels/StatisticsPanel.tsx +2 -3
- package/src/components/resource/panels/TagEntry.tsx +7 -8
- package/src/components/resource/panels/TaggingPanel.tsx +14 -23
- package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +4 -3
- package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +22 -57
- package/src/components/resource/panels/__tests__/CollaborationPanel.test.tsx +51 -20
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +22 -61
- package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +1 -2
- package/src/components/resource/panels/__tests__/ReferenceEntry.test.tsx +7 -8
- 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 +28 -53
- package/src/components/resource/panels/__tests__/StatisticsPanel.test.tsx +3 -3
- package/src/components/resource/panels/__tests__/TagEntry.test.tsx +4 -4
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +19 -52
- package/src/components/settings/SettingsPanel.tsx +9 -9
- package/src/components/settings/__tests__/SettingsPanel.test.tsx +15 -15
- package/src/features/admin-devops/components/AdminDevOpsPage.tsx +1 -2
- package/src/features/admin-exchange/components/AdminExchangePage.tsx +1 -1
- package/src/features/admin-exchange/components/ImportCard.tsx +2 -7
- package/src/features/admin-security/components/AdminSecurityPage.tsx +1 -2
- package/src/features/admin-users/components/AdminUsersPage.tsx +1 -1
- package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -2
- package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -2
- 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 +6 -22
- package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +4 -3
- package/src/features/resource-discovery/components/ResourceCard.tsx +1 -2
- package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +3 -4
- package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +37 -45
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +129 -197
- 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
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import type { Ref } from 'react';
|
|
4
|
-
import type {
|
|
5
|
-
import { getAnnotationExactText } from '@semiont/
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
type Annotation = components['schemas']['Annotation'];
|
|
4
|
+
import type { Annotation } from '@semiont/core';
|
|
5
|
+
import { getAnnotationExactText } from '@semiont/core';
|
|
6
|
+
import { useSemiont } from '../../../session/SemiontProvider';
|
|
7
|
+
import { useObservable } from '../../../hooks/useObservable';
|
|
8
|
+
import { useHoverEmitter } from '../../../hooks/useHoverEmitter';
|
|
10
9
|
|
|
11
10
|
// W3C Annotation TextualBody type
|
|
12
11
|
interface TextualBody {
|
|
@@ -74,7 +73,7 @@ export function AssessmentEntry({
|
|
|
74
73
|
isHovered = false,
|
|
75
74
|
ref,
|
|
76
75
|
}: AssessmentEntryProps) {
|
|
77
|
-
const
|
|
76
|
+
const session = useObservable(useSemiont().activeSession$);
|
|
78
77
|
const hoverProps = useHoverEmitter(assessment.id);
|
|
79
78
|
|
|
80
79
|
const selectedText = getAnnotationExactText(assessment);
|
|
@@ -87,7 +86,7 @@ export function AssessmentEntry({
|
|
|
87
86
|
data-type="assessment"
|
|
88
87
|
data-focused={isFocused ? 'true' : 'false'}
|
|
89
88
|
onClick={() => {
|
|
90
|
-
|
|
89
|
+
session?.client.browse.click(assessment.id, assessment.motivation);
|
|
91
90
|
}}
|
|
92
91
|
{...hoverProps}
|
|
93
92
|
>
|
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
|
4
4
|
import { useTranslations } from '../../../contexts/TranslationContext';
|
|
5
|
-
import {
|
|
5
|
+
import { useSemiont } from '../../../session/SemiontProvider';
|
|
6
|
+
import { useObservable } from '../../../hooks/useObservable';
|
|
6
7
|
import { useEventSubscriptions } from '../../../contexts/useEventSubscription';
|
|
7
8
|
import type { components, Selector } from '@semiont/core';
|
|
8
|
-
import { getTextPositionSelector, getTargetSelector } from '@semiont/
|
|
9
|
+
import { getTextPositionSelector, getTargetSelector } from '@semiont/core';
|
|
9
10
|
import { AssessmentEntry } from './AssessmentEntry';
|
|
10
11
|
import { AssistSection } from './AssistSection';
|
|
11
12
|
import { PanelHeader } from './PanelHeader';
|
|
12
13
|
import './AssessmentPanel.css';
|
|
13
14
|
|
|
14
|
-
type Annotation
|
|
15
|
+
import type { Annotation } from '@semiont/core';
|
|
15
16
|
type Motivation = components['schemas']['Motivation'];
|
|
17
|
+
type JobProgress = components['schemas']['JobProgress'];
|
|
16
18
|
|
|
17
19
|
// Unified pending annotation type
|
|
18
20
|
interface PendingAnnotation {
|
|
@@ -41,11 +43,7 @@ interface AssessmentPanelProps {
|
|
|
41
43
|
annotations: Annotation[];
|
|
42
44
|
pendingAnnotation: PendingAnnotation | null;
|
|
43
45
|
isAssisting?: boolean;
|
|
44
|
-
progress?:
|
|
45
|
-
status: string;
|
|
46
|
-
percentage?: number;
|
|
47
|
-
message?: string;
|
|
48
|
-
} | null;
|
|
46
|
+
progress?: JobProgress | null;
|
|
49
47
|
locale?: string;
|
|
50
48
|
annotateMode?: boolean;
|
|
51
49
|
scrollToAnnotationId?: string | null;
|
|
@@ -72,7 +70,7 @@ export function AssessmentPanel({
|
|
|
72
70
|
hoveredAnnotationId,
|
|
73
71
|
}: AssessmentPanelProps) {
|
|
74
72
|
const t = useTranslations('AssessmentPanel');
|
|
75
|
-
const
|
|
73
|
+
const session = useObservable(useSemiont().activeSession$);
|
|
76
74
|
const [newAssessmentText, setNewAssessmentText] = useState('');
|
|
77
75
|
const [focusedAnnotationId, setFocusedAnnotationId] = useState<string | null>(null);
|
|
78
76
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
@@ -139,14 +137,20 @@ export function AssessmentPanel({
|
|
|
139
137
|
|
|
140
138
|
const handleSaveNewAssessment = () => {
|
|
141
139
|
if (pendingAnnotation) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
140
|
+
// Body is optional for assessments. When the user typed text,
|
|
141
|
+
// emit a single TextualBody (matching the worker's output and
|
|
142
|
+
// the majority of historical persisted assessments). When they
|
|
143
|
+
// didn't, omit body entirely — motivation:'assessing' on a
|
|
144
|
+
// target is a valid empty-content assessment.
|
|
145
|
+
const trimmed = newAssessmentText.trim();
|
|
146
|
+
const body: components['schemas']['AnnotationBody'] | undefined = trimmed
|
|
147
|
+
? { type: 'TextualBody', value: trimmed, purpose: 'assessing' }
|
|
148
|
+
: undefined;
|
|
145
149
|
|
|
146
|
-
|
|
150
|
+
session?.client.mark.submit({
|
|
147
151
|
motivation: 'assessing',
|
|
148
152
|
selector: pendingAnnotation.selector,
|
|
149
|
-
body,
|
|
153
|
+
...(body !== undefined ? { body } : {}),
|
|
150
154
|
});
|
|
151
155
|
setNewAssessmentText('');
|
|
152
156
|
}
|
|
@@ -158,14 +162,14 @@ export function AssessmentPanel({
|
|
|
158
162
|
|
|
159
163
|
const handleEscape = (e: KeyboardEvent) => {
|
|
160
164
|
if (e.key === 'Escape') {
|
|
161
|
-
|
|
165
|
+
session?.client.mark.cancelPending();
|
|
162
166
|
setNewAssessmentText('');
|
|
163
167
|
}
|
|
164
168
|
};
|
|
165
169
|
|
|
166
170
|
document.addEventListener('keydown', handleEscape);
|
|
167
171
|
return () => document.removeEventListener('keydown', handleEscape);
|
|
168
|
-
}, [pendingAnnotation]);
|
|
172
|
+
}, [pendingAnnotation, session]);
|
|
169
173
|
|
|
170
174
|
// Event handler for annotation clicks (extracted to avoid inline arrow function)
|
|
171
175
|
const handleAnnotationClick = useCallback(({ annotationId }: { annotationId: string }) => {
|
|
@@ -211,7 +215,7 @@ export function AssessmentPanel({
|
|
|
211
215
|
<div className="semiont-annotation-prompt__actions">
|
|
212
216
|
<button
|
|
213
217
|
onClick={() => {
|
|
214
|
-
|
|
218
|
+
session?.client.mark.cancelPending();
|
|
215
219
|
setNewAssessmentText('');
|
|
216
220
|
}}
|
|
217
221
|
className="semiont-button semiont-button--secondary"
|
|
@@ -2,20 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useCallback } from 'react';
|
|
4
4
|
import { useTranslations } from '../../../contexts/TranslationContext';
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
5
|
+
import { useSemiont } from '../../../session/SemiontProvider';
|
|
6
|
+
import { useObservable } from '../../../hooks/useObservable';
|
|
7
|
+
import type { Motivation, components } from '@semiont/core';
|
|
7
8
|
import './AssistSection.css';
|
|
8
9
|
|
|
10
|
+
type JobProgress = components['schemas']['JobProgress'];
|
|
11
|
+
|
|
9
12
|
interface AssistSectionProps {
|
|
10
13
|
annotationType: 'highlight' | 'assessment' | 'comment';
|
|
11
14
|
isAssisting: boolean;
|
|
12
15
|
locale?: string;
|
|
13
|
-
progress?:
|
|
14
|
-
status: string;
|
|
15
|
-
percentage?: number;
|
|
16
|
-
message?: string;
|
|
17
|
-
requestParams?: Array<{ label: string; value: string }>;
|
|
18
|
-
} | null | undefined;
|
|
16
|
+
progress?: JobProgress | null | undefined;
|
|
19
17
|
}
|
|
20
18
|
|
|
21
19
|
// Color schemes are now handled via CSS data attributes
|
|
@@ -43,7 +41,7 @@ export function AssistSection({
|
|
|
43
41
|
annotationType === 'assessment' ? 'AssessmentPanel' :
|
|
44
42
|
'CommentsPanel';
|
|
45
43
|
const t = useTranslations(panelName);
|
|
46
|
-
const
|
|
44
|
+
const session = useObservable(useSemiont().activeSession$);
|
|
47
45
|
const [instructions, setInstructions] = useState('');
|
|
48
46
|
type ToneValue = 'scholarly' | 'explanatory' | 'conversational' | 'technical' | 'analytical' | 'critical' | 'balanced' | 'constructive' | '';
|
|
49
47
|
const [tone, setTone] = useState<ToneValue>('');
|
|
@@ -72,25 +70,21 @@ export function AssistSection({
|
|
|
72
70
|
annotationType === 'assessment' ? 'assessing' :
|
|
73
71
|
'commenting';
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
tone: (annotationType === 'comment' || annotationType === 'assessment') && tone ? tone : undefined,
|
|
81
|
-
density: (annotationType === 'comment' || annotationType === 'assessment' || annotationType === 'highlight') && useDensity ? density : undefined,
|
|
82
|
-
language: (annotationType === 'comment' || annotationType === 'assessment') ? locale : undefined,
|
|
83
|
-
},
|
|
73
|
+
session?.client.mark.requestAssist(motivation, {
|
|
74
|
+
instructions: instructions.trim() || undefined,
|
|
75
|
+
tone: (annotationType === 'comment' || annotationType === 'assessment') && tone ? tone : undefined,
|
|
76
|
+
density: (annotationType === 'comment' || annotationType === 'assessment' || annotationType === 'highlight') && useDensity ? density : undefined,
|
|
77
|
+
language: (annotationType === 'comment' || annotationType === 'assessment') ? locale : undefined,
|
|
84
78
|
});
|
|
85
79
|
|
|
86
80
|
setInstructions('');
|
|
87
81
|
setTone('');
|
|
88
82
|
// Don't reset density/useDensity - persist across assists
|
|
89
|
-
}, [annotationType, instructions, tone, useDensity, density, locale]);
|
|
83
|
+
}, [annotationType, instructions, tone, useDensity, density, locale, session]);
|
|
90
84
|
|
|
91
85
|
const handleDismissProgress = useCallback(() => {
|
|
92
|
-
|
|
93
|
-
}, []);
|
|
86
|
+
session?.client.mark.dismissProgress();
|
|
87
|
+
}, [session]);
|
|
94
88
|
|
|
95
89
|
return (
|
|
96
90
|
<div className="semiont-panel__section">
|
|
@@ -1,23 +1,45 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
+
import type { ConnectionState } from '@semiont/core';
|
|
3
4
|
import { useTranslations } from '../../../contexts/TranslationContext';
|
|
4
5
|
import './CollaborationPanel.css';
|
|
5
6
|
|
|
6
7
|
interface Props {
|
|
7
|
-
|
|
8
|
+
/**
|
|
9
|
+
* Connection state from `client.actor.state$`. See
|
|
10
|
+
* `packages/api-client/src/view-models/domain/actor-vm.ts`.
|
|
11
|
+
*
|
|
12
|
+
* UI mapping:
|
|
13
|
+
* `open` | `reconnecting` | `initial` | `connecting`
|
|
14
|
+
* → treated as "healthy" (green dot, "Live" label, event count visible).
|
|
15
|
+
* `reconnecting` is specifically INCLUDED in healthy because a
|
|
16
|
+
* brief reconnect (mount churn, channel-set change, quick blip)
|
|
17
|
+
* shouldn't alarm the user. The 100 ms reconnect debounce and
|
|
18
|
+
* sub-second fetch retry make `reconnecting` a transient state.
|
|
19
|
+
* `degraded` | `closed`
|
|
20
|
+
* → treated as "disconnected" (red dot, "Disconnected" label).
|
|
21
|
+
* `degraded` is the 3 s threshold at which the state machine
|
|
22
|
+
* decides the disconnect is sustained; this is the UI-banner
|
|
23
|
+
* trigger the plan was designed around.
|
|
24
|
+
*/
|
|
25
|
+
state: ConnectionState;
|
|
8
26
|
eventCount: number;
|
|
9
27
|
lastEventTimestamp?: string;
|
|
10
28
|
knowledgeBaseName?: string;
|
|
11
29
|
}
|
|
12
30
|
|
|
13
31
|
export function CollaborationPanel({
|
|
14
|
-
|
|
32
|
+
state,
|
|
15
33
|
eventCount,
|
|
16
34
|
lastEventTimestamp,
|
|
17
35
|
knowledgeBaseName
|
|
18
36
|
}: Props) {
|
|
19
37
|
const t = useTranslations('CollaborationPanel');
|
|
20
38
|
|
|
39
|
+
// Healthy = live, or briefly flapping. Only genuinely sustained
|
|
40
|
+
// disconnects surface as "Disconnected" in the UI.
|
|
41
|
+
const isHealthy = state === 'open' || state === 'reconnecting' || state === 'initial' || state === 'connecting';
|
|
42
|
+
|
|
21
43
|
// Format last sync time
|
|
22
44
|
let lastSyncText: string;
|
|
23
45
|
if (!lastEventTimestamp) {
|
|
@@ -71,16 +93,16 @@ export function CollaborationPanel({
|
|
|
71
93
|
<span className="semiont-collaboration-panel__indicator">
|
|
72
94
|
<span
|
|
73
95
|
className="semiont-collaboration-panel__dot"
|
|
74
|
-
data-connected={
|
|
96
|
+
data-connected={isHealthy ? 'true' : 'false'}
|
|
75
97
|
></span>
|
|
76
98
|
<span
|
|
77
99
|
className="semiont-collaboration-panel__status-text"
|
|
78
|
-
data-connected={
|
|
100
|
+
data-connected={isHealthy ? 'true' : 'false'}
|
|
79
101
|
>
|
|
80
|
-
{
|
|
102
|
+
{isHealthy ? t('live') : t('disconnected')}
|
|
81
103
|
</span>
|
|
82
104
|
</span>
|
|
83
|
-
{
|
|
105
|
+
{isHealthy && eventCount > 0 && (
|
|
84
106
|
<span className="semiont-collaboration-panel__event-count">
|
|
85
107
|
({t('events', { count: eventCount })})
|
|
86
108
|
</span>
|
|
@@ -93,7 +115,7 @@ export function CollaborationPanel({
|
|
|
93
115
|
<span className="semiont-collaboration-panel__label">{t('lastSync')}</span> {lastSyncText}
|
|
94
116
|
</div>
|
|
95
117
|
<div>
|
|
96
|
-
{
|
|
118
|
+
{isHealthy
|
|
97
119
|
? t('realtimeActive')
|
|
98
120
|
: t('reconnecting')}
|
|
99
121
|
</div>
|
|
@@ -2,12 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useRef, useImperativeHandle, type Ref } from 'react';
|
|
4
4
|
import { useTranslations } from '../../../contexts/TranslationContext';
|
|
5
|
-
import type {
|
|
6
|
-
import { getAnnotationExactText, getCommentText } from '@semiont/
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
type Annotation = components['schemas']['Annotation'];
|
|
5
|
+
import type { Annotation } from '@semiont/core';
|
|
6
|
+
import { getAnnotationExactText, getCommentText } from '@semiont/core';
|
|
7
|
+
import { useSemiont } from '../../../session/SemiontProvider';
|
|
8
|
+
import { useObservable } from '../../../hooks/useObservable';
|
|
9
|
+
import { useHoverEmitter } from '../../../hooks/useHoverEmitter';
|
|
11
10
|
|
|
12
11
|
interface CommentEntryProps {
|
|
13
12
|
comment: Annotation;
|
|
@@ -42,7 +41,7 @@ export function CommentEntry({
|
|
|
42
41
|
ref,
|
|
43
42
|
}: CommentEntryProps) {
|
|
44
43
|
const t = useTranslations('CommentsPanel');
|
|
45
|
-
const
|
|
44
|
+
const session = useObservable(useSemiont().activeSession$);
|
|
46
45
|
const hoverProps = useHoverEmitter(comment.id);
|
|
47
46
|
const [isEditing, setIsEditing] = useState(false);
|
|
48
47
|
const [editText, setEditText] = useState('');
|
|
@@ -86,7 +85,7 @@ export function CommentEntry({
|
|
|
86
85
|
data-type="comment"
|
|
87
86
|
data-focused={isFocused ? 'true' : 'false'}
|
|
88
87
|
onClick={() => {
|
|
89
|
-
|
|
88
|
+
session?.client.browse.click(comment.id, comment.motivation);
|
|
90
89
|
}}
|
|
91
90
|
{...hoverProps}
|
|
92
91
|
>
|
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
import { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
|
4
4
|
import { useTranslations } from '../../../contexts/TranslationContext';
|
|
5
|
-
import {
|
|
5
|
+
import { useSemiont } from '../../../session/SemiontProvider';
|
|
6
|
+
import { useObservable } from '../../../hooks/useObservable';
|
|
6
7
|
import { useEventSubscriptions } from '../../../contexts/useEventSubscription';
|
|
7
8
|
import type { components, Selector } from '@semiont/core';
|
|
8
|
-
import { getTextPositionSelector, getTargetSelector } from '@semiont/
|
|
9
|
+
import { getTextPositionSelector, getTargetSelector } from '@semiont/core';
|
|
9
10
|
import { CommentEntry } from './CommentEntry';
|
|
10
11
|
import { AssistSection } from './AssistSection';
|
|
11
12
|
import { PanelHeader } from './PanelHeader';
|
|
12
13
|
import './CommentsPanel.css';
|
|
13
14
|
|
|
14
|
-
type Annotation
|
|
15
|
+
import type { Annotation } from '@semiont/core';
|
|
15
16
|
type Motivation = components['schemas']['Motivation'];
|
|
17
|
+
type JobProgress = components['schemas']['JobProgress'];
|
|
16
18
|
|
|
17
19
|
// Unified pending annotation type
|
|
18
20
|
interface PendingAnnotation {
|
|
@@ -42,11 +44,7 @@ interface CommentsPanelProps {
|
|
|
42
44
|
pendingAnnotation: PendingAnnotation | null;
|
|
43
45
|
annotateMode?: boolean;
|
|
44
46
|
isAssisting?: boolean;
|
|
45
|
-
progress?:
|
|
46
|
-
status: string;
|
|
47
|
-
percentage?: number;
|
|
48
|
-
message?: string;
|
|
49
|
-
} | null;
|
|
47
|
+
progress?: JobProgress | null;
|
|
50
48
|
locale?: string;
|
|
51
49
|
scrollToAnnotationId?: string | null;
|
|
52
50
|
onScrollCompleted?: () => void;
|
|
@@ -72,7 +70,7 @@ export function CommentsPanel({
|
|
|
72
70
|
hoveredAnnotationId,
|
|
73
71
|
}: CommentsPanelProps) {
|
|
74
72
|
const t = useTranslations('CommentsPanel');
|
|
75
|
-
const
|
|
73
|
+
const session = useObservable(useSemiont().activeSession$);
|
|
76
74
|
const [newCommentText, setNewCommentText] = useState('');
|
|
77
75
|
const [focusedAnnotationId, setFocusedAnnotationId] = useState<string | null>(null);
|
|
78
76
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
@@ -169,7 +167,7 @@ export function CommentsPanel({
|
|
|
169
167
|
|
|
170
168
|
const handleSaveNewComment = () => {
|
|
171
169
|
if (newCommentText.trim() && pendingAnnotation) {
|
|
172
|
-
|
|
170
|
+
session?.client.mark.submit({
|
|
173
171
|
motivation: 'commenting',
|
|
174
172
|
selector: pendingAnnotation.selector,
|
|
175
173
|
body: [{ type: 'TextualBody', value: newCommentText, purpose: 'commenting' }],
|
|
@@ -184,14 +182,14 @@ export function CommentsPanel({
|
|
|
184
182
|
|
|
185
183
|
const handleEscape = (e: KeyboardEvent) => {
|
|
186
184
|
if (e.key === 'Escape') {
|
|
187
|
-
|
|
185
|
+
session?.client.mark.cancelPending();
|
|
188
186
|
setNewCommentText('');
|
|
189
187
|
}
|
|
190
188
|
};
|
|
191
189
|
|
|
192
190
|
document.addEventListener('keydown', handleEscape);
|
|
193
191
|
return () => document.removeEventListener('keydown', handleEscape);
|
|
194
|
-
}, [pendingAnnotation]);
|
|
192
|
+
}, [pendingAnnotation, session]);
|
|
195
193
|
|
|
196
194
|
return (
|
|
197
195
|
<div className="semiont-panel">
|
|
@@ -226,7 +224,7 @@ export function CommentsPanel({
|
|
|
226
224
|
<div className="semiont-annotation-prompt__actions">
|
|
227
225
|
<button
|
|
228
226
|
onClick={() => {
|
|
229
|
-
|
|
227
|
+
session?.client.mark.cancelPending();
|
|
230
228
|
setNewCommentText('');
|
|
231
229
|
}}
|
|
232
230
|
className="semiont-button semiont-button--secondary"
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
3
|
import type { Ref } from 'react';
|
|
4
|
-
import type {
|
|
5
|
-
import { getAnnotationExactText } from '@semiont/
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
type Annotation = components['schemas']['Annotation'];
|
|
4
|
+
import type { Annotation } from '@semiont/core';
|
|
5
|
+
import { getAnnotationExactText } from '@semiont/core';
|
|
6
|
+
import { useSemiont } from '../../../session/SemiontProvider';
|
|
7
|
+
import { useObservable } from '../../../hooks/useObservable';
|
|
8
|
+
import { useHoverEmitter } from '../../../hooks/useHoverEmitter';
|
|
10
9
|
|
|
11
10
|
interface HighlightEntryProps {
|
|
12
11
|
highlight: Annotation;
|
|
@@ -38,7 +37,7 @@ export function HighlightEntry({
|
|
|
38
37
|
isHovered = false,
|
|
39
38
|
ref,
|
|
40
39
|
}: HighlightEntryProps) {
|
|
41
|
-
const
|
|
40
|
+
const session = useObservable(useSemiont().activeSession$);
|
|
42
41
|
const hoverProps = useHoverEmitter(highlight.id);
|
|
43
42
|
|
|
44
43
|
const selectedText = getAnnotationExactText(highlight);
|
|
@@ -50,7 +49,7 @@ export function HighlightEntry({
|
|
|
50
49
|
data-type="highlight"
|
|
51
50
|
data-focused={isFocused ? 'true' : 'false'}
|
|
52
51
|
onClick={() => {
|
|
53
|
-
|
|
52
|
+
session?.client.browse.click(highlight.id, highlight.motivation);
|
|
54
53
|
}}
|
|
55
54
|
{...hoverProps}
|
|
56
55
|
>
|
|
@@ -2,17 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
import { useEffect, useState, useRef, useCallback, useMemo } from 'react';
|
|
4
4
|
import { useTranslations } from '../../../contexts/TranslationContext';
|
|
5
|
-
import {
|
|
5
|
+
import { useSemiont } from '../../../session/SemiontProvider';
|
|
6
|
+
import { useObservable } from '../../../hooks/useObservable';
|
|
6
7
|
import { useEventSubscriptions } from '../../../contexts/useEventSubscription';
|
|
7
8
|
import type { components, Selector } from '@semiont/core';
|
|
8
|
-
import { getTextPositionSelector, getTargetSelector } from '@semiont/
|
|
9
|
+
import { getTextPositionSelector, getTargetSelector } from '@semiont/core';
|
|
9
10
|
import { HighlightEntry } from './HighlightEntry';
|
|
10
11
|
import { AssistSection } from './AssistSection';
|
|
11
12
|
import { PanelHeader } from './PanelHeader';
|
|
12
13
|
import './HighlightPanel.css';
|
|
13
14
|
|
|
14
|
-
type Annotation
|
|
15
|
+
import type { Annotation } from '@semiont/core';
|
|
15
16
|
type Motivation = components['schemas']['Motivation'];
|
|
17
|
+
type JobProgress = components['schemas']['JobProgress'];
|
|
16
18
|
|
|
17
19
|
// Unified pending annotation type
|
|
18
20
|
interface PendingAnnotation {
|
|
@@ -24,11 +26,7 @@ interface HighlightPanelProps {
|
|
|
24
26
|
annotations: Annotation[];
|
|
25
27
|
pendingAnnotation: PendingAnnotation | null;
|
|
26
28
|
isAssisting?: boolean;
|
|
27
|
-
progress?:
|
|
28
|
-
status: string;
|
|
29
|
-
percentage?: number;
|
|
30
|
-
message?: string;
|
|
31
|
-
} | null;
|
|
29
|
+
progress?: JobProgress | null;
|
|
32
30
|
annotateMode?: boolean;
|
|
33
31
|
scrollToAnnotationId?: string | null;
|
|
34
32
|
onScrollCompleted?: () => void;
|
|
@@ -53,7 +51,7 @@ export function HighlightPanel({
|
|
|
53
51
|
}: HighlightPanelProps) {
|
|
54
52
|
|
|
55
53
|
const t = useTranslations('HighlightPanel');
|
|
56
|
-
const
|
|
54
|
+
const session = useObservable(useSemiont().activeSession$);
|
|
57
55
|
const [focusedAnnotationId, setFocusedAnnotationId] = useState<string | null>(null);
|
|
58
56
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
59
57
|
|
|
@@ -129,16 +127,17 @@ export function HighlightPanel({
|
|
|
129
127
|
});
|
|
130
128
|
|
|
131
129
|
// Highlights auto-create: when pendingAnnotation arrives with highlighting motivation,
|
|
132
|
-
// immediately emit mark:submit event
|
|
130
|
+
// immediately emit mark:submit event. Highlights carry no body —
|
|
131
|
+
// motivation:'highlighting' on a target is a complete annotation
|
|
132
|
+
// per the W3C Web Annotation Model.
|
|
133
133
|
useEffect(() => {
|
|
134
134
|
if (pendingAnnotation && pendingAnnotation.motivation === 'highlighting') {
|
|
135
|
-
|
|
135
|
+
session?.client.mark.submit({
|
|
136
136
|
motivation: 'highlighting',
|
|
137
137
|
selector: pendingAnnotation.selector,
|
|
138
|
-
body: [],
|
|
139
138
|
});
|
|
140
139
|
}
|
|
141
|
-
}, [pendingAnnotation]);
|
|
140
|
+
}, [pendingAnnotation, session]);
|
|
142
141
|
|
|
143
142
|
return (
|
|
144
143
|
<div className="semiont-panel">
|
|
@@ -3,17 +3,15 @@
|
|
|
3
3
|
import type { Ref } from 'react';
|
|
4
4
|
import type { RouteBuilder } from '../../../contexts/RoutingContext';
|
|
5
5
|
import { useTranslations } from '../../../contexts/TranslationContext';
|
|
6
|
-
import type {
|
|
7
|
-
import {
|
|
8
|
-
import { getAnnotationExactText, isBodyResolved, getBodySource, getFragmentSelector, getSvgSelector, getTargetSelector } from '@semiont/
|
|
6
|
+
import type { Annotation } from '@semiont/core';
|
|
7
|
+
import { resourceId } from '@semiont/core';
|
|
8
|
+
import { getAnnotationExactText, isBodyResolved, getBodySource, getFragmentSelector, getSvgSelector, getTargetSelector } from '@semiont/core';
|
|
9
9
|
import { getEntityTypes } from '@semiont/ontology';
|
|
10
10
|
import { getResourceIcon } from '../../../lib/resource-utils';
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
11
|
+
import { useSemiont } from '../../../session/SemiontProvider';
|
|
12
|
+
import { useObservable } from '../../../hooks/useObservable';
|
|
13
13
|
import { useObservableExternalNavigation } from '../../../hooks/useObservableBrowse';
|
|
14
|
-
import { useHoverEmitter } from '../../../hooks/
|
|
15
|
-
|
|
16
|
-
type Annotation = components['schemas']['Annotation'];
|
|
14
|
+
import { useHoverEmitter } from '../../../hooks/useHoverEmitter';
|
|
17
15
|
|
|
18
16
|
// Extended annotation type with runtime properties added by backend enrichment
|
|
19
17
|
interface EnrichedAnnotation extends Annotation {
|
|
@@ -41,8 +39,8 @@ export function ReferenceEntry({
|
|
|
41
39
|
ref,
|
|
42
40
|
}: ReferenceEntryProps) {
|
|
43
41
|
const t = useTranslations('ReferencesPanel');
|
|
44
|
-
const
|
|
45
|
-
const semiont =
|
|
42
|
+
const session = useObservable(useSemiont().activeSession$);
|
|
43
|
+
const semiont = session?.client;
|
|
46
44
|
const navigate = useObservableExternalNavigation();
|
|
47
45
|
const hoverProps = useHoverEmitter(reference.id);
|
|
48
46
|
|
|
@@ -75,18 +73,18 @@ export function ReferenceEntry({
|
|
|
75
73
|
: '';
|
|
76
74
|
|
|
77
75
|
const handleUnlink = () => {
|
|
78
|
-
if (source && resolvedResourceUri) {
|
|
76
|
+
if (source && resolvedResourceUri && semiont) {
|
|
79
77
|
semiont.bind.body(
|
|
80
78
|
resourceId(source),
|
|
81
|
-
|
|
79
|
+
reference.id,
|
|
82
80
|
[{ op: 'remove', item: { type: 'SpecificResource', source: resolvedResourceUri, purpose: 'linking' } }],
|
|
83
81
|
).catch(() => { /* error handled by events-stream */ });
|
|
84
82
|
}
|
|
85
83
|
};
|
|
86
84
|
|
|
87
85
|
const handleInitiateWizard = () => {
|
|
88
|
-
|
|
89
|
-
annotationId:
|
|
86
|
+
session?.client.bind.initiate({
|
|
87
|
+
annotationId: reference.id,
|
|
90
88
|
resourceId: resourceId(source),
|
|
91
89
|
defaultTitle: selectedText,
|
|
92
90
|
entityTypes,
|
|
@@ -112,7 +110,7 @@ export function ReferenceEntry({
|
|
|
112
110
|
data-type="reference"
|
|
113
111
|
data-focused={isFocused ? 'true' : 'false'}
|
|
114
112
|
onClick={() => {
|
|
115
|
-
|
|
113
|
+
session?.client.browse.click(reference.id, reference.motivation);
|
|
116
114
|
}}
|
|
117
115
|
{...hoverProps}
|
|
118
116
|
>
|