@semiont/react-ui 0.5.1 → 0.5.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (177) hide show
  1. package/README.md +13 -0
  2. package/dist/{ar-3W37O3R3.mjs → ar-UUMMNQKF.mjs} +2 -17
  3. package/dist/ar-UUMMNQKF.mjs.map +1 -0
  4. package/dist/{bn-JZTJLMVE.mjs → bn-AL5BJSR3.mjs} +2 -17
  5. package/dist/bn-AL5BJSR3.mjs.map +1 -0
  6. package/dist/{chunk-4NOUO3W6.mjs → chunk-EBBL3VJI.mjs} +5062 -2906
  7. package/dist/chunk-EBBL3VJI.mjs.map +1 -0
  8. package/dist/{chunk-NOD3NCXE.mjs → chunk-OJSRLEER.mjs} +2 -17
  9. package/dist/chunk-OJSRLEER.mjs.map +1 -0
  10. package/dist/{cs-XYHH7HNE.mjs → cs-UMINALSU.mjs} +2 -17
  11. package/dist/cs-UMINALSU.mjs.map +1 -0
  12. package/dist/{da-MZKIECVT.mjs → da-FKUX6CDL.mjs} +2 -17
  13. package/dist/da-FKUX6CDL.mjs.map +1 -0
  14. package/dist/{de-AYXTMRQW.mjs → de-XSJ3E25S.mjs} +2 -17
  15. package/dist/de-XSJ3E25S.mjs.map +1 -0
  16. package/dist/{el-A6CVQWAW.mjs → el-UJXNRCBP.mjs} +2 -17
  17. package/dist/el-UJXNRCBP.mjs.map +1 -0
  18. package/dist/{en-YPQQBI4T.mjs → en-J5DHKLQ5.mjs} +2 -2
  19. package/dist/{es-M2HXLJGT.mjs → es-VURP62BU.mjs} +2 -17
  20. package/dist/es-VURP62BU.mjs.map +1 -0
  21. package/dist/{fa-V6JZJDYP.mjs → fa-TIT5ZPZY.mjs} +2 -17
  22. package/dist/fa-TIT5ZPZY.mjs.map +1 -0
  23. package/dist/{fi-ONDTZ5H7.mjs → fi-F7VTGT4H.mjs} +2 -17
  24. package/dist/fi-F7VTGT4H.mjs.map +1 -0
  25. package/dist/{fr-PAPV4H4G.mjs → fr-2ZR26VF7.mjs} +2 -17
  26. package/dist/fr-2ZR26VF7.mjs.map +1 -0
  27. package/dist/{he-F6VTLJLW.mjs → he-BXP2KYVZ.mjs} +2 -17
  28. package/dist/he-BXP2KYVZ.mjs.map +1 -0
  29. package/dist/{hi-CFUAV4BF.mjs → hi-PSWTP3NC.mjs} +2 -17
  30. package/dist/hi-PSWTP3NC.mjs.map +1 -0
  31. package/dist/{id-NBKLCCI7.mjs → id-HO6TXGTO.mjs} +2 -17
  32. package/dist/id-HO6TXGTO.mjs.map +1 -0
  33. package/dist/index.d.mts +292 -27
  34. package/dist/index.mjs +1134 -592
  35. package/dist/index.mjs.map +1 -1
  36. package/dist/{it-SLSOWVVU.mjs → it-AGTDMBL3.mjs} +2 -17
  37. package/dist/it-AGTDMBL3.mjs.map +1 -0
  38. package/dist/{ja-L5IG4ECE.mjs → ja-TTGOVF5K.mjs} +2 -17
  39. package/dist/ja-TTGOVF5K.mjs.map +1 -0
  40. package/dist/{ko-QYMTULKK.mjs → ko-FF77IQ7N.mjs} +2 -17
  41. package/dist/ko-FF77IQ7N.mjs.map +1 -0
  42. package/dist/{ms-5DGSFKM2.mjs → ms-UPQWWIL4.mjs} +2 -17
  43. package/dist/ms-UPQWWIL4.mjs.map +1 -0
  44. package/dist/{nl-VZPCGONO.mjs → nl-W75HEPFL.mjs} +2 -17
  45. package/dist/nl-W75HEPFL.mjs.map +1 -0
  46. package/dist/{no-MF6F352I.mjs → no-R4W7W7ZU.mjs} +2 -17
  47. package/dist/no-R4W7W7ZU.mjs.map +1 -0
  48. package/dist/{pl-WIK72JUO.mjs → pl-GQC2ELWO.mjs} +2 -17
  49. package/dist/pl-GQC2ELWO.mjs.map +1 -0
  50. package/dist/{pt-RRP5ZF6A.mjs → pt-YGVT62RU.mjs} +2 -17
  51. package/dist/pt-YGVT62RU.mjs.map +1 -0
  52. package/dist/{ro-XHQLC3T7.mjs → ro-TST6XS6X.mjs} +2 -17
  53. package/dist/ro-TST6XS6X.mjs.map +1 -0
  54. package/dist/{sv-EWULDN6E.mjs → sv-TQLF6HV7.mjs} +2 -17
  55. package/dist/sv-TQLF6HV7.mjs.map +1 -0
  56. package/dist/test-utils.d.mts +1 -1
  57. package/dist/test-utils.mjs +5 -2353
  58. package/dist/test-utils.mjs.map +1 -1
  59. package/dist/{th-TGOBHFG4.mjs → th-HJUIETVR.mjs} +2 -17
  60. package/dist/th-HJUIETVR.mjs.map +1 -0
  61. package/dist/{tr-LMMPBMV7.mjs → tr-CW3C46TW.mjs} +2 -17
  62. package/dist/tr-CW3C46TW.mjs.map +1 -0
  63. package/dist/{uk-IPGRRJY6.mjs → uk-WTHZQB2U.mjs} +2 -17
  64. package/dist/uk-WTHZQB2U.mjs.map +1 -0
  65. package/dist/{vi-Q676OJQS.mjs → vi-PHWHJLKP.mjs} +2 -17
  66. package/dist/vi-PHWHJLKP.mjs.map +1 -0
  67. package/dist/{zh-F3MTWQDX.mjs → zh-MO3FCUD6.mjs} +2 -17
  68. package/dist/zh-MO3FCUD6.mjs.map +1 -0
  69. package/package.json +1 -1
  70. package/src/components/StatusDisplay.tsx +1 -1
  71. package/src/components/modals/PermissionDeniedModal.tsx +2 -2
  72. package/src/components/modals/SessionExpiredModal.tsx +4 -4
  73. package/src/components/resource/panels/AssessmentPanel.tsx +4 -0
  74. package/src/components/resource/panels/AssistSection.tsx +10 -1
  75. package/src/components/resource/panels/CollaborationPanel.tsx +1 -1
  76. package/src/components/resource/panels/CommentsPanel.tsx +4 -0
  77. package/src/components/resource/panels/HighlightPanel.tsx +4 -0
  78. package/src/components/resource/panels/ReferencesPanel.tsx +11 -0
  79. package/src/components/resource/panels/TagEntry.tsx +13 -2
  80. package/src/components/resource/panels/TaggingPanel.tsx +93 -41
  81. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +11 -1
  82. package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +2 -2
  83. package/src/components/resource/panels/__tests__/TagEntry.test.tsx +26 -19
  84. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +128 -38
  85. package/src/features/admin-devops/components/AdminDevOpsPage.tsx +1 -1
  86. package/src/features/admin-exchange/components/AdminExchangePage.tsx +1 -1
  87. package/src/features/admin-exchange/components/ImportCard.tsx +1 -1
  88. package/src/features/admin-exchange/state/__tests__/exchange-state-unit.test.ts +171 -0
  89. package/src/features/admin-exchange/state/exchange-state-unit.ts +131 -0
  90. package/src/features/admin-security/components/AdminSecurityPage.tsx +1 -1
  91. package/src/features/admin-security/state/__tests__/admin-security-state-unit.test.ts +68 -0
  92. package/src/features/admin-security/state/admin-security-state-unit.ts +46 -0
  93. package/src/features/admin-users/components/AdminUsersPage.tsx +1 -1
  94. package/src/features/admin-users/state/__tests__/admin-users-state-unit.test.ts +86 -0
  95. package/src/features/admin-users/state/admin-users-state-unit.ts +73 -0
  96. package/src/features/auth-welcome/state/__tests__/welcome-state-unit.test.ts +86 -0
  97. package/src/features/auth-welcome/state/welcome-state-unit.ts +44 -0
  98. package/src/features/moderate-entity-tags/components/EntityTagsPage.tsx +1 -1
  99. package/src/features/moderate-entity-tags/state/__tests__/entity-tags-state-unit.test.ts +102 -0
  100. package/src/features/moderate-entity-tags/state/entity-tags-state-unit.ts +64 -0
  101. package/src/features/moderate-recent/components/RecentDocumentsPage.tsx +1 -1
  102. package/src/features/moderate-tag-schemas/components/TagSchemasPage.tsx +4 -4
  103. package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +1 -1
  104. package/src/features/resource-compose/__tests__/UploadProgressBar.test.tsx +225 -0
  105. package/src/features/resource-compose/components/ResourceComposePage.tsx +19 -4
  106. package/src/features/resource-compose/components/UploadProgressBar.tsx +94 -0
  107. package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +187 -0
  108. package/src/features/resource-compose/state/compose-page-state-unit.ts +209 -0
  109. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +1 -1
  110. package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +76 -0
  111. package/src/features/resource-discovery/state/discover-state-unit.ts +54 -0
  112. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +4 -2
  113. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +36 -32
  114. package/src/features/resource-viewer/state/__tests__/resource-loader-state-unit.test.ts +46 -0
  115. package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +203 -0
  116. package/src/features/resource-viewer/state/resource-loader-state-unit.ts +26 -0
  117. package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +180 -0
  118. package/translations/ar.json +1 -16
  119. package/translations/bn.json +1 -16
  120. package/translations/cs.json +1 -16
  121. package/translations/da.json +1 -16
  122. package/translations/de.json +1 -16
  123. package/translations/el.json +1 -16
  124. package/translations/en.json +1 -16
  125. package/translations/es.json +1 -16
  126. package/translations/fa.json +1 -16
  127. package/translations/fi.json +1 -16
  128. package/translations/fr.json +1 -16
  129. package/translations/he.json +1 -16
  130. package/translations/hi.json +1 -16
  131. package/translations/id.json +1 -16
  132. package/translations/it.json +1 -16
  133. package/translations/ja.json +1 -16
  134. package/translations/ko.json +1 -16
  135. package/translations/ms.json +1 -16
  136. package/translations/nl.json +1 -16
  137. package/translations/no.json +1 -16
  138. package/translations/pl.json +1 -16
  139. package/translations/pt.json +1 -16
  140. package/translations/ro.json +1 -16
  141. package/translations/sv.json +1 -16
  142. package/translations/th.json +1 -16
  143. package/translations/tr.json +1 -16
  144. package/translations/uk.json +1 -16
  145. package/translations/vi.json +1 -16
  146. package/translations/zh.json +1 -16
  147. package/dist/ar-3W37O3R3.mjs.map +0 -1
  148. package/dist/bn-JZTJLMVE.mjs.map +0 -1
  149. package/dist/chunk-4NOUO3W6.mjs.map +0 -1
  150. package/dist/chunk-NOD3NCXE.mjs.map +0 -1
  151. package/dist/cs-XYHH7HNE.mjs.map +0 -1
  152. package/dist/da-MZKIECVT.mjs.map +0 -1
  153. package/dist/de-AYXTMRQW.mjs.map +0 -1
  154. package/dist/el-A6CVQWAW.mjs.map +0 -1
  155. package/dist/es-M2HXLJGT.mjs.map +0 -1
  156. package/dist/fa-V6JZJDYP.mjs.map +0 -1
  157. package/dist/fi-ONDTZ5H7.mjs.map +0 -1
  158. package/dist/fr-PAPV4H4G.mjs.map +0 -1
  159. package/dist/he-F6VTLJLW.mjs.map +0 -1
  160. package/dist/hi-CFUAV4BF.mjs.map +0 -1
  161. package/dist/id-NBKLCCI7.mjs.map +0 -1
  162. package/dist/it-SLSOWVVU.mjs.map +0 -1
  163. package/dist/ja-L5IG4ECE.mjs.map +0 -1
  164. package/dist/ko-QYMTULKK.mjs.map +0 -1
  165. package/dist/ms-5DGSFKM2.mjs.map +0 -1
  166. package/dist/nl-VZPCGONO.mjs.map +0 -1
  167. package/dist/no-MF6F352I.mjs.map +0 -1
  168. package/dist/pl-WIK72JUO.mjs.map +0 -1
  169. package/dist/pt-RRP5ZF6A.mjs.map +0 -1
  170. package/dist/ro-XHQLC3T7.mjs.map +0 -1
  171. package/dist/sv-EWULDN6E.mjs.map +0 -1
  172. package/dist/th-TGOBHFG4.mjs.map +0 -1
  173. package/dist/tr-LMMPBMV7.mjs.map +0 -1
  174. package/dist/uk-IPGRRJY6.mjs.map +0 -1
  175. package/dist/vi-Q676OJQS.mjs.map +0 -1
  176. package/dist/zh-F3MTWQDX.mjs.map +0 -1
  177. /package/dist/{en-YPQQBI4T.mjs.map → en-J5DHKLQ5.mjs.map} +0 -0
@@ -0,0 +1,203 @@
1
+ import { describe, it, expect, vi, afterEach } from 'vitest';
2
+ import { BehaviorSubject, Observable, firstValueFrom } from 'rxjs';
3
+ import { filter } from 'rxjs/operators';
4
+ import { annotationId, resourceId as makeResourceId } from '@semiont/core';
5
+ import type { ShellStateUnit } from '../../../../state/shell-state-unit';
6
+ import { createResourceViewerPageStateUnit } from '../resource-viewer-page-state-unit';
7
+ import { makeTestClient, type TestClient } from '../../../../__tests__/test-client';
8
+
9
+ const RID = makeResourceId('res-1');
10
+
11
+ function mockBrowse(): ShellStateUnit {
12
+ return {
13
+ activePanel$: new BehaviorSubject(null).asObservable(),
14
+ scrollToAnnotationId$: new BehaviorSubject(null).asObservable(),
15
+ panelInitialTab$: new BehaviorSubject(null).asObservable(),
16
+ onScrollCompleted: vi.fn(),
17
+ openPanel: vi.fn(),
18
+ closePanel: vi.fn(),
19
+ dispose: vi.fn(),
20
+ } as unknown as ShellStateUnit;
21
+ }
22
+
23
+ function clientWithNamespaces(overrides: {
24
+ annotations$?: BehaviorSubject<unknown[] | undefined>;
25
+ entityTypes$?: BehaviorSubject<string[] | undefined>;
26
+ events$?: BehaviorSubject<unknown[] | undefined>;
27
+ referencedBy$?: BehaviorSubject<unknown[] | undefined>;
28
+ resourceRepresentation?: ReturnType<typeof vi.fn>;
29
+ mediaToken?: ReturnType<typeof vi.fn>;
30
+ } = {}): TestClient {
31
+ const annotations$ = overrides.annotations$ ?? new BehaviorSubject<unknown[] | undefined>([]);
32
+ const entityTypes$ = overrides.entityTypes$ ?? new BehaviorSubject<string[] | undefined>(['Person']);
33
+ const events$ = overrides.events$ ?? new BehaviorSubject<unknown[] | undefined>([]);
34
+ const referencedBy$ = overrides.referencedBy$ ?? new BehaviorSubject<unknown[] | undefined>([]);
35
+
36
+ return makeTestClient({
37
+ browse: {
38
+ annotations: () => annotations$.asObservable(),
39
+ entityTypes: () => entityTypes$.asObservable(),
40
+ events: () => events$.asObservable(),
41
+ referencedBy: () => referencedBy$.asObservable(),
42
+ resourceRepresentation: overrides.resourceRepresentation ?? vi.fn().mockResolvedValue({
43
+ data: new TextEncoder().encode('hello').buffer,
44
+ contentType: 'text/plain',
45
+ }),
46
+ },
47
+ auth: {
48
+ mediaToken: overrides.mediaToken ?? vi.fn().mockResolvedValue({ token: 'tok-123' }),
49
+ },
50
+ mark: {
51
+ annotation: vi.fn().mockResolvedValue({ annotationId: 'ann-new' }),
52
+ delete: vi.fn().mockResolvedValue(undefined),
53
+ assist: vi.fn(() => new Observable(() => {})),
54
+ },
55
+ gather: { annotation: vi.fn(() => new Observable(() => {})) },
56
+ match: { search: vi.fn(() => new Observable(() => {})) },
57
+ yield: { fromAnnotation: vi.fn(() => new Observable(() => {})) },
58
+ bind: { body: vi.fn().mockResolvedValue(undefined) },
59
+ subscribeToResource: vi.fn().mockReturnValue(() => {}),
60
+ });
61
+ }
62
+
63
+ describe('createResourceViewerPageStateUnit', () => {
64
+ let tc: TestClient;
65
+
66
+ afterEach(() => { tc?.bus.destroy(); });
67
+
68
+ it('exposes flow VMs', () => {
69
+ tc = clientWithNamespaces();
70
+ const stateUnit = createResourceViewerPageStateUnit(tc.client, RID, 'en', mockBrowse());
71
+
72
+ expect(stateUnit.beckon).toBeDefined();
73
+ expect(stateUnit.mark).toBeDefined();
74
+ expect(stateUnit.gather).toBeDefined();
75
+ expect(stateUnit.yield).toBeDefined();
76
+ expect(stateUnit.browse).toBeDefined();
77
+
78
+ stateUnit.dispose();
79
+ });
80
+
81
+ it('derives annotations from browse namespace', async () => {
82
+ const annotations$ = new BehaviorSubject<unknown[] | undefined>([
83
+ { id: 'a1', motivation: 'highlighting' },
84
+ ]);
85
+ tc = clientWithNamespaces({ annotations$ });
86
+ const stateUnit = createResourceViewerPageStateUnit(tc.client, RID, 'en', mockBrowse());
87
+
88
+ const anns = await firstValueFrom(stateUnit.annotations$);
89
+ expect(anns).toHaveLength(1);
90
+
91
+ stateUnit.dispose();
92
+ });
93
+
94
+ it('groups annotations by type', async () => {
95
+ const annotations$ = new BehaviorSubject<unknown[] | undefined>([
96
+ { id: 'a1', motivation: 'highlighting', target: { selector: { type: 'TextQuoteSelector', exact: 'x' } } },
97
+ { id: 'a2', motivation: 'commenting', body: [{ type: 'TextualBody', value: 'note' }], target: { selector: { type: 'TextQuoteSelector', exact: 'y' } } },
98
+ ]);
99
+ tc = clientWithNamespaces({ annotations$ });
100
+ const stateUnit = createResourceViewerPageStateUnit(tc.client, RID, 'en', mockBrowse());
101
+
102
+ const groups = await firstValueFrom(stateUnit.annotationGroups$);
103
+ expect(groups.highlights.length + groups.comments.length).toBeGreaterThanOrEqual(1);
104
+
105
+ stateUnit.dispose();
106
+ });
107
+
108
+ it('exposes entity types', async () => {
109
+ tc = clientWithNamespaces();
110
+ const stateUnit = createResourceViewerPageStateUnit(tc.client, RID, 'en', mockBrowse());
111
+
112
+ const types = await firstValueFrom(stateUnit.entityTypes$);
113
+ expect(types).toEqual(['Person']);
114
+
115
+ stateUnit.dispose();
116
+ });
117
+
118
+ it('exposes events from browse namespace', async () => {
119
+ const events$ = new BehaviorSubject<unknown[] | undefined>([{ id: 'e1', type: 'mark:added' }]);
120
+ tc = clientWithNamespaces({ events$ });
121
+ const stateUnit = createResourceViewerPageStateUnit(tc.client, RID, 'en', mockBrowse());
122
+
123
+ const events = await firstValueFrom(stateUnit.events$);
124
+ expect(events).toEqual([{ id: 'e1', type: 'mark:added' }]);
125
+
126
+ stateUnit.dispose();
127
+ });
128
+
129
+ it('exposes referencedBy from browse namespace', async () => {
130
+ const referencedBy$ = new BehaviorSubject<unknown[] | undefined>([{ resourceId: 'r2' }]);
131
+ tc = clientWithNamespaces({ referencedBy$ });
132
+ const stateUnit = createResourceViewerPageStateUnit(tc.client, RID, 'en', mockBrowse());
133
+
134
+ const refs = await firstValueFrom(stateUnit.referencedBy$);
135
+ expect(refs).toEqual([{ resourceId: 'r2' }]);
136
+
137
+ stateUnit.dispose();
138
+ });
139
+
140
+ it('fetches media token for binary types', async () => {
141
+ const mediaToken = vi.fn().mockResolvedValue({ token: 'tok-456' });
142
+ tc = clientWithNamespaces({ mediaToken });
143
+ const stateUnit = createResourceViewerPageStateUnit(
144
+ tc.client, RID, 'en', mockBrowse(),
145
+ { mediaType: 'image/png' },
146
+ );
147
+
148
+ const token = await firstValueFrom(stateUnit.mediaToken$.pipe(filter((t) => t !== null)));
149
+ expect(token).toBe('tok-456');
150
+
151
+ stateUnit.dispose();
152
+ });
153
+
154
+ it('wizard initializes closed', async () => {
155
+ tc = clientWithNamespaces();
156
+ const stateUnit = createResourceViewerPageStateUnit(tc.client, RID, 'en', mockBrowse());
157
+
158
+ const wizard = await firstValueFrom(stateUnit.wizard$);
159
+ expect(wizard.open).toBe(false);
160
+
161
+ stateUnit.dispose();
162
+ });
163
+
164
+ it('bind:initiate opens wizard and fires gather:requested', async () => {
165
+ tc = clientWithNamespaces();
166
+ const stateUnit = createResourceViewerPageStateUnit(tc.client, RID, 'en', mockBrowse());
167
+ const gatherEvents: unknown[] = [];
168
+ tc.bus.get('gather:requested').subscribe((e) => gatherEvents.push(e));
169
+
170
+ tc.bus.get('bind:initiate').next({
171
+ annotationId: annotationId('ann-1'),
172
+ resourceId: makeResourceId('res-1'),
173
+ defaultTitle: 'Test',
174
+ entityTypes: ['Person'],
175
+ });
176
+
177
+ const wizard = await firstValueFrom(stateUnit.wizard$.pipe(filter((w) => w.open)));
178
+ expect(wizard.annotationId).toBe('ann-1');
179
+ expect(gatherEvents).toHaveLength(1);
180
+
181
+ stateUnit.dispose();
182
+ });
183
+
184
+ it('closeWizard resets wizard state', async () => {
185
+ tc = clientWithNamespaces();
186
+ const stateUnit = createResourceViewerPageStateUnit(tc.client, RID, 'en', mockBrowse());
187
+
188
+ tc.bus.get('bind:initiate').next({
189
+ annotationId: annotationId('ann-1'),
190
+ resourceId: makeResourceId('res-1'),
191
+ defaultTitle: 'Test',
192
+ entityTypes: [],
193
+ });
194
+
195
+ await firstValueFrom(stateUnit.wizard$.pipe(filter((w) => w.open)));
196
+ stateUnit.closeWizard();
197
+
198
+ const wizard = await firstValueFrom(stateUnit.wizard$.pipe(filter((w) => !w.open)));
199
+ expect(wizard.open).toBe(false);
200
+
201
+ stateUnit.dispose();
202
+ });
203
+ });
@@ -0,0 +1,26 @@
1
+ import { type Observable, map } from 'rxjs';
2
+ import type { ResourceDescriptor, ResourceId } from '@semiont/core';
3
+ import type { StateUnit } from '@semiont/sdk';
4
+ import type { SemiontClient } from '@semiont/sdk';
5
+
6
+ export interface ResourceLoaderStateUnit extends StateUnit {
7
+ resource$: Observable<ResourceDescriptor | undefined>;
8
+ isLoading$: Observable<boolean>;
9
+ invalidate(): void;
10
+ }
11
+
12
+ export function createResourceLoaderStateUnit(
13
+ client: SemiontClient,
14
+ resourceId: ResourceId,
15
+ ): ResourceLoaderStateUnit {
16
+ const raw$ = client.browse.resource(resourceId);
17
+ const resource$ = raw$;
18
+ const isLoading$: Observable<boolean> = raw$.pipe(map((r) => r === undefined));
19
+
20
+ return {
21
+ resource$,
22
+ isLoading$,
23
+ invalidate: () => client.browse.invalidateResourceDetail(resourceId),
24
+ dispose: () => {},
25
+ };
26
+ }
@@ -0,0 +1,180 @@
1
+ import { BehaviorSubject, type Observable, map } from 'rxjs';
2
+ import type { ResourceId, components } from '@semiont/core';
3
+ import { createDisposer } from '@semiont/sdk';
4
+ import type { StateUnit } from '@semiont/sdk';
5
+ import type { ShellStateUnit } from '../../../state/shell-state-unit';
6
+ import { createBeckonStateUnit, type BeckonStateUnit } from '@semiont/sdk';
7
+ import { createMarkStateUnit, type MarkStateUnit } from '@semiont/sdk';
8
+ import { createGatherStateUnit, type GatherStateUnit } from '@semiont/sdk';
9
+ import { createMatchStateUnit } from '@semiont/sdk';
10
+ import { createYieldStateUnit, type YieldStateUnit } from '@semiont/sdk';
11
+ import type { SemiontClient } from '@semiont/sdk';
12
+ import { decodeWithCharset } from '@semiont/core';
13
+ import { isHighlight, isComment, isAssessment, isReference, isTag } from '@semiont/core';
14
+ import type { ReferencedByEntry } from '@semiont/sdk';
15
+
16
+ import type { Annotation } from '@semiont/core';
17
+
18
+ export interface AnnotationGroups {
19
+ highlights: Annotation[];
20
+ comments: Annotation[];
21
+ assessments: Annotation[];
22
+ references: Annotation[];
23
+ tags: Annotation[];
24
+ }
25
+ type StoredEventResponse = components['schemas']['StoredEventResponse'];
26
+
27
+ export interface WizardState {
28
+ open: boolean;
29
+ annotationId: string | null;
30
+ resourceId: string | null;
31
+ defaultTitle: string;
32
+ entityTypes: string[];
33
+ }
34
+
35
+ const WIZARD_CLOSED: WizardState = {
36
+ open: false, annotationId: null, resourceId: null, defaultTitle: '', entityTypes: [],
37
+ };
38
+
39
+ export interface ResourceViewerPageStateUnit extends StateUnit {
40
+ beckon: BeckonStateUnit;
41
+ browse: ShellStateUnit;
42
+ mark: MarkStateUnit;
43
+ gather: GatherStateUnit;
44
+ yield: YieldStateUnit;
45
+
46
+ annotations$: Observable<Annotation[]>;
47
+ annotationGroups$: Observable<AnnotationGroups>;
48
+ entityTypes$: Observable<string[]>;
49
+ events$: Observable<StoredEventResponse[]>;
50
+ referencedBy$: Observable<ReferencedByEntry[]>;
51
+ content$: Observable<string>;
52
+ contentLoading$: Observable<boolean>;
53
+ mediaToken$: Observable<string | null>;
54
+ wizard$: Observable<WizardState>;
55
+
56
+ closeWizard(): void;
57
+ }
58
+
59
+ export function createResourceViewerPageStateUnit(
60
+ client: SemiontClient,
61
+ resourceId: ResourceId,
62
+ locale: string,
63
+ browse: ShellStateUnit,
64
+ options?: { mediaType?: string },
65
+ ): ResourceViewerPageStateUnit {
66
+ const disposer = createDisposer();
67
+
68
+ const beckon = createBeckonStateUnit(client);
69
+ const mark = createMarkStateUnit(client, resourceId);
70
+ const gather = createGatherStateUnit(client, resourceId);
71
+ const matchStateUnit = createMatchStateUnit(client, resourceId);
72
+ const yieldStateUnit = createYieldStateUnit(client, resourceId, locale);
73
+
74
+ disposer.add(beckon);
75
+ disposer.add(browse);
76
+ disposer.add(mark);
77
+ disposer.add(gather);
78
+ disposer.add(matchStateUnit);
79
+ disposer.add(yieldStateUnit);
80
+
81
+ const annotations$: Observable<Annotation[]> = client.browse.annotations(resourceId).pipe(
82
+ map((a) => a ?? []),
83
+ );
84
+
85
+ const annotationGroups$: Observable<AnnotationGroups> = annotations$.pipe(
86
+ map((anns) => {
87
+ const groups: AnnotationGroups = { highlights: [], comments: [], assessments: [], references: [], tags: [] };
88
+ for (const ann of anns) {
89
+ if (isHighlight(ann)) groups.highlights.push(ann);
90
+ else if (isComment(ann)) groups.comments.push(ann);
91
+ else if (isAssessment(ann)) groups.assessments.push(ann);
92
+ else if (isReference(ann)) groups.references.push(ann);
93
+ else if (isTag(ann)) groups.tags.push(ann);
94
+ }
95
+ return groups;
96
+ }),
97
+ );
98
+
99
+ const entityTypes$: Observable<string[]> = client.browse.entityTypes().pipe(
100
+ map((e) => e ?? []),
101
+ );
102
+
103
+ const events$: Observable<StoredEventResponse[]> = client.browse.events(resourceId).pipe(
104
+ map((e) => e ?? []),
105
+ );
106
+
107
+ const referencedBy$: Observable<ReferencedByEntry[]> = client.browse.referencedBy(resourceId).pipe(
108
+ map((r) => r ?? []),
109
+ );
110
+
111
+ const content$ = new BehaviorSubject<string>('');
112
+ const contentLoading$ = new BehaviorSubject<boolean>(false);
113
+ const mediaToken$ = new BehaviorSubject<string | null>(null);
114
+
115
+ const mediaType = options?.mediaType || 'text/plain';
116
+ const isBinaryType = mediaType.startsWith('image/') || mediaType === 'application/pdf';
117
+
118
+ if (!isBinaryType && mediaType) {
119
+ contentLoading$.next(true);
120
+ client.browse.resourceRepresentation(resourceId, { accept: mediaType })
121
+ .then(({ data }) => {
122
+ content$.next(decodeWithCharset(data, mediaType));
123
+ contentLoading$.next(false);
124
+ })
125
+ .catch(() => { contentLoading$.next(false); });
126
+ }
127
+
128
+ if (isBinaryType) {
129
+ client.auth!.mediaToken(resourceId)
130
+ .then(({ token }) => mediaToken$.next(token))
131
+ .catch(() => {});
132
+ }
133
+
134
+ const wizard$ = new BehaviorSubject<WizardState>(WIZARD_CLOSED);
135
+
136
+ const unsubscribeResource = client.subscribeToResource(resourceId);
137
+ disposer.add(unsubscribeResource);
138
+
139
+ const bindInitiateSub = client.bus.get('bind:initiate').subscribe((event) => {
140
+ wizard$.next({
141
+ open: true,
142
+ annotationId: event.annotationId,
143
+ resourceId: event.resourceId,
144
+ defaultTitle: event.defaultTitle,
145
+ entityTypes: event.entityTypes,
146
+ });
147
+ client.bus.get('gather:requested').next({
148
+ correlationId: crypto.randomUUID(),
149
+ annotationId: event.annotationId,
150
+ resourceId: event.resourceId,
151
+ options: { contextWindow: 2000 },
152
+ });
153
+ });
154
+ disposer.add(() => bindInitiateSub.unsubscribe());
155
+
156
+ return {
157
+ beckon,
158
+ browse,
159
+ mark,
160
+ gather,
161
+ yield: yieldStateUnit,
162
+ annotations$,
163
+ annotationGroups$,
164
+ entityTypes$,
165
+ events$,
166
+ referencedBy$,
167
+ content$: content$.asObservable(),
168
+ contentLoading$: contentLoading$.asObservable(),
169
+ mediaToken$: mediaToken$.asObservable(),
170
+ wizard$: wizard$.asObservable(),
171
+ closeWizard: () => wizard$.next(WIZARD_CLOSED),
172
+ dispose: () => {
173
+ wizard$.complete();
174
+ content$.complete();
175
+ contentLoading$.complete();
176
+ mediaToken$.complete();
177
+ disposer.dispose();
178
+ },
179
+ };
180
+ }
@@ -148,25 +148,10 @@
148
148
  "TaggingPanel": {
149
149
  "title": "العلامات",
150
150
  "noTags": "لا توجد علامات بعد. استخدم التوضيح بالذكاء الاصطناعي لتحديد الأدوار الهيكلية.",
151
+ "noSchemas": "لا توجد مخططات وسوم مسجلة لقاعدة المعارف هذه. يجب على مسؤول قاعدة المعارف تسجيل مخطط تحليل هيكلي واحد على الأقل (مثلاً عبر مهارة `register-tag-schemas`) قبل استخدام الوسم.",
151
152
  "annotateTags": "توضيح العلامات",
152
153
  "selectSchema": "اختيار الإطار",
153
- "schemaLegal": "التحليل القانوني (IRAC)",
154
- "schemaScientific": "الورقة العلمية (IMRAD)",
155
- "schemaArgument": "بنية الحجة (Toulmin)",
156
154
  "selectCategories": "اختيار الفئات",
157
- "categoryIssue": "القضية",
158
- "categoryRule": "القاعدة",
159
- "categoryApplication": "التطبيق",
160
- "categoryConclusion": "الخاتمة",
161
- "categoryIntroduction": "المقدمة",
162
- "categoryMethods": "المناهج",
163
- "categoryResults": "النتائج",
164
- "categoryDiscussion": "المناقشة",
165
- "categoryClaim": "الادعاء",
166
- "categoryEvidence": "الدليل",
167
- "categoryWarrant": "المبرر",
168
- "categoryCounterargument": "الحجة المضادة",
169
- "categoryRebuttal": "الدحض",
170
155
  "annotate": "توضيح",
171
156
  "annotating": "جارٍ التوضيح...",
172
157
  "cancel": "إلغاء",
@@ -148,25 +148,10 @@
148
148
  "TaggingPanel": {
149
149
  "title": "ট্যাগসমূহ",
150
150
  "noTags": "এখনো কোনো ট্যাগ নেই। কাঠামোগত ভূমিকা শনাক্ত করতে AI টীকাকরণ ব্যবহার করুন।",
151
+ "noSchemas": "এই জ্ঞান ভাণ্ডারের জন্য কোনো ট্যাগ স্কিমা নিবন্ধিত নেই। ট্যাগিং ব্যবহার করার আগে একজন KB প্রশাসককে কমপক্ষে একটি কাঠামোগত-বিশ্লেষণ স্কিমা নিবন্ধন করতে হবে (যেমন `register-tag-schemas` দক্ষতার মাধ্যমে)।",
151
152
  "annotateTags": "ট্যাগ টীকাকরণ",
152
153
  "selectSchema": "ফ্রেমওয়ার্ক নির্বাচন করুন",
153
- "schemaLegal": "আইনি বিশ্লেষণ (IRAC)",
154
- "schemaScientific": "বৈজ্ঞানিক গবেষণাপত্র (IMRAD)",
155
- "schemaArgument": "যুক্তি কাঠামো (Toulmin)",
156
154
  "selectCategories": "বিভাগ নির্বাচন করুন",
157
- "categoryIssue": "বিষয়",
158
- "categoryRule": "নিয়ম",
159
- "categoryApplication": "প্রয়োগ",
160
- "categoryConclusion": "উপসংহার",
161
- "categoryIntroduction": "ভূমিকা",
162
- "categoryMethods": "পদ্ধতি",
163
- "categoryResults": "ফলাফল",
164
- "categoryDiscussion": "আলোচনা",
165
- "categoryClaim": "দাবি",
166
- "categoryEvidence": "প্রমাণ",
167
- "categoryWarrant": "যুক্তিসূত্র",
168
- "categoryCounterargument": "প্রতিযুক্তি",
169
- "categoryRebuttal": "খণ্ডন",
170
155
  "annotate": "টীকাকরণ",
171
156
  "annotating": "টীকাকরণ হচ্ছে...",
172
157
  "cancel": "বাতিল করুন",
@@ -148,25 +148,10 @@
148
148
  "TaggingPanel": {
149
149
  "title": "Štítky",
150
150
  "noTags": "Zatím žádné štítky. Použijte AI anotaci pro identifikaci strukturálních rolí.",
151
+ "noSchemas": "Pro tuto znalostní bázi nejsou registrována žádná schémata značek. Správce KB musí zaregistrovat alespoň jedno schéma strukturální analýzy (např. pomocí dovednosti `register-tag-schemas`), než bude možné používat značkování.",
151
152
  "annotateTags": "Anotovat štítky",
152
153
  "selectSchema": "Vybrat rámec",
153
- "schemaLegal": "Právní analýza (IRAC)",
154
- "schemaScientific": "Vědecký článek (IMRAD)",
155
- "schemaArgument": "Struktura argumentace (Toulmin)",
156
154
  "selectCategories": "Vybrat kategorie",
157
- "categoryIssue": "Otázka",
158
- "categoryRule": "Pravidlo",
159
- "categoryApplication": "Aplikace",
160
- "categoryConclusion": "Závěr",
161
- "categoryIntroduction": "Úvod",
162
- "categoryMethods": "Metody",
163
- "categoryResults": "Výsledky",
164
- "categoryDiscussion": "Diskuze",
165
- "categoryClaim": "Tvrzení",
166
- "categoryEvidence": "Důkaz",
167
- "categoryWarrant": "Odůvodnění",
168
- "categoryCounterargument": "Protiargument",
169
- "categoryRebuttal": "Vyvrácení",
170
155
  "annotate": "Anotovat",
171
156
  "annotating": "Anotování...",
172
157
  "cancel": "Zrušit",
@@ -148,25 +148,10 @@
148
148
  "TaggingPanel": {
149
149
  "title": "Tags",
150
150
  "noTags": "Ingen tags endnu. Brug AI-annotering til at identificere strukturelle roller.",
151
+ "noSchemas": "Ingen tag-skemaer registreret for denne videnbase. En KB-administrator skal registrere mindst ét struktur-analyse-skema (f.eks. via `register-tag-schemas`-færdigheden), før tagging kan bruges.",
151
152
  "annotateTags": "Annoter tags",
152
153
  "selectSchema": "Vælg rammestruktur",
153
- "schemaLegal": "Juridisk analyse (IRAC)",
154
- "schemaScientific": "Videnskabelig artikel (IMRAD)",
155
- "schemaArgument": "Argumentstruktur (Toulmin)",
156
154
  "selectCategories": "Vælg kategorier",
157
- "categoryIssue": "Problem",
158
- "categoryRule": "Regel",
159
- "categoryApplication": "Anvendelse",
160
- "categoryConclusion": "Konklusion",
161
- "categoryIntroduction": "Introduktion",
162
- "categoryMethods": "Metoder",
163
- "categoryResults": "Resultater",
164
- "categoryDiscussion": "Diskussion",
165
- "categoryClaim": "Påstand",
166
- "categoryEvidence": "Dokumentation",
167
- "categoryWarrant": "Begrundelse",
168
- "categoryCounterargument": "Modargument",
169
- "categoryRebuttal": "Gendrivelse",
170
155
  "annotate": "Annoter",
171
156
  "annotating": "Annoterer...",
172
157
  "cancel": "Annuller",
@@ -148,25 +148,10 @@
148
148
  "TaggingPanel": {
149
149
  "title": "Tags",
150
150
  "noTags": "Noch keine Tags. Verwenden Sie die KI-Annotation, um strukturelle Rollen zu identifizieren.",
151
+ "noSchemas": "Für diese Wissensbasis sind keine Tag-Schemata registriert. Ein KB-Administrator muss mindestens ein Strukturanalyse-Schema registrieren (z. B. über den `register-tag-schemas`-Skill), bevor Tagging verwendet werden kann.",
151
152
  "annotateTags": "Tags annotieren",
152
153
  "selectSchema": "Framework auswählen",
153
- "schemaLegal": "Juristische Analyse (IRAC)",
154
- "schemaScientific": "Wissenschaftliche Arbeit (IMRAD)",
155
- "schemaArgument": "Argumentationsstruktur (Toulmin)",
156
154
  "selectCategories": "Kategorien auswählen",
157
- "categoryIssue": "Fragestellung",
158
- "categoryRule": "Regel",
159
- "categoryApplication": "Anwendung",
160
- "categoryConclusion": "Schlussfolgerung",
161
- "categoryIntroduction": "Einleitung",
162
- "categoryMethods": "Methoden",
163
- "categoryResults": "Ergebnisse",
164
- "categoryDiscussion": "Diskussion",
165
- "categoryClaim": "These",
166
- "categoryEvidence": "Beweis",
167
- "categoryWarrant": "Begründung",
168
- "categoryCounterargument": "Gegenargument",
169
- "categoryRebuttal": "Widerlegung",
170
155
  "annotate": "Annotieren",
171
156
  "annotating": "Wird annotiert...",
172
157
  "cancel": "Abbrechen",
@@ -148,25 +148,10 @@
148
148
  "TaggingPanel": {
149
149
  "title": "Ετικέτες",
150
150
  "noTags": "Δεν υπάρχουν ετικέτες ακόμα. Χρησιμοποιήστε τον σχολιασμό AI για να αναγνωρίσετε δομικούς ρόλους.",
151
+ "noSchemas": "Δεν υπάρχουν εγγεγραμμένα σχήματα ετικετών για αυτή τη βάση γνώσης. Ένας διαχειριστής KB πρέπει να εγγράψει τουλάχιστον ένα σχήμα δομικής ανάλυσης (π.χ. μέσω της δεξιότητας `register-tag-schemas`) πριν χρησιμοποιηθεί η ετικετοποίηση.",
151
152
  "annotateTags": "Σχολιασμός Ετικετών",
152
153
  "selectSchema": "Επιλογή Πλαισίου",
153
- "schemaLegal": "Νομική Ανάλυση (IRAC)",
154
- "schemaScientific": "Επιστημονικό Άρθρο (IMRAD)",
155
- "schemaArgument": "Δομή Επιχειρήματος (Toulmin)",
156
154
  "selectCategories": "Επιλογή Κατηγοριών",
157
- "categoryIssue": "Ζήτημα",
158
- "categoryRule": "Κανόνας",
159
- "categoryApplication": "Εφαρμογή",
160
- "categoryConclusion": "Συμπέρασμα",
161
- "categoryIntroduction": "Εισαγωγή",
162
- "categoryMethods": "Μέθοδοι",
163
- "categoryResults": "Αποτελέσματα",
164
- "categoryDiscussion": "Συζήτηση",
165
- "categoryClaim": "Ισχυρισμός",
166
- "categoryEvidence": "Τεκμήριο",
167
- "categoryWarrant": "Αιτιολόγηση",
168
- "categoryCounterargument": "Αντεπιχείρημα",
169
- "categoryRebuttal": "Ανασκευή",
170
155
  "annotate": "Σχολιασμός",
171
156
  "annotating": "Σχολιασμός...",
172
157
  "cancel": "Ακύρωση",
@@ -149,25 +149,10 @@
149
149
  "TaggingPanel": {
150
150
  "title": "Tags",
151
151
  "noTags": "No tags yet. Use AI annotation to identify structural roles.",
152
+ "noSchemas": "No tag schemas registered for this knowledge base. A KB administrator must register at least one structural-analysis schema (e.g. via the `register-tag-schemas` skill) before tagging can be used.",
152
153
  "annotateTags": "Annotate Tags",
153
154
  "selectSchema": "Select Framework",
154
- "schemaLegal": "Legal Analysis (IRAC)",
155
- "schemaScientific": "Scientific Paper (IMRAD)",
156
- "schemaArgument": "Argument Structure (Toulmin)",
157
155
  "selectCategories": "Select Categories",
158
- "categoryIssue": "Issue",
159
- "categoryRule": "Rule",
160
- "categoryApplication": "Application",
161
- "categoryConclusion": "Conclusion",
162
- "categoryIntroduction": "Introduction",
163
- "categoryMethods": "Methods",
164
- "categoryResults": "Results",
165
- "categoryDiscussion": "Discussion",
166
- "categoryClaim": "Claim",
167
- "categoryEvidence": "Evidence",
168
- "categoryWarrant": "Warrant",
169
- "categoryCounterargument": "Counterargument",
170
- "categoryRebuttal": "Rebuttal",
171
156
  "annotate": "Annotate",
172
157
  "annotating": "Annotating...",
173
158
  "cancel": "Cancel",
@@ -148,25 +148,10 @@
148
148
  "TaggingPanel": {
149
149
  "title": "Etiquetas",
150
150
  "noTags": "Aún no hay etiquetas. Usa anotación por IA para identificar roles estructurales.",
151
+ "noSchemas": "No hay esquemas de etiquetas registrados para esta base de conocimiento. Un administrador de la KB debe registrar al menos un esquema de análisis estructural (p. ej. mediante la habilidad `register-tag-schemas`) antes de poder usar el etiquetado.",
151
152
  "annotateTags": "Anotar Etiquetas",
152
153
  "selectSchema": "Seleccionar Marco",
153
- "schemaLegal": "Análisis Legal (IRAC)",
154
- "schemaScientific": "Artículo Científico (IMRAD)",
155
- "schemaArgument": "Estructura de Argumento (Toulmin)",
156
154
  "selectCategories": "Seleccionar Categorías",
157
- "categoryIssue": "Cuestión",
158
- "categoryRule": "Regla",
159
- "categoryApplication": "Aplicación",
160
- "categoryConclusion": "Conclusión",
161
- "categoryIntroduction": "Introducción",
162
- "categoryMethods": "Métodos",
163
- "categoryResults": "Resultados",
164
- "categoryDiscussion": "Discusión",
165
- "categoryClaim": "Afirmación",
166
- "categoryEvidence": "Evidencia",
167
- "categoryWarrant": "Garantía",
168
- "categoryCounterargument": "Contraargumento",
169
- "categoryRebuttal": "Refutación",
170
155
  "assist": "Asistir",
171
156
  "assisting": "Asistiendo...",
172
157
  "cancel": "Cancelar",
@@ -148,25 +148,10 @@
148
148
  "TaggingPanel": {
149
149
  "title": "برچسب‌ها",
150
150
  "noTags": "هنوز برچسبی وجود ندارد. از حاشیه‌نویسی هوش مصنوعی برای شناسایی نقش‌های ساختاری استفاده کنید.",
151
+ "noSchemas": "هیچ طرحواره برچسبی برای این پایگاه دانش ثبت نشده است. مدیر پایگاه دانش باید پیش از استفاده از برچسب‌گذاری حداقل یک طرحواره تحلیل ساختاری ثبت کند (مثلاً از طریق مهارت `register-tag-schemas`).",
151
152
  "annotateTags": "حاشیه‌نویسی برچسب‌ها",
152
153
  "selectSchema": "انتخاب چارچوب",
153
- "schemaLegal": "تحلیل حقوقی (IRAC)",
154
- "schemaScientific": "مقاله علمی (IMRAD)",
155
- "schemaArgument": "ساختار استدلال (Toulmin)",
156
154
  "selectCategories": "انتخاب دسته‌بندی‌ها",
157
- "categoryIssue": "مسئله",
158
- "categoryRule": "قاعده",
159
- "categoryApplication": "کاربرد",
160
- "categoryConclusion": "نتیجه‌گیری",
161
- "categoryIntroduction": "مقدمه",
162
- "categoryMethods": "روش‌ها",
163
- "categoryResults": "نتایج",
164
- "categoryDiscussion": "بحث",
165
- "categoryClaim": "ادعا",
166
- "categoryEvidence": "شواهد",
167
- "categoryWarrant": "توجیه",
168
- "categoryCounterargument": "استدلال مخالف",
169
- "categoryRebuttal": "پاسخ به استدلال مخالف",
170
155
  "annotate": "حاشیه‌نویسی",
171
156
  "annotating": "در حال حاشیه‌نویسی...",
172
157
  "cancel": "انصراف",