@semiont/react-ui 0.5.9 → 0.5.10
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/dist/{PdfAnnotationCanvas.client-U4EVBZEV.js → PdfAnnotationCanvas.client-P37SXBWQ.js} +2 -2
- package/dist/{ar-WA47UUWA.js → ar-ZHKO4XDR.js} +45 -4
- package/dist/ar-ZHKO4XDR.js.map +1 -0
- package/dist/{bn-5ANDRIU6.js → bn-3BUDKAK5.js} +45 -4
- package/dist/bn-3BUDKAK5.js.map +1 -0
- package/dist/{chunk-WHUGODTB.js → chunk-5LFFAVDI.js} +32 -32
- package/dist/{chunk-YBGN4ACY.js → chunk-O23TLLXW.js} +1 -6
- package/dist/{chunk-GERSK2DY.js → chunk-R52YAGHK.js} +42 -1
- package/dist/chunk-R52YAGHK.js.map +1 -0
- package/dist/{cs-3RU7F4JX.js → cs-2EFEUKTW.js} +45 -4
- package/dist/cs-2EFEUKTW.js.map +1 -0
- package/dist/{da-GSW5P5HG.js → da-SUD3Q44Q.js} +45 -4
- package/dist/da-SUD3Q44Q.js.map +1 -0
- package/dist/{de-JUAUYF4Z.js → de-6IC5QRX7.js} +45 -4
- package/dist/de-6IC5QRX7.js.map +1 -0
- package/dist/{el-JNLWCKEC.js → el-Q4NAXFRC.js} +45 -4
- package/dist/el-Q4NAXFRC.js.map +1 -0
- package/dist/{en-RBN2GUHF.js → en-P2L4X27N.js} +5 -3
- package/dist/{es-WPOX225H.js → es-YUKZBD4W.js} +45 -4
- package/dist/es-YUKZBD4W.js.map +1 -0
- package/dist/{fa-TAEP6N77.js → fa-OJ47LILE.js} +45 -4
- package/dist/fa-OJ47LILE.js.map +1 -0
- package/dist/{fi-ZVZHANSP.js → fi-QFFL5CUB.js} +45 -4
- package/dist/fi-QFFL5CUB.js.map +1 -0
- package/dist/{fr-VLZW7M4N.js → fr-BOQZZWP4.js} +45 -4
- package/dist/fr-BOQZZWP4.js.map +1 -0
- package/dist/{he-QFAFYA77.js → he-LXDY3TFA.js} +45 -4
- package/dist/he-LXDY3TFA.js.map +1 -0
- package/dist/{hi-AO3DQPCV.js → hi-HOJER77V.js} +45 -4
- package/dist/hi-HOJER77V.js.map +1 -0
- package/dist/{id-GTXF42WM.js → id-I5WQ6EWD.js} +45 -4
- package/dist/id-I5WQ6EWD.js.map +1 -0
- package/dist/index.d.ts +119 -9
- package/dist/index.js +4417 -18858
- package/dist/index.js.map +1 -1
- package/dist/integrations/css-modules-helper.js +1 -1
- package/dist/integrations/styled-components-theme.js +1 -1
- package/dist/{it-AS5GM232.js → it-UYH642IJ.js} +45 -4
- package/dist/it-UYH642IJ.js.map +1 -0
- package/dist/{ja-GZ4HLUOF.js → ja-JTVWAC4L.js} +45 -4
- package/dist/ja-JTVWAC4L.js.map +1 -0
- package/dist/{ko-A4XUXFGJ.js → ko-IHXFMOIZ.js} +45 -4
- package/dist/ko-IHXFMOIZ.js.map +1 -0
- package/dist/{ms-YBAO3S6K.js → ms-JH25VFLO.js} +45 -4
- package/dist/ms-JH25VFLO.js.map +1 -0
- package/dist/{nl-3TJGIIIU.js → nl-SURXW7IQ.js} +45 -4
- package/dist/nl-SURXW7IQ.js.map +1 -0
- package/dist/{no-4AXIQPRQ.js → no-HXIKQFDZ.js} +45 -4
- package/dist/no-HXIKQFDZ.js.map +1 -0
- package/dist/{pl-5GP6GKXO.js → pl-7A4KU6XK.js} +45 -4
- package/dist/pl-7A4KU6XK.js.map +1 -0
- package/dist/{pt-26Y6JFG5.js → pt-6MGYP7Q5.js} +45 -4
- package/dist/pt-6MGYP7Q5.js.map +1 -0
- package/dist/{ro-C7UXFRWJ.js → ro-IROCHNIA.js} +45 -4
- package/dist/ro-IROCHNIA.js.map +1 -0
- package/dist/{sv-44DVD76T.js → sv-WDJD75CV.js} +45 -4
- package/dist/sv-WDJD75CV.js.map +1 -0
- package/dist/test-utils.js +3 -3
- package/dist/{th-GIQRTBOY.js → th-RIO2E6B3.js} +45 -4
- package/dist/th-RIO2E6B3.js.map +1 -0
- package/dist/{tr-WMQWO4D6.js → tr-TZ7ERCIN.js} +45 -4
- package/dist/tr-TZ7ERCIN.js.map +1 -0
- package/dist/{uk-I7ML6RGG.js → uk-ZFWDKLUE.js} +45 -4
- package/dist/uk-ZFWDKLUE.js.map +1 -0
- package/dist/{vi-FGWECASQ.js → vi-UIOSP2JG.js} +45 -4
- package/dist/vi-UIOSP2JG.js.map +1 -0
- package/dist/{zh-L5FN73XC.js → zh-6R6YUY46.js} +45 -4
- package/dist/zh-6R6YUY46.js.map +1 -0
- package/package.json +17 -8
- package/src/components/AnnotateReferencesProgressWidget.tsx +35 -16
- package/src/components/__tests__/AnnotateReferencesProgressWidget.test.tsx +41 -91
- package/src/components/modals/ConfigureGatherStep.tsx +102 -0
- package/src/components/modals/ContextSummary.tsx +15 -6
- package/src/components/modals/GatherContextStep.tsx +104 -59
- package/src/components/modals/ResourceGenerateModal.tsx +258 -0
- package/src/components/modals/__tests__/ConfigureGatherStep.test.tsx +82 -0
- package/src/components/modals/__tests__/GatherContextStep.resource.test.tsx +107 -0
- package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +197 -0
- package/src/components/resource/panels/ReferencesPanel.tsx +10 -0
- package/src/components/resource/panels/ResourceInfoPanel.tsx +22 -0
- package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +26 -0
- package/src/features/admin-exchange/state/__tests__/exchange-state-unit.test.ts +14 -0
- package/src/features/admin-exchange/state/exchange-state-unit.ts +4 -2
- package/src/features/admin-security/state/__tests__/admin-security-state-unit.test.ts +14 -0
- package/src/features/admin-security/state/admin-security-state-unit.ts +4 -2
- package/src/features/admin-users/state/__tests__/admin-users-state-unit.test.ts +12 -5
- package/src/features/admin-users/state/admin-users-state-unit.ts +4 -2
- package/src/features/auth-welcome/state/__tests__/welcome-state-unit.test.ts +11 -0
- package/src/features/auth-welcome/state/welcome-state-unit.ts +1 -1
- package/src/features/moderate-entity-tags/state/__tests__/entity-tags-state-unit.test.ts +14 -0
- package/src/features/moderate-entity-tags/state/entity-tags-state-unit.ts +4 -2
- package/src/features/resource-compose/components/ResourceComposePage.tsx +13 -7
- package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +13 -0
- package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -2
- package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +15 -9
- package/src/features/resource-discovery/state/discover-state-unit.ts +5 -2
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +79 -0
- package/src/features/resource-viewer/state/__tests__/resource-loader-state-unit.test.ts +11 -0
- package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +30 -0
- package/src/features/resource-viewer/state/resource-loader-state-unit.ts +1 -1
- package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +5 -2
- package/translations/ar.json +41 -2
- package/translations/bn.json +41 -2
- package/translations/cs.json +41 -2
- package/translations/da.json +41 -2
- package/translations/de.json +41 -2
- package/translations/el.json +41 -2
- package/translations/en.json +39 -0
- package/translations/es.json +41 -2
- package/translations/fa.json +41 -2
- package/translations/fi.json +41 -2
- package/translations/fr.json +41 -2
- package/translations/he.json +41 -2
- package/translations/hi.json +41 -2
- package/translations/id.json +41 -2
- package/translations/it.json +41 -2
- package/translations/ja.json +41 -2
- package/translations/ko.json +41 -2
- package/translations/ms.json +41 -2
- package/translations/nl.json +41 -2
- package/translations/no.json +41 -2
- package/translations/pl.json +41 -2
- package/translations/pt.json +41 -2
- package/translations/ro.json +41 -2
- package/translations/sv.json +41 -2
- package/translations/th.json +41 -2
- package/translations/tr.json +41 -2
- package/translations/uk.json +41 -2
- package/translations/vi.json +41 -2
- package/translations/zh.json +41 -2
- package/dist/ar-WA47UUWA.js.map +0 -1
- package/dist/bn-5ANDRIU6.js.map +0 -1
- package/dist/chunk-GERSK2DY.js.map +0 -1
- package/dist/cs-3RU7F4JX.js.map +0 -1
- package/dist/da-GSW5P5HG.js.map +0 -1
- package/dist/de-JUAUYF4Z.js.map +0 -1
- package/dist/el-JNLWCKEC.js.map +0 -1
- package/dist/es-WPOX225H.js.map +0 -1
- package/dist/fa-TAEP6N77.js.map +0 -1
- package/dist/fi-ZVZHANSP.js.map +0 -1
- package/dist/fr-VLZW7M4N.js.map +0 -1
- package/dist/he-QFAFYA77.js.map +0 -1
- package/dist/hi-AO3DQPCV.js.map +0 -1
- package/dist/id-GTXF42WM.js.map +0 -1
- package/dist/it-AS5GM232.js.map +0 -1
- package/dist/ja-GZ4HLUOF.js.map +0 -1
- package/dist/ko-A4XUXFGJ.js.map +0 -1
- package/dist/ms-YBAO3S6K.js.map +0 -1
- package/dist/nl-3TJGIIIU.js.map +0 -1
- package/dist/no-4AXIQPRQ.js.map +0 -1
- package/dist/pl-5GP6GKXO.js.map +0 -1
- package/dist/pt-26Y6JFG5.js.map +0 -1
- package/dist/ro-C7UXFRWJ.js.map +0 -1
- package/dist/sv-44DVD76T.js.map +0 -1
- package/dist/th-GIQRTBOY.js.map +0 -1
- package/dist/tr-WMQWO4D6.js.map +0 -1
- package/dist/uk-I7ML6RGG.js.map +0 -1
- package/dist/vi-FGWECASQ.js.map +0 -1
- package/dist/zh-L5FN73XC.js.map +0 -1
- /package/dist/{PdfAnnotationCanvas.client-U4EVBZEV.js.map → PdfAnnotationCanvas.client-P37SXBWQ.js.map} +0 -0
- /package/dist/{chunk-WHUGODTB.js.map → chunk-5LFFAVDI.js.map} +0 -0
- /package/dist/{chunk-YBGN4ACY.js.map → chunk-O23TLLXW.js.map} +0 -0
- /package/dist/{en-RBN2GUHF.js.map → en-P2L4X27N.js.map} +0 -0
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
|
|
5
|
+
export interface ResourceGatherConfig {
|
|
6
|
+
depth: number;
|
|
7
|
+
maxResources: number;
|
|
8
|
+
includeContent: boolean;
|
|
9
|
+
includeSummary: boolean;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ConfigureGatherStepProps {
|
|
13
|
+
defaults?: Partial<ResourceGatherConfig>;
|
|
14
|
+
onGather: (config: ResourceGatherConfig) => void;
|
|
15
|
+
onCancel: () => void;
|
|
16
|
+
translations: {
|
|
17
|
+
intro: string;
|
|
18
|
+
includeContent: string;
|
|
19
|
+
includeSummary: string;
|
|
20
|
+
depth: string;
|
|
21
|
+
maxResources: string;
|
|
22
|
+
cancel: string;
|
|
23
|
+
gather: string;
|
|
24
|
+
};
|
|
25
|
+
/** Slot for the exclusion multi-select (GENERATE-FROM-BUTTON Phase 4). */
|
|
26
|
+
children?: React.ReactNode;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* First step of the resource-generate flow: pick the gather options before
|
|
31
|
+
* `gather.resource` runs. The `children` slot hosts the entity-type exclusion
|
|
32
|
+
* multi-select (Phase 4).
|
|
33
|
+
*/
|
|
34
|
+
export function ConfigureGatherStep({ defaults, onGather, onCancel, translations: t, children }: ConfigureGatherStepProps) {
|
|
35
|
+
const [includeContent, setIncludeContent] = useState(defaults?.includeContent ?? true);
|
|
36
|
+
const [includeSummary, setIncludeSummary] = useState(defaults?.includeSummary ?? true);
|
|
37
|
+
const [depth, setDepth] = useState(defaults?.depth ?? 2);
|
|
38
|
+
const [maxResources, setMaxResources] = useState(defaults?.maxResources ?? 10);
|
|
39
|
+
|
|
40
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
onGather({ includeContent, includeSummary, depth, maxResources });
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<form onSubmit={handleSubmit} className="semiont-form semiont-form--scrollable">
|
|
47
|
+
<p className="semiont-form__helper-text">{t.intro}</p>
|
|
48
|
+
|
|
49
|
+
<div className="semiont-form__checkbox-field">
|
|
50
|
+
<label className="semiont-form__checkbox-label">
|
|
51
|
+
<input type="checkbox" checked={includeContent} onChange={(e) => setIncludeContent(e.target.checked)} />
|
|
52
|
+
{t.includeContent}
|
|
53
|
+
</label>
|
|
54
|
+
</div>
|
|
55
|
+
<div className="semiont-form__checkbox-field">
|
|
56
|
+
<label className="semiont-form__checkbox-label">
|
|
57
|
+
<input type="checkbox" checked={includeSummary} onChange={(e) => setIncludeSummary(e.target.checked)} />
|
|
58
|
+
{t.includeSummary}
|
|
59
|
+
</label>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div className="semiont-form__inline-row">
|
|
63
|
+
<div className="semiont-form__field semiont-form__field--inline semiont-form__field--narrow">
|
|
64
|
+
<label htmlFor="gather-depth" className="semiont-form__label">{t.depth}</label>
|
|
65
|
+
<input
|
|
66
|
+
id="gather-depth"
|
|
67
|
+
type="number"
|
|
68
|
+
min="1"
|
|
69
|
+
max="5"
|
|
70
|
+
value={depth}
|
|
71
|
+
onChange={(e) => setDepth(parseInt(e.target.value))}
|
|
72
|
+
className="semiont-input"
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
<div className="semiont-form__field semiont-form__field--inline semiont-form__field--narrow">
|
|
76
|
+
<label htmlFor="gather-max" className="semiont-form__label">{t.maxResources}</label>
|
|
77
|
+
<input
|
|
78
|
+
id="gather-max"
|
|
79
|
+
type="number"
|
|
80
|
+
min="1"
|
|
81
|
+
max="50"
|
|
82
|
+
value={maxResources}
|
|
83
|
+
onChange={(e) => setMaxResources(parseInt(e.target.value))}
|
|
84
|
+
className="semiont-input"
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
{/* Exclusion multi-select slot (Phase 4) */}
|
|
90
|
+
{children}
|
|
91
|
+
|
|
92
|
+
<div className="semiont-modal__actions" style={{ paddingTop: '0.5rem' }}>
|
|
93
|
+
<button type="button" onClick={onCancel} className="semiont-button--secondary semiont-button--flex">
|
|
94
|
+
✕ {t.cancel}
|
|
95
|
+
</button>
|
|
96
|
+
<button type="submit" className="semiont-button--primary semiont-button--flex">
|
|
97
|
+
✨ {t.gather}
|
|
98
|
+
</button>
|
|
99
|
+
</div>
|
|
100
|
+
</form>
|
|
101
|
+
);
|
|
102
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import type
|
|
3
|
+
import { deriveViews, type GatheredContext } from '@semiont/core';
|
|
4
4
|
|
|
5
5
|
export interface ContextSummaryTranslations {
|
|
6
6
|
sourceContextLabel: string;
|
|
@@ -16,14 +16,23 @@ export interface ContextSummaryProps {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
export function ContextSummary({ context, translations: t }: ContextSummaryProps) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
|
|
19
|
+
// Graph views are focus-agnostic — only the focal resource id (and an optional
|
|
20
|
+
// focal annotation id) differ by kind.
|
|
21
|
+
const mainResourceId = context.focus.kind === 'annotation'
|
|
22
|
+
? String(context.focus.sourceResource.id)
|
|
23
|
+
: String(context.focus.resource.id);
|
|
24
|
+
const focalAnnotationId = context.focus.kind === 'annotation'
|
|
25
|
+
? context.focus.annotation.id
|
|
26
|
+
: undefined;
|
|
27
|
+
const { connections, citedBy, citedByCount } = deriveViews(
|
|
28
|
+
context.graph,
|
|
29
|
+
mainResourceId,
|
|
30
|
+
focalAnnotationId,
|
|
31
|
+
);
|
|
23
32
|
|
|
24
33
|
return (
|
|
25
34
|
<>
|
|
26
|
-
{
|
|
35
|
+
{(connections.length > 0 || citedByCount > 0) && (
|
|
27
36
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
|
|
28
37
|
|
|
29
38
|
{connections.length > 0 && (
|
|
@@ -9,11 +9,12 @@ export interface GatherContextStepProps {
|
|
|
9
9
|
context: GatheredContext | null;
|
|
10
10
|
contextLoading: boolean;
|
|
11
11
|
contextError: Error | null;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
/** Annotation-wizard controls. Omit for a display-only (e.g. resource-focus) render. */
|
|
13
|
+
userHint?: string;
|
|
14
|
+
onUserHintChange?: (value: string) => void;
|
|
15
|
+
onBind?: () => void;
|
|
16
|
+
onGenerate?: () => void;
|
|
17
|
+
onCompose?: () => void;
|
|
17
18
|
translations: {
|
|
18
19
|
title: string;
|
|
19
20
|
loadingContext: string;
|
|
@@ -29,7 +30,7 @@ export function GatherContextStep({
|
|
|
29
30
|
context,
|
|
30
31
|
contextLoading,
|
|
31
32
|
contextError,
|
|
32
|
-
userHint,
|
|
33
|
+
userHint = '',
|
|
33
34
|
onUserHintChange,
|
|
34
35
|
onBind,
|
|
35
36
|
onGenerate,
|
|
@@ -38,7 +39,8 @@ export function GatherContextStep({
|
|
|
38
39
|
}: GatherContextStepProps) {
|
|
39
40
|
const [sourceExpanded, setSourceExpanded] = useState(false);
|
|
40
41
|
const contextReady = !contextLoading && !contextError && !!context;
|
|
41
|
-
const
|
|
42
|
+
const focus = context?.focus.kind === 'annotation' ? context.focus : null;
|
|
43
|
+
const resourceFocus = context?.focus.kind === 'resource' ? context.focus : null;
|
|
42
44
|
const highlightRef = useRef<HTMLSpanElement>(null);
|
|
43
45
|
|
|
44
46
|
// Scroll the highlighted term into view when context loads
|
|
@@ -69,16 +71,16 @@ export function GatherContextStep({
|
|
|
69
71
|
|
|
70
72
|
{context && (
|
|
71
73
|
<>
|
|
72
|
-
{/* Full-width source context strip */}
|
|
73
|
-
{
|
|
74
|
+
{/* Full-width source context strip — annotation focus */}
|
|
75
|
+
{focus?.selected && (
|
|
74
76
|
<div className="semiont-gather__source-strip">
|
|
75
77
|
<label className="semiont-form__label" style={{ marginBottom: '0.375rem' }}>
|
|
76
|
-
{t.sourceContextLabel}{
|
|
78
|
+
{t.sourceContextLabel}{focus.sourceResource.name ? ` "${focus.sourceResource.name}"` : ''}
|
|
77
79
|
</label>
|
|
78
80
|
<div className={`semiont-gather__source-box${sourceExpanded ? ' semiont-gather__source-box--expanded' : ''}`}>
|
|
79
81
|
<div className="semiont-gather__source-context">
|
|
80
82
|
<div style={{ fontSize: 'var(--semiont-text-sm)', fontFamily: 'monospace', whiteSpace: 'pre-wrap', color: 'var(--semiont-text-secondary)' }}>
|
|
81
|
-
{
|
|
83
|
+
{focus.selected.before && <span>{focus.selected.before}</span>}
|
|
82
84
|
<span
|
|
83
85
|
ref={highlightRef}
|
|
84
86
|
style={{
|
|
@@ -88,19 +90,19 @@ export function GatherContextStep({
|
|
|
88
90
|
color: 'var(--semiont-color-primary-900)',
|
|
89
91
|
}}
|
|
90
92
|
>
|
|
91
|
-
{
|
|
93
|
+
{focus.selected.text}
|
|
92
94
|
</span>
|
|
93
95
|
{(context.metadata?.entityTypes ?? []).map(et => (
|
|
94
96
|
<span key={et} className="semiont-chip" style={{ fontSize: 'var(--semiont-text-xs)', padding: '0.125rem 0.375rem', fontWeight: 400, verticalAlign: 'middle', marginLeft: '0.25rem' }}>
|
|
95
97
|
{et}
|
|
96
98
|
</span>
|
|
97
99
|
))}
|
|
98
|
-
{
|
|
100
|
+
{focus.annotation.motivation && (
|
|
99
101
|
<span className="semiont-chip" style={{ fontSize: 'var(--semiont-text-xs)', padding: '0.125rem 0.375rem', fontWeight: 400, verticalAlign: 'middle', marginLeft: '0.25rem' }}>
|
|
100
|
-
{
|
|
102
|
+
{focus.annotation.motivation}
|
|
101
103
|
</span>
|
|
102
104
|
)}
|
|
103
|
-
{
|
|
105
|
+
{focus.selected.after && <span>{focus.selected.after}</span>}
|
|
104
106
|
</div>
|
|
105
107
|
</div>
|
|
106
108
|
<button
|
|
@@ -114,60 +116,103 @@ export function GatherContextStep({
|
|
|
114
116
|
</div>
|
|
115
117
|
)}
|
|
116
118
|
|
|
119
|
+
{/* Full-width source context strip — resource focus */}
|
|
120
|
+
{resourceFocus && (
|
|
121
|
+
<div className="semiont-gather__source-strip">
|
|
122
|
+
<label className="semiont-form__label" style={{ marginBottom: '0.375rem' }}>
|
|
123
|
+
{t.sourceContextLabel}{resourceFocus.resource.name ? ` "${resourceFocus.resource.name}"` : ''}
|
|
124
|
+
</label>
|
|
125
|
+
{(resourceFocus.summary || resourceFocus.content?.main) && (
|
|
126
|
+
<div className={`semiont-gather__source-box${sourceExpanded ? ' semiont-gather__source-box--expanded' : ''}`}>
|
|
127
|
+
<div className="semiont-gather__source-context">
|
|
128
|
+
<div style={{ fontSize: 'var(--semiont-text-sm)', whiteSpace: 'pre-wrap', color: 'var(--semiont-text-secondary)' }}>
|
|
129
|
+
{resourceFocus.summary ?? resourceFocus.content?.main}
|
|
130
|
+
{(context.metadata?.entityTypes ?? []).map(et => (
|
|
131
|
+
<span key={et} className="semiont-chip" style={{ fontSize: 'var(--semiont-text-xs)', padding: '0.125rem 0.375rem', fontWeight: 400, verticalAlign: 'middle', marginLeft: '0.25rem' }}>
|
|
132
|
+
{et}
|
|
133
|
+
</span>
|
|
134
|
+
))}
|
|
135
|
+
</div>
|
|
136
|
+
</div>
|
|
137
|
+
<button
|
|
138
|
+
type="button"
|
|
139
|
+
className="semiont-gather__expand-btn"
|
|
140
|
+
onClick={() => setSourceExpanded(v => !v)}
|
|
141
|
+
>
|
|
142
|
+
{sourceExpanded ? '▲ less' : '▼ more'}
|
|
143
|
+
</button>
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
{resourceFocus.suggestedReferences && resourceFocus.suggestedReferences.length > 0 && (
|
|
147
|
+
<div style={{ marginTop: '0.5rem' }}>
|
|
148
|
+
{resourceFocus.suggestedReferences.map(ref => (
|
|
149
|
+
<span key={ref} className="semiont-chip" style={{ fontSize: 'var(--semiont-text-xs)', padding: '0.125rem 0.375rem', marginRight: '0.25rem' }}>
|
|
150
|
+
{ref}
|
|
151
|
+
</span>
|
|
152
|
+
))}
|
|
153
|
+
</div>
|
|
154
|
+
)}
|
|
155
|
+
</div>
|
|
156
|
+
)}
|
|
157
|
+
|
|
117
158
|
{/* Two-column body */}
|
|
118
159
|
<div className="semiont-gather__body">
|
|
119
|
-
{/* Left:
|
|
160
|
+
{/* Left: context summary (graph views) */}
|
|
120
161
|
<div className="semiont-gather__left">
|
|
121
162
|
<ContextSummary context={context} translations={t} />
|
|
122
163
|
</div>
|
|
123
164
|
|
|
124
|
-
{/* Right: hint textarea */}
|
|
125
|
-
|
|
126
|
-
<div className="semiont-
|
|
127
|
-
<
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
165
|
+
{/* Right: hint textarea (annotation focus only) */}
|
|
166
|
+
{focus && (
|
|
167
|
+
<div className="semiont-gather__right">
|
|
168
|
+
<div className="semiont-form__field">
|
|
169
|
+
<label className="semiont-form__label">
|
|
170
|
+
{t.userHintLabel}
|
|
171
|
+
</label>
|
|
172
|
+
<textarea
|
|
173
|
+
value={userHint}
|
|
174
|
+
onChange={(e) => onUserHintChange?.(e.target.value)}
|
|
175
|
+
placeholder={t.userHintPlaceholder}
|
|
176
|
+
className="semiont-search-modal__search-input semiont-gather__hint-textarea"
|
|
177
|
+
style={{ resize: 'vertical', fontFamily: 'inherit' }}
|
|
178
|
+
/>
|
|
179
|
+
</div>
|
|
137
180
|
</div>
|
|
138
|
-
|
|
181
|
+
)}
|
|
139
182
|
</div>
|
|
140
183
|
|
|
141
|
-
{/* Full-width footer: resolution strategy */}
|
|
142
|
-
|
|
143
|
-
<div className="semiont-gather__footer
|
|
144
|
-
|
|
145
|
-
<
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
184
|
+
{/* Full-width footer: resolution strategy (annotation focus only) */}
|
|
185
|
+
{focus && (
|
|
186
|
+
<div className="semiont-gather__footer">
|
|
187
|
+
<div className="semiont-gather__footer-label">{t.resolutionStrategyLabel}</div>
|
|
188
|
+
<div className="semiont-gather__actions">
|
|
189
|
+
<button
|
|
190
|
+
type="button"
|
|
191
|
+
onClick={onBind}
|
|
192
|
+
disabled={!contextReady}
|
|
193
|
+
className="semiont-button--primary semiont-button--flex"
|
|
194
|
+
>
|
|
195
|
+
🔍 {t.search}…
|
|
196
|
+
</button>
|
|
197
|
+
<button
|
|
198
|
+
type="button"
|
|
199
|
+
onClick={onGenerate}
|
|
200
|
+
disabled={!contextReady}
|
|
201
|
+
className="semiont-button--primary semiont-button--flex"
|
|
202
|
+
>
|
|
203
|
+
✨ {t.generate}…
|
|
204
|
+
</button>
|
|
205
|
+
<button
|
|
206
|
+
type="button"
|
|
207
|
+
onClick={onCompose}
|
|
208
|
+
disabled={!contextReady}
|
|
209
|
+
className="semiont-button--secondary semiont-button--flex"
|
|
210
|
+
>
|
|
211
|
+
✍️ {t.compose}
|
|
212
|
+
</button>
|
|
213
|
+
</div>
|
|
169
214
|
</div>
|
|
170
|
-
|
|
215
|
+
)}
|
|
171
216
|
</>
|
|
172
217
|
)}
|
|
173
218
|
</div>
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
4
|
+
import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@headlessui/react';
|
|
5
|
+
import { useResourceGather } from '../../hooks/useResourceGather';
|
|
6
|
+
import { useSemiont } from '../../session/SemiontProvider';
|
|
7
|
+
import { useObservable } from '../../hooks/useObservable';
|
|
8
|
+
import { ConfigureGatherStep, type ResourceGatherConfig } from './ConfigureGatherStep';
|
|
9
|
+
import { GatherContextStep } from './GatherContextStep';
|
|
10
|
+
import { ConfigureGenerationStep, type GenerationConfig } from './ConfigureGenerationStep';
|
|
11
|
+
|
|
12
|
+
export interface ResourceGenerateModalTranslations {
|
|
13
|
+
// Step titles + nav
|
|
14
|
+
gatherTitle: string;
|
|
15
|
+
reviewTitle: string;
|
|
16
|
+
configureTitle: string;
|
|
17
|
+
next: string;
|
|
18
|
+
back: string;
|
|
19
|
+
cancel: string;
|
|
20
|
+
// ConfigureGatherStep
|
|
21
|
+
gatherIntro: string;
|
|
22
|
+
includeContent: string;
|
|
23
|
+
includeSummary: string;
|
|
24
|
+
gatherDepth: string;
|
|
25
|
+
gatherMaxResources: string;
|
|
26
|
+
gatherButton: string;
|
|
27
|
+
excludeLabel: string;
|
|
28
|
+
// GatherContextStep display
|
|
29
|
+
loadingContext: string;
|
|
30
|
+
failedContext: string;
|
|
31
|
+
sourceContextLabel: string;
|
|
32
|
+
connectionsLabel: string;
|
|
33
|
+
citedByLabel: string;
|
|
34
|
+
// ConfigureGenerationStep
|
|
35
|
+
resourceTitle: string;
|
|
36
|
+
resourceTitlePlaceholder: string;
|
|
37
|
+
additionalInstructions: string;
|
|
38
|
+
additionalInstructionsPlaceholder: string;
|
|
39
|
+
language: string;
|
|
40
|
+
languageHelp: string;
|
|
41
|
+
creativity: string;
|
|
42
|
+
creativityFocused: string;
|
|
43
|
+
creativityCreative: string;
|
|
44
|
+
maxLength: string;
|
|
45
|
+
maxLengthHelp: string;
|
|
46
|
+
generate: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface ResourceGenerateModalProps {
|
|
50
|
+
isOpen: boolean;
|
|
51
|
+
onClose: () => void;
|
|
52
|
+
resourceId: string;
|
|
53
|
+
defaultTitle: string;
|
|
54
|
+
locale: string;
|
|
55
|
+
gatherDefaults?: Partial<ResourceGatherConfig>;
|
|
56
|
+
/**
|
|
57
|
+
* Emit the chosen generation config. The parent runs the job
|
|
58
|
+
* (`client.yield.fromResource(resourceId, …).run(…)`) — mirrors how the
|
|
59
|
+
* annotation wizard delegates `yield.fromAnnotation` to its parent.
|
|
60
|
+
*/
|
|
61
|
+
onGenerateSubmit: (resourceId: string, config: GenerationConfig) => void;
|
|
62
|
+
translations: ResourceGenerateModalTranslations;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type Step = 'configure-gather' | 'review' | 'configure-generation';
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Resource-generate flow (GENERATE-FROM-BUTTON): configure gather options →
|
|
69
|
+
* `gather.resource` → review the gathered `GatheredContext` → configure
|
|
70
|
+
* generation → emit. Reuses the kind-aware `GatherContextStep` for the review.
|
|
71
|
+
*/
|
|
72
|
+
export function ResourceGenerateModal({
|
|
73
|
+
isOpen,
|
|
74
|
+
onClose,
|
|
75
|
+
resourceId,
|
|
76
|
+
defaultTitle,
|
|
77
|
+
locale,
|
|
78
|
+
gatherDefaults,
|
|
79
|
+
onGenerateSubmit,
|
|
80
|
+
translations: t,
|
|
81
|
+
}: ResourceGenerateModalProps) {
|
|
82
|
+
const [step, setStep] = useState<Step>('configure-gather');
|
|
83
|
+
const { context, loading, error, gather, reset } = useResourceGather();
|
|
84
|
+
const client = useObservable(useSemiont().activeSession$)?.client;
|
|
85
|
+
const entityTypeOptions = useObservable(client?.browse.entityTypes()) ?? [];
|
|
86
|
+
const [excludeEntityTypes, setExcludeEntityTypes] = useState<string[]>([]);
|
|
87
|
+
|
|
88
|
+
// Reset to the first step whenever the modal (re)opens.
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
if (isOpen) {
|
|
91
|
+
setStep('configure-gather');
|
|
92
|
+
setExcludeEntityTypes([]);
|
|
93
|
+
reset();
|
|
94
|
+
}
|
|
95
|
+
}, [isOpen, reset]);
|
|
96
|
+
|
|
97
|
+
const handleGather = useCallback((config: ResourceGatherConfig) => {
|
|
98
|
+
setStep('review');
|
|
99
|
+
void gather(resourceId, {
|
|
100
|
+
...config,
|
|
101
|
+
...(excludeEntityTypes.length ? { excludeEntityTypes } : {}),
|
|
102
|
+
});
|
|
103
|
+
}, [gather, resourceId, excludeEntityTypes]);
|
|
104
|
+
|
|
105
|
+
const handleGenerate = useCallback((config: GenerationConfig) => {
|
|
106
|
+
onGenerateSubmit(resourceId, config);
|
|
107
|
+
onClose();
|
|
108
|
+
}, [onGenerateSubmit, resourceId, onClose]);
|
|
109
|
+
|
|
110
|
+
const stepTitle = step === 'configure-gather' ? t.gatherTitle : step === 'review' ? t.reviewTitle : t.configureTitle;
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<Transition appear show={isOpen}>
|
|
114
|
+
<Dialog as="div" className="semiont-search-modal" onClose={onClose}>
|
|
115
|
+
<TransitionChild
|
|
116
|
+
enter="ease-out duration-200"
|
|
117
|
+
enterFrom="opacity-0"
|
|
118
|
+
enterTo="opacity-100"
|
|
119
|
+
leave="ease-in duration-150"
|
|
120
|
+
leaveFrom="opacity-100"
|
|
121
|
+
leaveTo="opacity-0"
|
|
122
|
+
>
|
|
123
|
+
<div className="semiont-search-modal__backdrop" />
|
|
124
|
+
</TransitionChild>
|
|
125
|
+
|
|
126
|
+
<div className="semiont-search-modal__wrapper">
|
|
127
|
+
<div className="semiont-search-modal__centering semiont-search-modal__centering--center">
|
|
128
|
+
<TransitionChild
|
|
129
|
+
enter="ease-out duration-200"
|
|
130
|
+
enterFrom="opacity-0 scale-95"
|
|
131
|
+
enterTo="opacity-100 scale-100"
|
|
132
|
+
leave="ease-in duration-150"
|
|
133
|
+
leaveFrom="opacity-100 scale-100"
|
|
134
|
+
leaveTo="opacity-0 scale-95"
|
|
135
|
+
>
|
|
136
|
+
<DialogPanel className="semiont-search-modal__panel semiont-search-modal__panel--with-border semiont-search-modal__panel--gather">
|
|
137
|
+
<div className="semiont-search-modal__header">
|
|
138
|
+
<DialogTitle className="semiont-search-modal__title">{stepTitle}</DialogTitle>
|
|
139
|
+
<button onClick={onClose} className="semiont-search-modal__close-button" aria-label="Close">
|
|
140
|
+
✕
|
|
141
|
+
</button>
|
|
142
|
+
</div>
|
|
143
|
+
|
|
144
|
+
{step === 'configure-gather' && (
|
|
145
|
+
<ConfigureGatherStep
|
|
146
|
+
defaults={gatherDefaults}
|
|
147
|
+
onGather={handleGather}
|
|
148
|
+
onCancel={onClose}
|
|
149
|
+
translations={{
|
|
150
|
+
intro: t.gatherIntro,
|
|
151
|
+
includeContent: t.includeContent,
|
|
152
|
+
includeSummary: t.includeSummary,
|
|
153
|
+
depth: t.gatherDepth,
|
|
154
|
+
maxResources: t.gatherMaxResources,
|
|
155
|
+
cancel: t.cancel,
|
|
156
|
+
gather: t.gatherButton,
|
|
157
|
+
}}
|
|
158
|
+
>
|
|
159
|
+
{entityTypeOptions.length > 0 && (
|
|
160
|
+
<div className="semiont-form__field semiont-form__entity-types">
|
|
161
|
+
<label className="semiont-form__label">{t.excludeLabel}</label>
|
|
162
|
+
<div className="semiont-form__entity-type-buttons">
|
|
163
|
+
{entityTypeOptions.map((et) => {
|
|
164
|
+
const isSelected = excludeEntityTypes.includes(et);
|
|
165
|
+
return (
|
|
166
|
+
<button
|
|
167
|
+
key={et}
|
|
168
|
+
type="button"
|
|
169
|
+
className="semiont-form__entity-type-button"
|
|
170
|
+
data-selected={isSelected}
|
|
171
|
+
aria-pressed={isSelected}
|
|
172
|
+
onClick={() => setExcludeEntityTypes(prev => prev.includes(et) ? prev.filter(x => x !== et) : [...prev, et])}
|
|
173
|
+
>
|
|
174
|
+
{et}
|
|
175
|
+
</button>
|
|
176
|
+
);
|
|
177
|
+
})}
|
|
178
|
+
</div>
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
</ConfigureGatherStep>
|
|
182
|
+
)}
|
|
183
|
+
|
|
184
|
+
{step === 'review' && (
|
|
185
|
+
<>
|
|
186
|
+
<GatherContextStep
|
|
187
|
+
context={context}
|
|
188
|
+
contextLoading={loading}
|
|
189
|
+
contextError={error}
|
|
190
|
+
translations={{
|
|
191
|
+
title: '',
|
|
192
|
+
loadingContext: t.loadingContext,
|
|
193
|
+
failedContext: t.failedContext,
|
|
194
|
+
search: '',
|
|
195
|
+
generate: '',
|
|
196
|
+
compose: '',
|
|
197
|
+
resolutionStrategyLabel: '',
|
|
198
|
+
sourceContextLabel: t.sourceContextLabel,
|
|
199
|
+
connectionsLabel: t.connectionsLabel,
|
|
200
|
+
citedByLabel: t.citedByLabel,
|
|
201
|
+
userHintLabel: '',
|
|
202
|
+
userHintPlaceholder: '',
|
|
203
|
+
}}
|
|
204
|
+
/>
|
|
205
|
+
<div className="semiont-modal__actions" style={{ paddingTop: '0.5rem' }}>
|
|
206
|
+
<button
|
|
207
|
+
type="button"
|
|
208
|
+
onClick={() => setStep('configure-gather')}
|
|
209
|
+
className="semiont-button--secondary semiont-button--flex"
|
|
210
|
+
>
|
|
211
|
+
◀ {t.back}
|
|
212
|
+
</button>
|
|
213
|
+
<button
|
|
214
|
+
type="button"
|
|
215
|
+
onClick={() => setStep('configure-generation')}
|
|
216
|
+
disabled={!context}
|
|
217
|
+
className="semiont-button--primary semiont-button--flex"
|
|
218
|
+
>
|
|
219
|
+
{t.next} ▶
|
|
220
|
+
</button>
|
|
221
|
+
</div>
|
|
222
|
+
</>
|
|
223
|
+
)}
|
|
224
|
+
|
|
225
|
+
{step === 'configure-generation' && context && (
|
|
226
|
+
<ConfigureGenerationStep
|
|
227
|
+
defaultTitle={defaultTitle}
|
|
228
|
+
locale={locale}
|
|
229
|
+
context={context}
|
|
230
|
+
onBack={() => setStep('review')}
|
|
231
|
+
onCancel={onClose}
|
|
232
|
+
onGenerate={handleGenerate}
|
|
233
|
+
translations={{
|
|
234
|
+
resourceTitle: t.resourceTitle,
|
|
235
|
+
resourceTitlePlaceholder: t.resourceTitlePlaceholder,
|
|
236
|
+
additionalInstructions: t.additionalInstructions,
|
|
237
|
+
additionalInstructionsPlaceholder: t.additionalInstructionsPlaceholder,
|
|
238
|
+
language: t.language,
|
|
239
|
+
languageHelp: t.languageHelp,
|
|
240
|
+
creativity: t.creativity,
|
|
241
|
+
creativityFocused: t.creativityFocused,
|
|
242
|
+
creativityCreative: t.creativityCreative,
|
|
243
|
+
maxLength: t.maxLength,
|
|
244
|
+
maxLengthHelp: t.maxLengthHelp,
|
|
245
|
+
cancel: t.cancel,
|
|
246
|
+
back: t.back,
|
|
247
|
+
generate: t.generate,
|
|
248
|
+
}}
|
|
249
|
+
/>
|
|
250
|
+
)}
|
|
251
|
+
</DialogPanel>
|
|
252
|
+
</TransitionChild>
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
</Dialog>
|
|
256
|
+
</Transition>
|
|
257
|
+
);
|
|
258
|
+
}
|