@semiont/react-ui 0.5.6 → 0.5.8
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 +1 -1
- package/dist/{PdfAnnotationCanvas.client-NIMALXNZ.js → PdfAnnotationCanvas.client-U4EVBZEV.js} +6 -52
- package/dist/PdfAnnotationCanvas.client-U4EVBZEV.js.map +1 -0
- package/dist/{ar-U2EXWUMQ.js → ar-WA47UUWA.js} +4 -8
- package/dist/ar-WA47UUWA.js.map +1 -0
- package/dist/{bn-DRJGV772.js → bn-5ANDRIU6.js} +4 -8
- package/dist/bn-5ANDRIU6.js.map +1 -0
- package/dist/chunk-O2MD7TGE.js +42 -0
- package/dist/chunk-O2MD7TGE.js.map +1 -0
- package/dist/chunk-QT7LYM72.js +234 -0
- package/dist/chunk-QT7LYM72.js.map +1 -0
- package/dist/{chunk-3Q3TUKWP.js → chunk-WHUGODTB.js} +31 -31
- package/dist/chunk-XUDKYAVC.js +21 -0
- package/dist/chunk-XUDKYAVC.js.map +1 -0
- package/dist/{chunk-K6BJDL2I.js → chunk-YBGN4ACY.js} +6 -2
- package/dist/{cs-PTWDM23V.js → cs-3RU7F4JX.js} +4 -8
- package/dist/cs-3RU7F4JX.js.map +1 -0
- package/dist/{da-KSNIKYSS.js → da-GSW5P5HG.js} +7 -11
- package/dist/da-GSW5P5HG.js.map +1 -0
- package/dist/{de-F2XBEWFY.js → de-JUAUYF4Z.js} +4 -8
- package/dist/de-JUAUYF4Z.js.map +1 -0
- package/dist/{el-DLD2GWAP.js → el-JNLWCKEC.js} +4 -8
- package/dist/el-JNLWCKEC.js.map +1 -0
- package/dist/{en-L45VK7BS.js → en-RBN2GUHF.js} +2 -2
- package/dist/{es-WLPYWGB5.js → es-WPOX225H.js} +12 -16
- package/dist/es-WPOX225H.js.map +1 -0
- package/dist/{fa-BAXHSDZG.js → fa-TAEP6N77.js} +4 -8
- package/dist/fa-TAEP6N77.js.map +1 -0
- package/dist/{fi-FCHSYVOT.js → fi-ZVZHANSP.js} +4 -8
- package/dist/fi-ZVZHANSP.js.map +1 -0
- package/dist/{fr-3UERBSL6.js → fr-VLZW7M4N.js} +4 -8
- package/dist/fr-VLZW7M4N.js.map +1 -0
- package/dist/{he-F6F3FV2K.js → he-QFAFYA77.js} +4 -8
- package/dist/he-QFAFYA77.js.map +1 -0
- package/dist/{hi-4BK6IK7Q.js → hi-AO3DQPCV.js} +4 -8
- package/dist/hi-AO3DQPCV.js.map +1 -0
- package/dist/{id-7ECCWP3J.js → id-GTXF42WM.js} +4 -8
- package/dist/id-GTXF42WM.js.map +1 -0
- package/dist/index.css +97 -0
- package/dist/index.css.map +1 -1
- package/dist/index.d.ts +52 -23
- package/dist/index.js +2835 -3015
- package/dist/index.js.map +1 -1
- package/dist/integrations/css-modules-helper.d.ts +118 -0
- package/dist/integrations/css-modules-helper.js +117 -0
- package/dist/integrations/css-modules-helper.js.map +1 -0
- package/dist/integrations/styled-components-theme.d.ts +258 -0
- package/dist/integrations/styled-components-theme.js +1774 -0
- package/dist/integrations/styled-components-theme.js.map +1 -0
- package/dist/{it-234Z6XK6.js → it-AS5GM232.js} +4 -8
- package/dist/it-AS5GM232.js.map +1 -0
- package/dist/{ja-PJWQI4OQ.js → ja-GZ4HLUOF.js} +4 -8
- package/dist/ja-GZ4HLUOF.js.map +1 -0
- package/dist/{ko-APUEW2RS.js → ko-A4XUXFGJ.js} +4 -8
- package/dist/ko-A4XUXFGJ.js.map +1 -0
- package/dist/{ms-PJBZWZWD.js → ms-YBAO3S6K.js} +4 -8
- package/dist/ms-YBAO3S6K.js.map +1 -0
- package/dist/{nl-L4C3ZBCU.js → nl-3TJGIIIU.js} +5 -12
- package/dist/nl-3TJGIIIU.js.map +1 -0
- package/dist/{no-QE5N5KNG.js → no-4AXIQPRQ.js} +21 -25
- package/dist/no-4AXIQPRQ.js.map +1 -0
- package/dist/{pl-5Q2D23PD.js → pl-5GP6GKXO.js} +4 -8
- package/dist/pl-5GP6GKXO.js.map +1 -0
- package/dist/{pt-AIGUOIOC.js → pt-26Y6JFG5.js} +119 -123
- package/dist/pt-26Y6JFG5.js.map +1 -0
- package/dist/{ro-T56CSHTY.js → ro-C7UXFRWJ.js} +4 -8
- package/dist/ro-C7UXFRWJ.js.map +1 -0
- package/dist/{sv-L4TJQ2UH.js → sv-44DVD76T.js} +44 -48
- package/dist/sv-44DVD76T.js.map +1 -0
- package/dist/test-utils.js +3 -3
- package/dist/test-utils.js.map +1 -1
- package/dist/{th-6O7Y6O2Q.js → th-GIQRTBOY.js} +4 -8
- package/dist/th-GIQRTBOY.js.map +1 -0
- package/dist/{tr-D4CQCSNO.js → tr-WMQWO4D6.js} +4 -8
- package/dist/tr-WMQWO4D6.js.map +1 -0
- package/dist/{uk-2HMQG6ND.js → uk-I7ML6RGG.js} +4 -8
- package/dist/uk-I7ML6RGG.js.map +1 -0
- package/dist/{vi-XVJ4RUEJ.js → vi-FGWECASQ.js} +4 -8
- package/dist/vi-FGWECASQ.js.map +1 -0
- package/dist/{zh-K2KDPGHK.js → zh-L5FN73XC.js} +4 -8
- package/dist/zh-L5FN73XC.js.map +1 -0
- package/package.json +7 -6
- package/src/components/ProtectedErrorBoundary.css +119 -0
- package/src/components/ProtectedErrorBoundary.tsx +18 -13
- package/src/components/modals/__tests__/ResourceSearchModal.test.tsx +1 -1
- package/src/components/modals/__tests__/SearchModal.search-wiring.test.tsx +1 -1
- package/src/components/resource/AnnotateView.tsx +35 -37
- package/src/components/resource/BrowseView.tsx +31 -31
- package/src/components/resource/__tests__/AnnotationHistory.test.tsx +0 -1
- package/src/components/resource/__tests__/BrowseView.test.tsx +4 -8
- package/src/components/resource/__tests__/HistoryEvent.test.tsx +0 -1
- package/src/components/resource/__tests__/event-formatting.test.ts +1 -1
- package/src/components/resource/panels/CollaborationPanel.tsx +1 -1
- package/src/components/resource/panels/JsonLdPanel.tsx +33 -16
- package/src/components/resource/panels/__tests__/AssessmentEntry.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/CommentEntry.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/HighlightEntry.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/JsonLdPanel.test.tsx +95 -424
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/TagEntry.test.tsx +1 -1
- package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +1 -1
- package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +7 -10
- package/src/features/admin-exchange/__tests__/ImportProgress.test.tsx +38 -27
- package/src/features/admin-exchange/components/ImportProgress.tsx +28 -34
- package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +11 -9
- package/src/features/resource-compose/components/ResourceComposePage.tsx +36 -9
- package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -8
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +7 -5
- package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +37 -0
- package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +9 -5
- package/src/styles/features/exchange.css +0 -30
- package/src/styles/index.css +1 -0
- package/translations/ar.json +1 -3
- package/translations/bn.json +1 -3
- package/translations/cs.json +1 -3
- package/translations/da.json +4 -6
- package/translations/de.json +1 -3
- package/translations/el.json +1 -3
- package/translations/es.json +9 -11
- package/translations/fa.json +1 -3
- package/translations/fi.json +1 -3
- package/translations/fr.json +1 -3
- package/translations/he.json +1 -3
- package/translations/hi.json +1 -3
- package/translations/id.json +1 -3
- package/translations/it.json +1 -3
- package/translations/ja.json +1 -3
- package/translations/ko.json +1 -3
- package/translations/ms.json +1 -3
- package/translations/nl.json +2 -7
- package/translations/no.json +18 -20
- package/translations/pl.json +1 -3
- package/translations/pt.json +116 -118
- package/translations/ro.json +1 -3
- package/translations/sv.json +41 -43
- package/translations/th.json +1 -3
- package/translations/tr.json +1 -3
- package/translations/uk.json +1 -3
- package/translations/vi.json +1 -3
- package/translations/zh.json +1 -3
- package/dist/PdfAnnotationCanvas.client-NIMALXNZ.js.map +0 -1
- package/dist/ar-U2EXWUMQ.js.map +0 -1
- package/dist/bn-DRJGV772.js.map +0 -1
- package/dist/cs-PTWDM23V.js.map +0 -1
- package/dist/da-KSNIKYSS.js.map +0 -1
- package/dist/de-F2XBEWFY.js.map +0 -1
- package/dist/el-DLD2GWAP.js.map +0 -1
- package/dist/es-WLPYWGB5.js.map +0 -1
- package/dist/fa-BAXHSDZG.js.map +0 -1
- package/dist/fi-FCHSYVOT.js.map +0 -1
- package/dist/fr-3UERBSL6.js.map +0 -1
- package/dist/he-F6F3FV2K.js.map +0 -1
- package/dist/hi-4BK6IK7Q.js.map +0 -1
- package/dist/id-7ECCWP3J.js.map +0 -1
- package/dist/it-234Z6XK6.js.map +0 -1
- package/dist/ja-PJWQI4OQ.js.map +0 -1
- package/dist/ko-APUEW2RS.js.map +0 -1
- package/dist/ms-PJBZWZWD.js.map +0 -1
- package/dist/nl-L4C3ZBCU.js.map +0 -1
- package/dist/no-QE5N5KNG.js.map +0 -1
- package/dist/pl-5Q2D23PD.js.map +0 -1
- package/dist/pt-AIGUOIOC.js.map +0 -1
- package/dist/ro-T56CSHTY.js.map +0 -1
- package/dist/sv-L4TJQ2UH.js.map +0 -1
- package/dist/th-6O7Y6O2Q.js.map +0 -1
- package/dist/tr-D4CQCSNO.js.map +0 -1
- package/dist/uk-2HMQG6ND.js.map +0 -1
- package/dist/vi-XVJ4RUEJ.js.map +0 -1
- package/dist/zh-K2KDPGHK.js.map +0 -1
- /package/dist/{chunk-3Q3TUKWP.js.map → chunk-WHUGODTB.js.map} +0 -0
- /package/dist/{chunk-K6BJDL2I.js.map → chunk-YBGN4ACY.js.map} +0 -0
- /package/dist/{en-L45VK7BS.js.map → en-RBN2GUHF.js.map} +0 -0
|
@@ -45,16 +45,12 @@ const createProps = (overrides?: Partial<AdminExchangePageProps>): AdminExchange
|
|
|
45
45
|
},
|
|
46
46
|
progress: {
|
|
47
47
|
phaseStarted: 'Starting…',
|
|
48
|
-
phaseEntityTypes: 'Entity types…',
|
|
49
|
-
phaseResources: 'Resources…',
|
|
50
|
-
phaseAnnotations: 'Annotations…',
|
|
51
48
|
phaseComplete: 'Complete',
|
|
52
49
|
phaseError: 'Failed',
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
blobs: 'Blobs',
|
|
50
|
+
statsEventsReplayed: 'Events replayed',
|
|
51
|
+
statsResourcesCreated: 'Resources created',
|
|
52
|
+
statsAnnotationsCreated: 'Annotations created',
|
|
53
|
+
statsEntityTypesAdded: 'Entity types added',
|
|
58
54
|
},
|
|
59
55
|
},
|
|
60
56
|
ToolbarPanels: () => <div data-testid="toolbar-panels" />,
|
|
@@ -100,10 +96,11 @@ describe('AdminExchangePage', () => {
|
|
|
100
96
|
it('renders ImportProgress with backup result on completion', () => {
|
|
101
97
|
render(<AdminExchangePage {...createProps({
|
|
102
98
|
importPhase: 'complete',
|
|
103
|
-
importResult: { stats: {
|
|
99
|
+
importResult: { stats: { eventsReplayed: 42, resourcesCreated: 5, annotationsCreated: 12, entityTypesAdded: 3 } },
|
|
104
100
|
})} />);
|
|
105
101
|
expect(screen.getByText('Complete')).toBeInTheDocument();
|
|
106
|
-
expect(screen.getByText('
|
|
102
|
+
expect(screen.getByText('42')).toBeInTheDocument();
|
|
103
|
+
expect(screen.getByText('Events replayed')).toBeInTheDocument();
|
|
107
104
|
});
|
|
108
105
|
|
|
109
106
|
it('applies panel-open class when common panel is active', () => {
|
|
@@ -9,16 +9,12 @@ import type { ImportProgressProps } from '../components/ImportProgress';
|
|
|
9
9
|
|
|
10
10
|
const translations: ImportProgressProps['translations'] = {
|
|
11
11
|
phaseStarted: 'Starting restore…',
|
|
12
|
-
phaseEntityTypes: 'Adding entity types…',
|
|
13
|
-
phaseResources: 'Creating resources…',
|
|
14
|
-
phaseAnnotations: 'Creating annotations…',
|
|
15
12
|
phaseComplete: 'Restore complete',
|
|
16
13
|
phaseError: 'Restore failed',
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
blobs: 'Content blobs',
|
|
14
|
+
statsEventsReplayed: 'Events replayed',
|
|
15
|
+
statsResourcesCreated: 'Resources created',
|
|
16
|
+
statsAnnotationsCreated: 'Annotations created',
|
|
17
|
+
statsEntityTypesAdded: 'Entity types added',
|
|
22
18
|
};
|
|
23
19
|
|
|
24
20
|
describe('ImportProgress', () => {
|
|
@@ -33,8 +29,8 @@ describe('ImportProgress', () => {
|
|
|
33
29
|
});
|
|
34
30
|
|
|
35
31
|
it('renders message during active phases', () => {
|
|
36
|
-
render(<ImportProgress phase="
|
|
37
|
-
expect(screen.getByText('
|
|
32
|
+
render(<ImportProgress phase="started" message="Restoring backup..." translations={translations} />);
|
|
33
|
+
expect(screen.getByText('Restoring backup...')).toBeInTheDocument();
|
|
38
34
|
});
|
|
39
35
|
|
|
40
36
|
it('does not render message during complete phase', () => {
|
|
@@ -61,44 +57,59 @@ describe('ImportProgress', () => {
|
|
|
61
57
|
expect(screen.getByText('Connection failed')).toBeInTheDocument();
|
|
62
58
|
});
|
|
63
59
|
|
|
64
|
-
it('renders backup
|
|
60
|
+
it('renders backup restore stats nested under result.stats', () => {
|
|
65
61
|
render(<ImportProgress
|
|
66
62
|
phase="complete"
|
|
67
|
-
result={{ stats: {
|
|
63
|
+
result={{ stats: { eventsReplayed: 42, resourcesCreated: 5, annotationsCreated: 12, entityTypesAdded: 3 } }}
|
|
68
64
|
translations={translations}
|
|
69
65
|
/>);
|
|
70
|
-
expect(screen.getByText('5')).toBeInTheDocument();
|
|
71
|
-
expect(screen.getByText('Event streams')).toBeInTheDocument();
|
|
72
66
|
expect(screen.getByText('42')).toBeInTheDocument();
|
|
73
|
-
expect(screen.getByText('Events')).toBeInTheDocument();
|
|
67
|
+
expect(screen.getByText('Events replayed')).toBeInTheDocument();
|
|
68
|
+
expect(screen.getByText('5')).toBeInTheDocument();
|
|
69
|
+
expect(screen.getByText('Resources created')).toBeInTheDocument();
|
|
70
|
+
expect(screen.getByText('12')).toBeInTheDocument();
|
|
71
|
+
expect(screen.getByText('Annotations created')).toBeInTheDocument();
|
|
74
72
|
expect(screen.getByText('3')).toBeInTheDocument();
|
|
75
|
-
expect(screen.getByText('
|
|
73
|
+
expect(screen.getByText('Entity types added')).toBeInTheDocument();
|
|
76
74
|
});
|
|
77
75
|
|
|
78
|
-
it('renders
|
|
79
|
-
|
|
76
|
+
it('renders linked-data import stats nested under result.stats', () => {
|
|
77
|
+
render(<ImportProgress
|
|
80
78
|
phase="complete"
|
|
81
|
-
result={{
|
|
79
|
+
result={{ stats: { resourcesCreated: 5, annotationsCreated: 12, entityTypesAdded: 3 } }}
|
|
82
80
|
translations={translations}
|
|
83
81
|
/>);
|
|
84
|
-
expect(screen.getByText('
|
|
85
|
-
expect(
|
|
82
|
+
expect(screen.getByText('5')).toBeInTheDocument();
|
|
83
|
+
expect(screen.getByText('Resources created')).toBeInTheDocument();
|
|
84
|
+
expect(screen.getByText('12')).toBeInTheDocument();
|
|
85
|
+
expect(screen.getByText('Annotations created')).toBeInTheDocument();
|
|
86
|
+
expect(screen.getByText('3')).toBeInTheDocument();
|
|
87
|
+
expect(screen.getByText('Entity types added')).toBeInTheDocument();
|
|
86
88
|
});
|
|
87
89
|
|
|
88
|
-
it('
|
|
90
|
+
it('does not render result section when result lacks a stats object', () => {
|
|
89
91
|
const { container } = render(<ImportProgress
|
|
90
92
|
phase="complete"
|
|
91
|
-
result={{
|
|
93
|
+
result={{ resourcesCreated: 5 }}
|
|
94
|
+
translations={translations}
|
|
95
|
+
/>);
|
|
96
|
+
expect(container.querySelector('.semiont-exchange__result')).not.toBeInTheDocument();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('renders raw key for unknown stats', () => {
|
|
100
|
+
render(<ImportProgress
|
|
101
|
+
phase="complete"
|
|
102
|
+
result={{ stats: { somethingNew: 7 } }}
|
|
92
103
|
translations={translations}
|
|
93
104
|
/>);
|
|
94
|
-
expect(screen.getByText('
|
|
95
|
-
expect(
|
|
105
|
+
expect(screen.getByText('7')).toBeInTheDocument();
|
|
106
|
+
expect(screen.getByText('somethingNew')).toBeInTheDocument();
|
|
96
107
|
});
|
|
97
108
|
|
|
98
109
|
it('does not render result section during non-complete phases', () => {
|
|
99
110
|
const { container } = render(<ImportProgress
|
|
100
|
-
phase="
|
|
101
|
-
result={{ stats: {
|
|
111
|
+
phase="started"
|
|
112
|
+
result={{ stats: { eventsReplayed: 42, resourcesCreated: 5, annotationsCreated: 12, entityTypesAdded: 3 } }}
|
|
102
113
|
translations={translations}
|
|
103
114
|
/>);
|
|
104
115
|
expect(container.querySelector('.semiont-exchange__result')).not.toBeInTheDocument();
|
|
@@ -6,16 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
export interface ImportProgressTranslations {
|
|
8
8
|
phaseStarted: string;
|
|
9
|
-
phaseEntityTypes: string;
|
|
10
|
-
phaseResources: string;
|
|
11
|
-
phaseAnnotations: string;
|
|
12
9
|
phaseComplete: string;
|
|
13
10
|
phaseError: string;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
blobs: string;
|
|
11
|
+
statsEventsReplayed: string;
|
|
12
|
+
statsResourcesCreated: string;
|
|
13
|
+
statsAnnotationsCreated: string;
|
|
14
|
+
statsEntityTypesAdded: string;
|
|
19
15
|
}
|
|
20
16
|
|
|
21
17
|
export interface ImportProgressProps {
|
|
@@ -27,13 +23,17 @@ export interface ImportProgressProps {
|
|
|
27
23
|
|
|
28
24
|
const PHASE_LABELS: Record<string, keyof ImportProgressTranslations> = {
|
|
29
25
|
started: 'phaseStarted',
|
|
30
|
-
'entity-types': 'phaseEntityTypes',
|
|
31
|
-
resources: 'phaseResources',
|
|
32
|
-
annotations: 'phaseAnnotations',
|
|
33
26
|
complete: 'phaseComplete',
|
|
34
27
|
error: 'phaseError',
|
|
35
28
|
};
|
|
36
29
|
|
|
30
|
+
const STAT_LABELS: Record<string, keyof ImportProgressTranslations> = {
|
|
31
|
+
eventsReplayed: 'statsEventsReplayed',
|
|
32
|
+
resourcesCreated: 'statsResourcesCreated',
|
|
33
|
+
annotationsCreated: 'statsAnnotationsCreated',
|
|
34
|
+
entityTypesAdded: 'statsEntityTypesAdded',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
37
|
export function ImportProgress({ phase, message, result, translations: t }: ImportProgressProps) {
|
|
38
38
|
const labelKey = PHASE_LABELS[phase];
|
|
39
39
|
const phaseLabel = labelKey ? t[labelKey] : phase;
|
|
@@ -41,6 +41,13 @@ export function ImportProgress({ phase, message, result, translations: t }: Impo
|
|
|
41
41
|
const isComplete = phase === 'complete';
|
|
42
42
|
const isError = phase === 'error';
|
|
43
43
|
|
|
44
|
+
const stats = result?.stats != null && typeof result.stats === 'object'
|
|
45
|
+
? (result.stats as Record<string, unknown>)
|
|
46
|
+
: undefined;
|
|
47
|
+
const statEntries = stats
|
|
48
|
+
? Object.entries(stats).filter((entry): entry is [string, number] => typeof entry[1] === 'number')
|
|
49
|
+
: [];
|
|
50
|
+
|
|
44
51
|
return (
|
|
45
52
|
<div className="semiont-exchange__progress">
|
|
46
53
|
<div className={`semiont-exchange__phase-label${isError ? ' semiont-exchange__phase-label--error' : isComplete ? ' semiont-exchange__phase-label--complete' : ''}`}>
|
|
@@ -51,30 +58,17 @@ export function ImportProgress({ phase, message, result, translations: t }: Impo
|
|
|
51
58
|
<p className="semiont-exchange__progress-message">{message}</p>
|
|
52
59
|
)}
|
|
53
60
|
|
|
54
|
-
{isComplete &&
|
|
61
|
+
{isComplete && statEntries.length > 0 && (
|
|
55
62
|
<div className="semiont-exchange__result">
|
|
56
|
-
{
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
<span className="semiont-exchange__result-value">{value}</span>
|
|
66
|
-
<span className="semiont-exchange__result-label">{label}</span>
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
69
|
-
})}
|
|
70
|
-
</>
|
|
71
|
-
)}
|
|
72
|
-
|
|
73
|
-
{result.hashChainValid !== undefined && (
|
|
74
|
-
<div className={`semiont-exchange__hash-badge${result.hashChainValid ? ' semiont-exchange__hash-badge--valid' : ' semiont-exchange__hash-badge--invalid'}`}>
|
|
75
|
-
{result.hashChainValid ? t.hashChainValid : t.hashChainInvalid}
|
|
76
|
-
</div>
|
|
77
|
-
)}
|
|
63
|
+
{statEntries.map(([key, value]) => {
|
|
64
|
+
const statKey = STAT_LABELS[key];
|
|
65
|
+
return (
|
|
66
|
+
<div key={key} className="semiont-exchange__result-stat">
|
|
67
|
+
<span className="semiont-exchange__result-value">{value}</span>
|
|
68
|
+
<span className="semiont-exchange__result-label">{statKey ? t[statKey] : key}</span>
|
|
69
|
+
</div>
|
|
70
|
+
);
|
|
71
|
+
})}
|
|
78
72
|
</div>
|
|
79
73
|
)}
|
|
80
74
|
|
|
@@ -45,16 +45,12 @@ const createProps = (overrides?: Partial<LinkedDataPageProps>): LinkedDataPagePr
|
|
|
45
45
|
},
|
|
46
46
|
progress: {
|
|
47
47
|
phaseStarted: 'Starting…',
|
|
48
|
-
phaseEntityTypes: 'Entity types…',
|
|
49
|
-
phaseResources: 'Resources…',
|
|
50
|
-
phaseAnnotations: 'Annotations…',
|
|
51
48
|
phaseComplete: 'Complete',
|
|
52
49
|
phaseError: 'Failed',
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
blobs: 'Entity types',
|
|
50
|
+
statsEventsReplayed: 'Events replayed',
|
|
51
|
+
statsResourcesCreated: 'Resources created',
|
|
52
|
+
statsAnnotationsCreated: 'Annotations created',
|
|
53
|
+
statsEntityTypesAdded: 'Entity types added',
|
|
58
54
|
},
|
|
59
55
|
},
|
|
60
56
|
ToolbarPanels: () => <div data-testid="toolbar-panels" />,
|
|
@@ -100,9 +96,15 @@ describe('LinkedDataPage', () => {
|
|
|
100
96
|
it('renders ImportProgress with result on completion', () => {
|
|
101
97
|
render(<LinkedDataPage {...createProps({
|
|
102
98
|
importPhase: 'complete',
|
|
103
|
-
importResult: { resourcesCreated: 5, annotationsCreated: 12, entityTypesAdded: 3 },
|
|
99
|
+
importResult: { stats: { resourcesCreated: 5, annotationsCreated: 12, entityTypesAdded: 3 } },
|
|
104
100
|
})} />);
|
|
105
101
|
expect(screen.getByText('Complete')).toBeInTheDocument();
|
|
102
|
+
expect(screen.getByText('5')).toBeInTheDocument();
|
|
103
|
+
expect(screen.getByText('Resources created')).toBeInTheDocument();
|
|
104
|
+
expect(screen.getByText('12')).toBeInTheDocument();
|
|
105
|
+
expect(screen.getByText('Annotations created')).toBeInTheDocument();
|
|
106
|
+
expect(screen.getByText('3')).toBeInTheDocument();
|
|
107
|
+
expect(screen.getByText('Entity types added')).toBeInTheDocument();
|
|
106
108
|
});
|
|
107
109
|
|
|
108
110
|
it('applies panel-open class when common panel is active', () => {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import React, { useState, useEffect } from 'react';
|
|
10
10
|
import type { GatheredContext } from '@semiont/core';
|
|
11
|
-
import {
|
|
11
|
+
import { capabilitiesOf, AUTHORABLE_MEDIA_TYPES, MEDIA_TYPES, mediaTypeForExtension, isSupportedMediaType, LOCALES } from '@semiont/core';
|
|
12
12
|
import type { UploadProgress } from '@semiont/sdk';
|
|
13
13
|
import { type CloneData, type ReferenceData } from '../state/compose-page-state-unit';
|
|
14
14
|
import { COMMON_PANELS, type ToolbarPanelType } from '../../../state/shell-state-unit';
|
|
@@ -17,6 +17,29 @@ import { CodeMirrorRenderer } from '../../../components/CodeMirrorRenderer';
|
|
|
17
17
|
import { useFormAnnouncements } from '../../../components/LiveRegion';
|
|
18
18
|
import { UploadProgressBar } from './UploadProgressBar';
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Big tent: every registry type is uploadable. The `accept` hint lists all
|
|
22
|
+
* known extensions — it's only a hint; `handleFileUpload` never rejects.
|
|
23
|
+
*/
|
|
24
|
+
const UPLOAD_ACCEPT = Object.values(MEDIA_TYPES).map((c) => c.extension).join(',');
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Detection chain for uploaded files (same spirit as the CLI):
|
|
28
|
+
* 1. the browser's `file.type`, if it names a registry member;
|
|
29
|
+
* 2. otherwise the filename extension (`file.type` is routinely empty for
|
|
30
|
+
* `.md` and friends);
|
|
31
|
+
* 3. otherwise `application/octet-stream` — itself a registry member, so
|
|
32
|
+
* under the big tent every file is uploadable.
|
|
33
|
+
*/
|
|
34
|
+
function detectUploadMediaType(file: File): string {
|
|
35
|
+
if (file.type && isSupportedMediaType(file.type)) return file.type;
|
|
36
|
+
if (file.name.includes('.')) {
|
|
37
|
+
const byExt = mediaTypeForExtension(file.name.slice(file.name.lastIndexOf('.')));
|
|
38
|
+
if (byExt) return byExt;
|
|
39
|
+
}
|
|
40
|
+
return 'application/octet-stream';
|
|
41
|
+
}
|
|
42
|
+
|
|
20
43
|
export interface ResourceComposePageProps {
|
|
21
44
|
mode: 'new' | 'clone' | 'reference';
|
|
22
45
|
cloneData?: CloneData | null;
|
|
@@ -170,8 +193,9 @@ export function ResourceComposePage({
|
|
|
170
193
|
const file = e.target.files?.[0];
|
|
171
194
|
if (!file) return;
|
|
172
195
|
|
|
196
|
+
const detectedMediaType = detectUploadMediaType(file);
|
|
173
197
|
setUploadedFile(file);
|
|
174
|
-
setFileMimeType(
|
|
198
|
+
setFileMimeType(detectedMediaType);
|
|
175
199
|
setInputMethod('upload');
|
|
176
200
|
|
|
177
201
|
// Set file name as default resource name if empty
|
|
@@ -180,8 +204,9 @@ export function ResourceComposePage({
|
|
|
180
204
|
setNewResourceName(nameWithoutExt);
|
|
181
205
|
}
|
|
182
206
|
|
|
183
|
-
//
|
|
184
|
-
|
|
207
|
+
// Images and PDFs get an object-URL preview; everything else reads as text.
|
|
208
|
+
const detectedRender = capabilitiesOf(detectedMediaType)?.render;
|
|
209
|
+
if (detectedRender === 'image' || detectedRender === 'pdf') {
|
|
185
210
|
const previewUrl = URL.createObjectURL(file);
|
|
186
211
|
setFilePreviewUrl(previewUrl);
|
|
187
212
|
} else {
|
|
@@ -521,7 +546,7 @@ export function ResourceComposePage({
|
|
|
521
546
|
<div className="semiont-form__upload-area">
|
|
522
547
|
<input
|
|
523
548
|
type="file"
|
|
524
|
-
accept=
|
|
549
|
+
accept={UPLOAD_ACCEPT}
|
|
525
550
|
onChange={handleFileUpload}
|
|
526
551
|
className="semiont-form__upload-input"
|
|
527
552
|
disabled={isCreating}
|
|
@@ -563,7 +588,7 @@ export function ResourceComposePage({
|
|
|
563
588
|
)}
|
|
564
589
|
|
|
565
590
|
{/* Image Preview */}
|
|
566
|
-
{uploadedFile && filePreviewUrl &&
|
|
591
|
+
{uploadedFile && filePreviewUrl && capabilitiesOf(fileMimeType)?.render === 'image' && (
|
|
567
592
|
<div className="semiont-form__image-preview">
|
|
568
593
|
<p className="semiont-form__image-preview-label">Preview:</p>
|
|
569
594
|
<div className="semiont-form__image-preview-container">
|
|
@@ -594,9 +619,11 @@ export function ResourceComposePage({
|
|
|
594
619
|
disabled={isCreating}
|
|
595
620
|
className="semiont-select"
|
|
596
621
|
>
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
622
|
+
{AUTHORABLE_MEDIA_TYPES.map((mt) => (
|
|
623
|
+
<option key={mt} value={mt}>
|
|
624
|
+
{capabilitiesOf(mt)?.label ?? mt} ({mt})
|
|
625
|
+
</option>
|
|
626
|
+
))}
|
|
600
627
|
</select>
|
|
601
628
|
</div>
|
|
602
629
|
)}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { BehaviorSubject, type Observable, map } from 'rxjs';
|
|
2
|
-
import type { GatheredContext, AnnotationId,
|
|
2
|
+
import type { GatheredContext, AnnotationId, AccessToken, ResourceDescriptor, ResourceId } from '@semiont/core';
|
|
3
3
|
import { resourceId as makeResourceId, annotationId as makeAnnotationId } from '@semiont/core';
|
|
4
4
|
import { createDisposer } from '@semiont/sdk';
|
|
5
5
|
import type { StateUnit } from '@semiont/sdk';
|
|
6
6
|
import type { ShellStateUnit } from '../../../state/shell-state-unit';
|
|
7
7
|
import type { SemiontClient } from '@semiont/sdk';
|
|
8
|
-
import {
|
|
8
|
+
import { decodeWithCharset, extensionForMediaType } from '@semiont/core';
|
|
9
9
|
import type { UploadProgress } from '@semiont/sdk';
|
|
10
10
|
|
|
11
11
|
export type ComposeMode = 'new' | 'clone' | 'reference';
|
|
@@ -108,11 +108,8 @@ export function createComposePageStateUnit(
|
|
|
108
108
|
const tokenResult = await client.yield.fromToken(params.token!);
|
|
109
109
|
if (tokenResult && auth) {
|
|
110
110
|
const rId = makeResourceId(tokenResult['@id']);
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
accept: mediaType as ContentFormat,
|
|
114
|
-
});
|
|
115
|
-
const content = decodeWithCharset(data, mediaType);
|
|
111
|
+
const { data, contentType } = await client.browse.resourceRepresentation(rId);
|
|
112
|
+
const content = decodeWithCharset(data, contentType);
|
|
116
113
|
cloneData$.next({ sourceResource: tokenResult, sourceContent: content });
|
|
117
114
|
}
|
|
118
115
|
} catch {
|
|
@@ -143,7 +140,7 @@ export function createComposePageStateUnit(
|
|
|
143
140
|
mimeType = saveParams.format ?? 'application/octet-stream';
|
|
144
141
|
} else {
|
|
145
142
|
const blob = new Blob([saveParams.content || ''], { type: saveParams.format ?? 'application/octet-stream' });
|
|
146
|
-
const extension = saveParams.format
|
|
143
|
+
const extension = extensionForMediaType(saveParams.format ?? 'application/octet-stream');
|
|
147
144
|
fileToUpload = new File([blob], saveParams.name + extension, { type: saveParams.format ?? 'application/octet-stream' });
|
|
148
145
|
mimeType = saveParams.format ?? 'application/octet-stream';
|
|
149
146
|
}
|
|
@@ -9,8 +9,7 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|
|
9
9
|
import type { components, ResourceDescriptor, ResourceId, GatheredContext, EventMap } from '@semiont/core';
|
|
10
10
|
import type { ConnectionState } from '@semiont/core';
|
|
11
11
|
import { annotationId } from '@semiont/core';
|
|
12
|
-
import { getLanguage, getPrimaryRepresentation, getPrimaryMediaType } from '@semiont/core';
|
|
13
|
-
import { getMimeCategory } from '@semiont/core';
|
|
12
|
+
import { getLanguage, getPrimaryRepresentation, getPrimaryMediaType, capabilitiesOf } from '@semiont/core';
|
|
14
13
|
import { ANNOTATORS } from '@semiont/react-ui';
|
|
15
14
|
import { ErrorBoundary } from '@semiont/react-ui';
|
|
16
15
|
import { AnnotationHistory } from '@semiont/react-ui';
|
|
@@ -141,9 +140,12 @@ export function ResourceViewerPage({
|
|
|
141
140
|
const { hoverDelayMs } = useHoverDelay();
|
|
142
141
|
const { triggerSparkleAnimation, clearNewAnnotationId } = useResourceAnnotations();
|
|
143
142
|
|
|
144
|
-
//
|
|
143
|
+
// Render mode chooses the content path: 'text' decodes inline; 'image'
|
|
144
|
+
// and 'pdf' go through the media-token (binary) path. 'none'/registry-miss
|
|
145
|
+
// fall to the text path harmlessly — the viewer shows metadata + download.
|
|
145
146
|
const resourceMediaType = getPrimaryMediaType(resource) || 'text/plain';
|
|
146
|
-
const
|
|
147
|
+
const renderMode = capabilitiesOf(resourceMediaType)?.render;
|
|
148
|
+
const isBinary = renderMode === 'image' || renderMode === 'pdf';
|
|
147
149
|
|
|
148
150
|
// Text path: fetch and decode representation (disabled for binary — mediaToken path handles those)
|
|
149
151
|
const { content: textContent, loading: textLoading } = useResourceContent(rUri, resource, !isBinary);
|
|
@@ -577,7 +579,7 @@ export function ResourceViewerPage({
|
|
|
577
579
|
|
|
578
580
|
{/* JSON-LD Panel */}
|
|
579
581
|
{activePanel === 'jsonld' && (
|
|
580
|
-
<JsonLdPanel
|
|
582
|
+
<JsonLdPanel resourceId={rUri} />
|
|
581
583
|
)}
|
|
582
584
|
</ToolbarPanels>
|
|
583
585
|
|
package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts
CHANGED
|
@@ -150,6 +150,43 @@ describe('createResourceViewerPageStateUnit', () => {
|
|
|
150
150
|
stateUnit.dispose();
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
+
// isBinaryType keys off textExtractionOf(...) !== 'decode', not render mode:
|
|
154
|
+
// storage-tier binary (ZIP, gif/webp) must be fetched as bytes, never
|
|
155
|
+
// decoded as text — the client-side twin of the Phase 3a serving-side fix.
|
|
156
|
+
it('fetches storage-tier binary (ZIP) as bytes, never the text-decode path', async () => {
|
|
157
|
+
const mediaToken = vi.fn().mockResolvedValue({ token: 'tok-zip' });
|
|
158
|
+
const resourceRepresentation = vi.fn();
|
|
159
|
+
tc = clientWithNamespaces({ mediaToken, resourceRepresentation });
|
|
160
|
+
const stateUnit = createResourceViewerPageStateUnit(
|
|
161
|
+
tc.client, RID, 'en', mockBrowse(),
|
|
162
|
+
{ mediaType: 'application/zip' },
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const token = await firstValueFrom(stateUnit.mediaToken$.pipe(filter((t) => t !== null)));
|
|
166
|
+
expect(token).toBe('tok-zip');
|
|
167
|
+
expect(resourceRepresentation).not.toHaveBeenCalled();
|
|
168
|
+
|
|
169
|
+
stateUnit.dispose();
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it('decodes a registry-miss text/* subtype via the text path (RFC 2046)', async () => {
|
|
173
|
+
const mediaToken = vi.fn();
|
|
174
|
+
const resourceRepresentation = vi.fn().mockResolvedValue({
|
|
175
|
+
data: new TextEncoder().encode('hi').buffer,
|
|
176
|
+
contentType: 'text/x-custom',
|
|
177
|
+
});
|
|
178
|
+
tc = clientWithNamespaces({ mediaToken, resourceRepresentation });
|
|
179
|
+
const stateUnit = createResourceViewerPageStateUnit(
|
|
180
|
+
tc.client, RID, 'en', mockBrowse(),
|
|
181
|
+
{ mediaType: 'text/x-custom' },
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
expect(resourceRepresentation).toHaveBeenCalled();
|
|
185
|
+
expect(mediaToken).not.toHaveBeenCalled();
|
|
186
|
+
|
|
187
|
+
stateUnit.dispose();
|
|
188
|
+
});
|
|
189
|
+
|
|
153
190
|
it('wizard initializes closed', async () => {
|
|
154
191
|
tc = clientWithNamespaces();
|
|
155
192
|
const stateUnit = createResourceViewerPageStateUnit(tc.client, RID, 'en', mockBrowse());
|
|
@@ -9,7 +9,7 @@ import { createGatherStateUnit, type GatherStateUnit } from '@semiont/sdk';
|
|
|
9
9
|
import { createMatchStateUnit } from '@semiont/sdk';
|
|
10
10
|
import { createYieldStateUnit, type YieldStateUnit } from '@semiont/sdk';
|
|
11
11
|
import type { SemiontClient } from '@semiont/sdk';
|
|
12
|
-
import { decodeWithCharset } from '@semiont/core';
|
|
12
|
+
import { decodeWithCharset, textExtractionOf } from '@semiont/core';
|
|
13
13
|
import { isHighlight, isComment, isAssessment, isReference, isTag } from '@semiont/core';
|
|
14
14
|
import type { ReferencedByEntry } from '@semiont/sdk';
|
|
15
15
|
|
|
@@ -113,13 +113,17 @@ export function createResourceViewerPageStateUnit(
|
|
|
113
113
|
const mediaToken$ = new BehaviorSubject<string | null>(null);
|
|
114
114
|
|
|
115
115
|
const mediaType = options?.mediaType || 'text/plain';
|
|
116
|
-
|
|
116
|
+
// "Fetch raw bytes or decode as text?" — binary iff the registry says this
|
|
117
|
+
// type does not decode to text. Storage-tier images (gif/webp) are
|
|
118
|
+
// render:'none' but still binary, and a ZIP must avoid the text path; a
|
|
119
|
+
// mechanical render-mode check would mis-route both into mojibake.
|
|
120
|
+
const isBinaryType = textExtractionOf(mediaType) !== 'decode';
|
|
117
121
|
|
|
118
122
|
if (!isBinaryType && mediaType) {
|
|
119
123
|
contentLoading$.next(true);
|
|
120
|
-
client.browse.resourceRepresentation(resourceId
|
|
121
|
-
.then(({ data }) => {
|
|
122
|
-
content$.next(decodeWithCharset(data,
|
|
124
|
+
client.browse.resourceRepresentation(resourceId)
|
|
125
|
+
.then(({ data, contentType }) => {
|
|
126
|
+
content$.next(decodeWithCharset(data, contentType));
|
|
123
127
|
contentLoading$.next(false);
|
|
124
128
|
})
|
|
125
129
|
.catch(() => { contentLoading$.next(false); });
|
|
@@ -362,36 +362,6 @@
|
|
|
362
362
|
color: var(--semiont-color-gray-400);
|
|
363
363
|
}
|
|
364
364
|
|
|
365
|
-
/* Hash chain badge */
|
|
366
|
-
.semiont-exchange__hash-badge {
|
|
367
|
-
display: inline-flex;
|
|
368
|
-
align-items: center;
|
|
369
|
-
padding: 0.25rem 0.75rem;
|
|
370
|
-
border-radius: 9999px;
|
|
371
|
-
font-size: 0.75rem;
|
|
372
|
-
font-weight: 500;
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
.semiont-exchange__hash-badge--valid {
|
|
376
|
-
background-color: var(--semiont-color-green-100);
|
|
377
|
-
color: var(--semiont-color-green-800);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
[data-theme="dark"] .semiont-exchange__hash-badge--valid {
|
|
381
|
-
background-color: rgba(34, 197, 94, 0.15);
|
|
382
|
-
color: var(--semiont-color-green-300);
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
.semiont-exchange__hash-badge--invalid {
|
|
386
|
-
background-color: var(--semiont-color-red-100);
|
|
387
|
-
color: var(--semiont-color-red-800);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
[data-theme="dark"] .semiont-exchange__hash-badge--invalid {
|
|
391
|
-
background-color: rgba(239, 68, 68, 0.15);
|
|
392
|
-
color: var(--semiont-color-red-300);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
365
|
/* Error message */
|
|
396
366
|
.semiont-exchange__error-message {
|
|
397
367
|
font-size: 0.875rem;
|
package/src/styles/index.css
CHANGED
|
@@ -91,6 +91,7 @@
|
|
|
91
91
|
@import '../components/annotation/references.css';
|
|
92
92
|
@import '../components/Toast.css';
|
|
93
93
|
@import '../components/StatusDisplay.css';
|
|
94
|
+
@import '../components/ProtectedErrorBoundary.css';
|
|
94
95
|
@import '../components/loading-states/loading.css';
|
|
95
96
|
@import '../components/error-states/errors.css';
|
|
96
97
|
@import '../features/auth/auth.css';
|
package/translations/ar.json
CHANGED
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"annotationRemoved": "تمت إزالة التعليق التوضيحي",
|
|
72
72
|
"annotationBodyUpdated": "تم تحديث التعليق التوضيحي",
|
|
73
73
|
"jobEvent": "حدث مهمة",
|
|
74
|
+
"embeddingComputed": "تم حساب التضمين",
|
|
74
75
|
"justNow": "الآن",
|
|
75
76
|
"minutesAgo": "منذ {{count}}د",
|
|
76
77
|
"hoursAgo": "منذ {{count}}س",
|
|
@@ -356,8 +357,5 @@
|
|
|
356
357
|
"semanticScoringHelp": "استخدام الذكاء الاصطناعي لتقييم النتائج حسب الصلة الدلالية",
|
|
357
358
|
"searching": "جاري البحث...",
|
|
358
359
|
"search": "بحث"
|
|
359
|
-
},
|
|
360
|
-
"History": {
|
|
361
|
-
"embeddingComputed": "تم حساب التضمين"
|
|
362
360
|
}
|
|
363
361
|
}
|
package/translations/bn.json
CHANGED
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"annotationRemoved": "টীকা অপসারিত হয়েছে",
|
|
72
72
|
"annotationBodyUpdated": "টীকা আপডেট হয়েছে",
|
|
73
73
|
"jobEvent": "কাজের ঘটনা",
|
|
74
|
+
"embeddingComputed": "এমবেডিং গণনা করা হয়েছে",
|
|
74
75
|
"justNow": "এইমাত্র",
|
|
75
76
|
"minutesAgo": "{{count}} মি. আগে",
|
|
76
77
|
"hoursAgo": "{{count}} ঘ. আগে",
|
|
@@ -356,8 +357,5 @@
|
|
|
356
357
|
"semanticScoringHelp": "অর্থগত প্রাসঙ্গিকতা অনুযায়ী ফলাফল স্কোর করতে AI ব্যবহার করুন",
|
|
357
358
|
"searching": "অনুসন্ধান করা হচ্ছে...",
|
|
358
359
|
"search": "অনুসন্ধান"
|
|
359
|
-
},
|
|
360
|
-
"History": {
|
|
361
|
-
"embeddingComputed": "এমবেডিং গণনা করা হয়েছে"
|
|
362
360
|
}
|
|
363
361
|
}
|
package/translations/cs.json
CHANGED
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"annotationRemoved": "Anotace odebrána",
|
|
72
72
|
"annotationBodyUpdated": "Anotace aktualizována",
|
|
73
73
|
"jobEvent": "Událost úlohy",
|
|
74
|
+
"embeddingComputed": "Embedding vypočten",
|
|
74
75
|
"justNow": "právě teď",
|
|
75
76
|
"minutesAgo": "před {{count}} min",
|
|
76
77
|
"hoursAgo": "před {{count}} hod",
|
|
@@ -356,8 +357,5 @@
|
|
|
356
357
|
"semanticScoringHelp": "Použít AI k hodnocení výsledků podle sémantické relevance",
|
|
357
358
|
"searching": "Vyhledávání...",
|
|
358
359
|
"search": "Hledat"
|
|
359
|
-
},
|
|
360
|
-
"History": {
|
|
361
|
-
"embeddingComputed": "Embedding vypočten"
|
|
362
360
|
}
|
|
363
361
|
}
|