@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
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
* Only requires minimal props from the framework layer (routing, modals).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import React, { useState, useEffect, useCallback
|
|
9
|
-
import { useQueryClient } from '@tanstack/react-query';
|
|
8
|
+
import React, { useState, useEffect, useCallback } from 'react';
|
|
10
9
|
import type { components, ResourceId, GatheredContext, EventMap } from '@semiont/core';
|
|
10
|
+
import type { ConnectionState } from '@semiont/api-client';
|
|
11
11
|
import { annotationId } from '@semiont/core';
|
|
12
12
|
import { getLanguage, getPrimaryRepresentation, getPrimaryMediaType, getMimeCategory } from '@semiont/api-client';
|
|
13
13
|
import { ANNOTATORS } from '@semiont/react-ui';
|
|
@@ -21,34 +21,23 @@ import { Toolbar } from '@semiont/react-ui';
|
|
|
21
21
|
import { useResourceLoadingAnnouncements } from '@semiont/react-ui';
|
|
22
22
|
import { ResourceViewer } from '@semiont/react-ui';
|
|
23
23
|
import { useObservable } from '@semiont/react-ui';
|
|
24
|
-
import { QUERY_KEYS } from '../../../lib/query-keys';
|
|
25
|
-
import { useResources, useEntityTypes } from '../../../lib/api-hooks';
|
|
26
24
|
import { useResourceContent } from '../../../hooks/useResourceContent';
|
|
27
25
|
import { useMediaToken } from '../../../hooks/useMediaToken';
|
|
28
26
|
import { useToast } from '../../../components/Toast';
|
|
29
27
|
import { useTheme } from '../../../contexts/ThemeContext';
|
|
30
28
|
import { useLineNumbers } from '../../../hooks/useLineNumbers';
|
|
31
29
|
import { useHoverDelay } from '../../../hooks/useHoverDelay';
|
|
32
|
-
import { useResourceEvents } from '../../../hooks/useResourceEvents';
|
|
33
|
-
import { useOpenResources } from '../../../contexts/OpenResourcesContext';
|
|
34
|
-
// Import EventBus hooks directly from context to avoid mocking issues in tests
|
|
35
|
-
import { useEventBus } from '../../../contexts/EventBusContext';
|
|
36
30
|
import { useEventSubscriptions } from '../../../contexts/useEventSubscription';
|
|
37
31
|
import { useResourceAnnotations } from '../../../contexts/ResourceAnnotationsContext';
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import type { StreamStatus } from '../../../hooks/useResourceEvents';
|
|
43
|
-
import { usePanelBrowse } from '../../../hooks/usePanelBrowse';
|
|
44
|
-
import { useYieldFlow } from '../../../hooks/useYieldFlow';
|
|
45
|
-
import { useContextGatherFlow } from '../../../hooks/useContextGatherFlow';
|
|
32
|
+
import { useSemiont } from '../../../session/SemiontProvider';
|
|
33
|
+
import { createResourceViewerPageVM } from '@semiont/api-client';
|
|
34
|
+
import { useViewModel } from '../../../hooks/useViewModel';
|
|
35
|
+
import { useShellVM } from '../../../hooks/useShellVM';
|
|
46
36
|
import { useTranslations } from '../../../contexts/TranslationContext';
|
|
47
37
|
import { ReferenceWizardModal } from '../../../components/modals/ReferenceWizardModal';
|
|
48
38
|
import type { GenerationConfig } from '../../../components/modals/ConfigureGenerationStep';
|
|
49
39
|
|
|
50
40
|
type SemiontResource = components['schemas']['ResourceDescriptor'];
|
|
51
|
-
type Annotation = components['schemas']['Annotation'];
|
|
52
41
|
|
|
53
42
|
export interface ResourceViewerPageProps {
|
|
54
43
|
/**
|
|
@@ -87,9 +76,11 @@ export interface ResourceViewerPageProps {
|
|
|
87
76
|
refetchDocument: () => Promise<unknown>;
|
|
88
77
|
|
|
89
78
|
/**
|
|
90
|
-
*
|
|
79
|
+
* Bus connection state for the active workspace. Six-valued state
|
|
80
|
+
* machine from `actor.state$`; CollaborationPanel maps it to the
|
|
81
|
+
* "Live" / "Disconnected" visual.
|
|
91
82
|
*/
|
|
92
|
-
streamStatus:
|
|
83
|
+
streamStatus: ConnectionState;
|
|
93
84
|
|
|
94
85
|
/**
|
|
95
86
|
* Name of the active knowledge base (for display in panels)
|
|
@@ -102,7 +93,7 @@ export interface ResourceViewerPageProps {
|
|
|
102
93
|
*
|
|
103
94
|
* Uses hooks directly (NO containers, NO render props, NO ResourceViewerPageContent wrapper)
|
|
104
95
|
*
|
|
105
|
-
* @emits
|
|
96
|
+
* @emits nav:push - Navigate to a resource or filtered view
|
|
106
97
|
* @emits beckon:sparkle - Trigger sparkle animation on an annotation
|
|
107
98
|
* @emits bind:update-body - Update annotation body content
|
|
108
99
|
* @subscribes mark:archive - Archive the current resource
|
|
@@ -138,23 +129,17 @@ export function ResourceViewerPage({
|
|
|
138
129
|
// Translations
|
|
139
130
|
const tw = useTranslations('ReferenceWizard');
|
|
140
131
|
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
const semiont =
|
|
144
|
-
const queryClient = useQueryClient(); // retained for non-store queries (events log)
|
|
132
|
+
const browser = useSemiont();
|
|
133
|
+
const session = useObservable(browser.activeSession$);
|
|
134
|
+
const semiont = session?.client;
|
|
145
135
|
|
|
146
136
|
// UI state hooks
|
|
147
|
-
const { showError, showSuccess } = useToast();
|
|
137
|
+
const { showError, showSuccess, showInfo } = useToast();
|
|
148
138
|
const { theme, setTheme } = useTheme();
|
|
149
139
|
const { showLineNumbers, toggleLineNumbers } = useLineNumbers();
|
|
150
140
|
const { hoverDelayMs } = useHoverDelay();
|
|
151
|
-
const { addResource } = useOpenResources();
|
|
152
141
|
const { triggerSparkleAnimation, clearNewAnnotationId } = useResourceAnnotations();
|
|
153
142
|
|
|
154
|
-
// API hooks
|
|
155
|
-
const resources = useResources();
|
|
156
|
-
const entityTypesAPI = useEntityTypes();
|
|
157
|
-
|
|
158
143
|
// Determine MIME category to choose content path
|
|
159
144
|
const resourceMediaType = getPrimaryMediaType(resource) || 'text/plain';
|
|
160
145
|
const isBinary = getMimeCategory(resourceMediaType) === 'image';
|
|
@@ -171,56 +156,42 @@ export function ResourceViewerPage({
|
|
|
171
156
|
const content = isBinary ? binaryContent : textContent;
|
|
172
157
|
const contentLoading = isBinary ? mediaTokenLoading : textLoading;
|
|
173
158
|
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
const
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
const
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
const
|
|
202
|
-
const [wizardEntityTypes, setWizardEntityTypes] = useState<string[]>([]);
|
|
203
|
-
|
|
204
|
-
useEffect(() => {
|
|
205
|
-
const subscription = eventBus.get('bind:initiate').subscribe((event) => {
|
|
206
|
-
setWizardAnnotationId(event.annotationId);
|
|
207
|
-
setWizardResourceId(event.resourceId);
|
|
208
|
-
setWizardDefaultTitle(event.defaultTitle);
|
|
209
|
-
setWizardEntityTypes(event.entityTypes);
|
|
210
|
-
setWizardOpen(true);
|
|
211
|
-
|
|
212
|
-
// Trigger context gathering — gather:requested is consumed by useContextGatherFlow
|
|
213
|
-
eventBus.get('gather:requested').next({ correlationId: crypto.randomUUID(), annotationId: event.annotationId, resourceId: event.resourceId, options: { contextWindow: 2000 } });
|
|
214
|
-
});
|
|
215
|
-
return () => subscription.unsubscribe();
|
|
216
|
-
}, [eventBus]);
|
|
159
|
+
// Composite VM — owns all flow VMs, wizard state, annotations, entity types
|
|
160
|
+
const browseVM = useShellVM();
|
|
161
|
+
const vm = useViewModel(() => createResourceViewerPageVM(semiont!, rUri, locale, browseVM));
|
|
162
|
+
|
|
163
|
+
const annotations = useObservable(vm.annotations$) ?? [];
|
|
164
|
+
const groups = useObservable(vm.annotationGroups$);
|
|
165
|
+
const allEntityTypes = useObservable(vm.entityTypes$) ?? [];
|
|
166
|
+
const referencedByRaw = useObservable(vm.referencedBy$);
|
|
167
|
+
const referencedBy = referencedByRaw ?? [];
|
|
168
|
+
const referencedByLoading = referencedByRaw === undefined;
|
|
169
|
+
const hoveredAnnotationId = useObservable(vm.beckon.hoveredAnnotationId$) ?? null;
|
|
170
|
+
const pendingAnnotation = useObservable(vm.mark.pendingAnnotation$) ?? null;
|
|
171
|
+
const assistingMotivation = useObservable(vm.mark.assistingMotivation$) ?? null;
|
|
172
|
+
const progress = useObservable(vm.mark.progress$) ?? null;
|
|
173
|
+
const activePanel = useObservable(vm.browse.activePanel$) ?? null;
|
|
174
|
+
const scrollToAnnotationId = useObservable(vm.browse.scrollToAnnotationId$) ?? null;
|
|
175
|
+
const panelInitialTab = useObservable(vm.browse.panelInitialTab$) ?? null;
|
|
176
|
+
const onScrollCompleted = vm.browse.onScrollCompleted;
|
|
177
|
+
const generationProgress = useObservable(vm.yield.progress$) ?? null;
|
|
178
|
+
const gatherContext = useObservable(vm.gather.context$) ?? null;
|
|
179
|
+
const gatherLoading = useObservable(vm.gather.loading$) ?? false;
|
|
180
|
+
const gatherError = useObservable(vm.gather.error$) ?? null;
|
|
181
|
+
const wizardState = useObservable(vm.wizard$);
|
|
182
|
+
const wizardOpen = wizardState?.open ?? false;
|
|
183
|
+
const wizardAnnotationId = wizardState?.annotationId ?? null;
|
|
184
|
+
const wizardResourceId = wizardState?.resourceId ?? null;
|
|
185
|
+
const wizardDefaultTitle = wizardState?.defaultTitle ?? '';
|
|
186
|
+
const wizardEntityTypes = wizardState?.entityTypes ?? [];
|
|
217
187
|
|
|
218
188
|
const handleWizardClose = useCallback(() => {
|
|
219
|
-
|
|
220
|
-
}, []);
|
|
189
|
+
vm.closeWizard();
|
|
190
|
+
}, [vm]);
|
|
221
191
|
|
|
222
192
|
const handleWizardGenerateSubmit = useCallback((referenceId: string, config: GenerationConfig) => {
|
|
223
|
-
|
|
193
|
+
clearNewAnnotationId(annotationId(referenceId));
|
|
194
|
+
vm.yield.generate(referenceId, {
|
|
224
195
|
title: config.title,
|
|
225
196
|
storageUri: config.storagePath,
|
|
226
197
|
prompt: config.prompt,
|
|
@@ -229,9 +200,10 @@ export function ResourceViewerPage({
|
|
|
229
200
|
maxTokens: config.maxTokens,
|
|
230
201
|
context: config.context,
|
|
231
202
|
});
|
|
232
|
-
}, [
|
|
203
|
+
}, [vm, clearNewAnnotationId]);
|
|
233
204
|
|
|
234
205
|
const handleWizardLinkResource = useCallback(async (referenceId: string, targetResourceId: string) => {
|
|
206
|
+
if (!semiont) return;
|
|
235
207
|
try {
|
|
236
208
|
await semiont.bind.body(
|
|
237
209
|
rUri,
|
|
@@ -259,103 +231,71 @@ export function ResourceViewerPage({
|
|
|
259
231
|
name: title,
|
|
260
232
|
entityTypes: entTypes.join(','),
|
|
261
233
|
});
|
|
262
|
-
|
|
234
|
+
browser.emit('nav:push', {
|
|
263
235
|
path: `/know/compose?${params.toString()}`,
|
|
264
236
|
reason: 'compose-from-wizard',
|
|
265
237
|
});
|
|
266
|
-
}, []);
|
|
238
|
+
}, [session]);
|
|
267
239
|
|
|
268
240
|
// Add resource to open tabs when it loads
|
|
269
241
|
useEffect(() => {
|
|
270
242
|
if (resource && rUri) {
|
|
271
243
|
const mediaType = getPrimaryMediaType(resource);
|
|
272
|
-
|
|
244
|
+
browser.addOpenResource(rUri, resource.name, mediaType || undefined, resource.storageUri);
|
|
273
245
|
if (typeof localStorage !== 'undefined') {
|
|
274
246
|
localStorage.setItem('lastViewedDocumentId', rUri);
|
|
275
247
|
}
|
|
276
248
|
}
|
|
277
|
-
}, [resource, rUri,
|
|
278
|
-
|
|
279
|
-
// Real-time document events (SSE)
|
|
280
|
-
// Annotation updates are handled by AnnotationStore reacting to EventBus events.
|
|
281
|
-
// Callbacks here only handle non-annotation side effects.
|
|
282
|
-
useResourceEvents({
|
|
283
|
-
rUri,
|
|
284
|
-
autoConnect: true,
|
|
285
|
-
|
|
286
|
-
onAnnotationAdded: useCallback((_event: any) => {
|
|
287
|
-
// Store handles annotation refresh; events log needs explicit invalidation
|
|
288
|
-
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.resources.events(rUri) });
|
|
289
|
-
}, [queryClient, rUri]),
|
|
290
|
-
|
|
291
|
-
onAnnotationRemoved: useCallback((_event: any) => {
|
|
292
|
-
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.resources.events(rUri) });
|
|
293
|
-
}, [queryClient, rUri]),
|
|
294
|
-
|
|
295
|
-
onAnnotationBodyUpdated: useCallback((_event: any) => {
|
|
296
|
-
queryClient.invalidateQueries({ queryKey: QUERY_KEYS.resources.events(rUri) });
|
|
297
|
-
}, [queryClient, rUri]),
|
|
298
|
-
|
|
299
|
-
// Document status events
|
|
300
|
-
onDocumentArchived: useCallback((_event: any) => {
|
|
301
|
-
refetchDocument();
|
|
302
|
-
showSuccess('This document has been archived');
|
|
303
|
-
}, [refetchDocument, showSuccess]),
|
|
304
|
-
|
|
305
|
-
onDocumentUnarchived: useCallback((_event: any) => {
|
|
306
|
-
refetchDocument();
|
|
307
|
-
showSuccess('This document has been unarchived');
|
|
308
|
-
}, [refetchDocument, showSuccess]),
|
|
309
|
-
|
|
310
|
-
// Entity tag events
|
|
311
|
-
onEntityTagAdded: useCallback((_event: any) => {
|
|
312
|
-
refetchDocument();
|
|
313
|
-
}, [refetchDocument]),
|
|
314
|
-
|
|
315
|
-
onEntityTagRemoved: useCallback((_event: any) => {
|
|
316
|
-
refetchDocument();
|
|
317
|
-
}, [refetchDocument]),
|
|
318
|
-
|
|
319
|
-
onError: useCallback((error: any) => {
|
|
320
|
-
console.error('[RealTime] Event stream error:', error);
|
|
321
|
-
}, []),
|
|
322
|
-
});
|
|
249
|
+
}, [resource, rUri, browser]);
|
|
323
250
|
|
|
324
|
-
//
|
|
325
|
-
|
|
326
|
-
|
|
251
|
+
// Bridge: when the mark VM produces a pending annotation, open the
|
|
252
|
+
// annotations panel. The mark VM (session-scoped) can't emit `panel:open`
|
|
253
|
+
// (app-scoped) directly — the React tree is the natural seam between
|
|
254
|
+
// the two buses.
|
|
255
|
+
useEffect(() => {
|
|
256
|
+
if (pendingAnnotation) {
|
|
257
|
+
browser.emit('panel:open', { panel: 'annotations' });
|
|
258
|
+
}
|
|
259
|
+
}, [pendingAnnotation, browser]);
|
|
260
|
+
|
|
261
|
+
// Domain events flow through the bus gateway (ActorVM → local EventBus).
|
|
262
|
+
// BrowseNamespace cache invalidation handles annotation/resource updates.
|
|
263
|
+
// The resource-viewer-page-vm calls client.subscribeToResource(resourceId)
|
|
264
|
+
// which bridges scoped domain events into the local EventBus.
|
|
327
265
|
|
|
328
|
-
// Event handlers extracted to useCallback (tenet: no inline handlers in useEventSubscriptions)
|
|
329
266
|
const handleResourceArchive = useCallback(async () => {
|
|
267
|
+
if (!semiont) return;
|
|
330
268
|
try {
|
|
331
|
-
await
|
|
269
|
+
await semiont.updateResource(rUri, { archived: true });
|
|
332
270
|
await refetchDocument();
|
|
333
271
|
} catch (err) {
|
|
334
272
|
console.error('Failed to archive document:', err);
|
|
335
273
|
showError('Failed to archive document');
|
|
336
274
|
}
|
|
337
|
-
}, [
|
|
275
|
+
}, [semiont, rUri, refetchDocument, showError]);
|
|
338
276
|
|
|
339
277
|
const handleResourceUnarchive = useCallback(async () => {
|
|
278
|
+
if (!semiont) return;
|
|
340
279
|
try {
|
|
341
|
-
await
|
|
280
|
+
await semiont.updateResource(rUri, { archived: false });
|
|
342
281
|
await refetchDocument();
|
|
343
282
|
} catch (err) {
|
|
344
283
|
console.error('Failed to unarchive document:', err);
|
|
345
284
|
showError('Failed to unarchive document');
|
|
346
285
|
}
|
|
347
|
-
}, [
|
|
286
|
+
}, [semiont, rUri, refetchDocument, showError]);
|
|
348
287
|
|
|
349
288
|
const handleResourceClone = useCallback(async () => {
|
|
289
|
+
if (!semiont) return;
|
|
350
290
|
try {
|
|
351
|
-
const result = await
|
|
291
|
+
const result = await semiont.generateCloneToken(rUri);
|
|
352
292
|
const token = result.token;
|
|
353
|
-
|
|
293
|
+
browser.emit('nav:push', { path: `/know/compose?mode=clone&token=${token}`, reason: 'clone' });
|
|
354
294
|
} catch (err) {
|
|
355
295
|
console.error('Failed to generate clone token:', err);
|
|
356
296
|
showError('Failed to generate clone link');
|
|
357
297
|
}
|
|
358
|
-
}, [
|
|
298
|
+
}, [semiont, rUri, showError, session]);
|
|
359
299
|
|
|
360
300
|
const handleAnnotationSparkle = useCallback(({ annotationId }: { annotationId: string }) => {
|
|
361
301
|
triggerSparkleAnimation(annotationId);
|
|
@@ -365,43 +305,58 @@ export function ResourceViewerPage({
|
|
|
365
305
|
triggerSparkleAnimation(stored.payload.annotation.id);
|
|
366
306
|
}, [triggerSparkleAnimation]);
|
|
367
307
|
|
|
368
|
-
const handleAnnotationCreateFailed = useCallback((
|
|
369
|
-
|
|
308
|
+
const handleAnnotationCreateFailed = useCallback(({ message }: { message?: string }) =>
|
|
309
|
+
showError(`Failed to create annotation: ${message || 'unknown error'}`), [showError]);
|
|
310
|
+
const handleAnnotationDeleteFailed = useCallback(({ message }: { message?: string }) =>
|
|
311
|
+
showError(`Failed to delete annotation: ${message || 'unknown error'}`), [showError]);
|
|
370
312
|
const handleAnnotateBodyUpdated = useCallback(() => {
|
|
371
|
-
// Success - optimistic update already applied via
|
|
313
|
+
// Success - optimistic update already applied via EventBus
|
|
372
314
|
}, []);
|
|
373
|
-
const handleAnnotateBodyUpdateFailed = useCallback((
|
|
315
|
+
const handleAnnotateBodyUpdateFailed = useCallback(({ message }: { message: string }) =>
|
|
316
|
+
showError(`Failed to update reference: ${message}`), [showError]);
|
|
374
317
|
|
|
375
318
|
const handleSettingsThemeChanged = useCallback(({ theme }: { theme: any }) => setTheme(theme), [setTheme]);
|
|
376
319
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
320
|
+
// Unified job lifecycle handlers. `job:complete` / `job:fail` fire
|
|
321
|
+
// for every job type (annotation + generation); we dispatch on
|
|
322
|
+
// jobType and filter to this resource. `annotationId` is present on
|
|
323
|
+
// jobs attached to a specific annotation (today: generation from a
|
|
324
|
+
// reference); it's what UI consumers lower down in the tree use to
|
|
325
|
+
// attach per-annotation visual feedback.
|
|
326
|
+
const handleJobComplete = useCallback((event: components['schemas']['JobCompleteCommand']) => {
|
|
327
|
+
if (event.resourceId !== (resource.id as string)) return;
|
|
328
|
+
if (event.jobType === 'generation') {
|
|
329
|
+
const result = event.result as components['schemas']['JobGenerationResult'] | undefined;
|
|
330
|
+
const name = result?.resourceName;
|
|
331
|
+
showSuccess(name
|
|
332
|
+
? `Resource "${name}" created successfully!`
|
|
333
|
+
: 'Resource created successfully!');
|
|
334
|
+
} else {
|
|
335
|
+
showSuccess('Annotation complete');
|
|
336
|
+
}
|
|
337
|
+
}, [resource.id, showSuccess]);
|
|
338
|
+
const handleJobFailed = useCallback((event: components['schemas']['JobFailCommand']) => {
|
|
339
|
+
if (event.resourceId !== (resource.id as string)) return;
|
|
340
|
+
if (event.jobType === 'generation') {
|
|
341
|
+
showError(`Resource generation failed: ${event.error}`);
|
|
342
|
+
} else {
|
|
343
|
+
showError(event.error || 'Annotation failed');
|
|
344
|
+
}
|
|
345
|
+
}, [resource.id, showError]);
|
|
391
346
|
|
|
392
347
|
const handleReferenceNavigate = useCallback(({ resourceId }: { resourceId: string }) => {
|
|
393
348
|
if (routes.resourceDetail) {
|
|
394
349
|
const path = routes.resourceDetail(resourceId);
|
|
395
|
-
|
|
350
|
+
browser.emit('nav:push', { path, reason: 'reference-link' });
|
|
396
351
|
}
|
|
397
|
-
}, [routes.resourceDetail]);
|
|
352
|
+
}, [routes.resourceDetail, session]);
|
|
398
353
|
|
|
399
354
|
const handleEntityTypeClicked = useCallback(({ entityType }: { entityType: string }) => {
|
|
400
355
|
if (routes.know) {
|
|
401
356
|
const path = `${routes.know}?entityType=${encodeURIComponent(entityType)}`;
|
|
402
|
-
|
|
357
|
+
browser.emit('nav:push', { path, reason: 'entity-type-filter' });
|
|
403
358
|
}
|
|
404
|
-
}, [routes.know]);
|
|
359
|
+
}, [routes.know, session]);
|
|
405
360
|
|
|
406
361
|
const handleModeToggled = useCallback(() => {
|
|
407
362
|
setAnnotateMode(prev => !prev);
|
|
@@ -421,10 +376,9 @@ export function ResourceViewerPage({
|
|
|
421
376
|
'bind:body-update-failed': handleAnnotateBodyUpdateFailed,
|
|
422
377
|
'settings:theme-changed': handleSettingsThemeChanged,
|
|
423
378
|
'settings:line-numbers-toggled': toggleLineNumbers,
|
|
424
|
-
'
|
|
425
|
-
'
|
|
426
|
-
'
|
|
427
|
-
'yield:failed': handleGenerationFailed,
|
|
379
|
+
'job:complete': handleJobComplete,
|
|
380
|
+
'job:fail': handleJobFailed,
|
|
381
|
+
'mark:assist-cancelled': () => showInfo('Annotation cancelled'),
|
|
428
382
|
'browse:reference-navigate': handleReferenceNavigate,
|
|
429
383
|
'browse:entity-type-clicked': handleEntityTypeClicked,
|
|
430
384
|
});
|
|
@@ -460,28 +414,6 @@ export function ResourceViewerPage({
|
|
|
460
414
|
return false;
|
|
461
415
|
});
|
|
462
416
|
|
|
463
|
-
// Group annotations by type using static ANNOTATORS (memoized to avoid re-grouping on unrelated re-renders)
|
|
464
|
-
const groups = useMemo(() => {
|
|
465
|
-
const result = {
|
|
466
|
-
highlights: [] as Annotation[],
|
|
467
|
-
references: [] as Annotation[],
|
|
468
|
-
assessments: [] as Annotation[],
|
|
469
|
-
comments: [] as Annotation[],
|
|
470
|
-
tags: [] as Annotation[]
|
|
471
|
-
};
|
|
472
|
-
|
|
473
|
-
for (const ann of annotations) {
|
|
474
|
-
const annotator = Object.values(ANNOTATORS).find(a => a.matchesAnnotation(ann));
|
|
475
|
-
if (annotator) {
|
|
476
|
-
const key = annotator.internalType + 's'; // highlight -> highlights
|
|
477
|
-
if (result[key as keyof typeof result]) {
|
|
478
|
-
result[key as keyof typeof result].push(ann);
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
}
|
|
482
|
-
|
|
483
|
-
return result;
|
|
484
|
-
}, [annotations]);
|
|
485
417
|
|
|
486
418
|
// Combine resource with content
|
|
487
419
|
const resourceWithContent = { ...resource, content };
|
|
@@ -489,9 +421,9 @@ export function ResourceViewerPage({
|
|
|
489
421
|
// Handlers for AnnotationHistory (legacy event-based interaction)
|
|
490
422
|
const handleEventHover = useCallback((annotationId: string | null) => {
|
|
491
423
|
if (annotationId) {
|
|
492
|
-
|
|
424
|
+
session?.client.emit('beckon:sparkle', { annotationId });
|
|
493
425
|
}
|
|
494
|
-
}, []);
|
|
426
|
+
}, [session]);
|
|
495
427
|
|
|
496
428
|
const handleEventClick = useCallback((_annotationId: string | null) => {
|
|
497
429
|
// ResourceViewer now manages scroll state internally
|
|
@@ -539,8 +471,8 @@ export function ResourceViewerPage({
|
|
|
539
471
|
) : (
|
|
540
472
|
<ResourceViewer
|
|
541
473
|
resource={resourceWithContent}
|
|
542
|
-
annotations={groups}
|
|
543
|
-
generatingReferenceId={generationProgress?.
|
|
474
|
+
annotations={groups ?? { highlights: [], comments: [], assessments: [], references: [], tags: [] }}
|
|
475
|
+
generatingReferenceId={generationProgress?.annotationId ?? null}
|
|
544
476
|
showLineNumbers={showLineNumbers}
|
|
545
477
|
hoverDelayMs={hoverDelayMs}
|
|
546
478
|
hoveredAnnotationId={hoveredAnnotationId}
|
|
@@ -583,7 +515,7 @@ export function ResourceViewerPage({
|
|
|
583
515
|
progress={progress}
|
|
584
516
|
pendingAnnotation={pendingAnnotation}
|
|
585
517
|
allEntityTypes={allEntityTypes}
|
|
586
|
-
generatingReferenceId={generationProgress?.
|
|
518
|
+
generatingReferenceId={generationProgress?.annotationId ?? null}
|
|
587
519
|
referencedBy={referencedBy}
|
|
588
520
|
referencedByLoading={referencedByLoading}
|
|
589
521
|
resourceId={rUri}
|
|
@@ -632,7 +564,7 @@ export function ResourceViewerPage({
|
|
|
632
564
|
{/* Collaboration Panel */}
|
|
633
565
|
{activePanel === 'collaboration' && (
|
|
634
566
|
<CollaborationPanel
|
|
635
|
-
|
|
567
|
+
state={streamStatus}
|
|
636
568
|
eventCount={0}
|
|
637
569
|
knowledgeBaseName={knowledgeBaseName}
|
|
638
570
|
/>
|
|
@@ -665,7 +597,6 @@ export function ResourceViewerPage({
|
|
|
665
597
|
context={gatherContext}
|
|
666
598
|
contextLoading={gatherLoading}
|
|
667
599
|
contextError={gatherError}
|
|
668
|
-
eventBus={eventBus}
|
|
669
600
|
onGenerateSubmit={handleWizardGenerateSubmit}
|
|
670
601
|
onLinkResource={handleWizardLinkResource}
|
|
671
602
|
onComposeNavigate={handleWizardComposeNavigate}
|