@semiont/react-ui 0.5.9 → 0.5.11

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 (209) hide show
  1. package/README.md +1 -1
  2. package/dist/{PdfAnnotationCanvas.client-U4EVBZEV.js → PdfAnnotationCanvas.client-KWQ7XDWT.js} +5 -2
  3. package/dist/PdfAnnotationCanvas.client-KWQ7XDWT.js.map +1 -0
  4. package/dist/{ar-WA47UUWA.js → ar-ZHKO4XDR.js} +45 -4
  5. package/dist/ar-ZHKO4XDR.js.map +1 -0
  6. package/dist/{bn-5ANDRIU6.js → bn-3BUDKAK5.js} +45 -4
  7. package/dist/bn-3BUDKAK5.js.map +1 -0
  8. package/dist/{chunk-YBGN4ACY.js → chunk-O23TLLXW.js} +1 -6
  9. package/dist/{chunk-WHUGODTB.js → chunk-PFUXQPWJ.js} +127 -127
  10. package/dist/{chunk-WHUGODTB.js.map → chunk-PFUXQPWJ.js.map} +1 -1
  11. package/dist/{chunk-GERSK2DY.js → chunk-R52YAGHK.js} +42 -1
  12. package/dist/chunk-R52YAGHK.js.map +1 -0
  13. package/dist/{cs-3RU7F4JX.js → cs-2EFEUKTW.js} +45 -4
  14. package/dist/cs-2EFEUKTW.js.map +1 -0
  15. package/dist/{da-GSW5P5HG.js → da-SUD3Q44Q.js} +45 -4
  16. package/dist/da-SUD3Q44Q.js.map +1 -0
  17. package/dist/{de-JUAUYF4Z.js → de-6IC5QRX7.js} +45 -4
  18. package/dist/de-6IC5QRX7.js.map +1 -0
  19. package/dist/{el-JNLWCKEC.js → el-Q4NAXFRC.js} +45 -4
  20. package/dist/el-Q4NAXFRC.js.map +1 -0
  21. package/dist/{en-RBN2GUHF.js → en-P2L4X27N.js} +5 -3
  22. package/dist/{es-WPOX225H.js → es-YUKZBD4W.js} +45 -4
  23. package/dist/es-YUKZBD4W.js.map +1 -0
  24. package/dist/{fa-TAEP6N77.js → fa-OJ47LILE.js} +45 -4
  25. package/dist/fa-OJ47LILE.js.map +1 -0
  26. package/dist/{fi-ZVZHANSP.js → fi-QFFL5CUB.js} +45 -4
  27. package/dist/fi-QFFL5CUB.js.map +1 -0
  28. package/dist/{fr-VLZW7M4N.js → fr-BOQZZWP4.js} +45 -4
  29. package/dist/fr-BOQZZWP4.js.map +1 -0
  30. package/dist/{he-QFAFYA77.js → he-LXDY3TFA.js} +45 -4
  31. package/dist/he-LXDY3TFA.js.map +1 -0
  32. package/dist/{hi-AO3DQPCV.js → hi-HOJER77V.js} +45 -4
  33. package/dist/hi-HOJER77V.js.map +1 -0
  34. package/dist/{id-GTXF42WM.js → id-I5WQ6EWD.js} +45 -4
  35. package/dist/id-I5WQ6EWD.js.map +1 -0
  36. package/dist/index.css +150 -112
  37. package/dist/index.css.map +1 -1
  38. package/dist/index.d.ts +403 -45
  39. package/dist/index.js +4660 -18860
  40. package/dist/index.js.map +1 -1
  41. package/dist/integrations/css-modules-helper.js +1 -1
  42. package/dist/integrations/styled-components-theme.js +1 -1
  43. package/dist/{it-AS5GM232.js → it-UYH642IJ.js} +45 -4
  44. package/dist/it-UYH642IJ.js.map +1 -0
  45. package/dist/{ja-GZ4HLUOF.js → ja-JTVWAC4L.js} +45 -4
  46. package/dist/ja-JTVWAC4L.js.map +1 -0
  47. package/dist/{ko-A4XUXFGJ.js → ko-IHXFMOIZ.js} +45 -4
  48. package/dist/ko-IHXFMOIZ.js.map +1 -0
  49. package/dist/{ms-YBAO3S6K.js → ms-JH25VFLO.js} +45 -4
  50. package/dist/ms-JH25VFLO.js.map +1 -0
  51. package/dist/{nl-3TJGIIIU.js → nl-SURXW7IQ.js} +45 -4
  52. package/dist/nl-SURXW7IQ.js.map +1 -0
  53. package/dist/{no-4AXIQPRQ.js → no-HXIKQFDZ.js} +45 -4
  54. package/dist/no-HXIKQFDZ.js.map +1 -0
  55. package/dist/{pl-5GP6GKXO.js → pl-7A4KU6XK.js} +45 -4
  56. package/dist/pl-7A4KU6XK.js.map +1 -0
  57. package/dist/{pt-26Y6JFG5.js → pt-6MGYP7Q5.js} +45 -4
  58. package/dist/pt-6MGYP7Q5.js.map +1 -0
  59. package/dist/{ro-C7UXFRWJ.js → ro-IROCHNIA.js} +45 -4
  60. package/dist/ro-IROCHNIA.js.map +1 -0
  61. package/dist/{sv-44DVD76T.js → sv-WDJD75CV.js} +45 -4
  62. package/dist/sv-WDJD75CV.js.map +1 -0
  63. package/dist/test-utils.js +9 -4
  64. package/dist/test-utils.js.map +1 -1
  65. package/dist/{th-GIQRTBOY.js → th-RIO2E6B3.js} +45 -4
  66. package/dist/th-RIO2E6B3.js.map +1 -0
  67. package/dist/{tr-WMQWO4D6.js → tr-TZ7ERCIN.js} +45 -4
  68. package/dist/tr-TZ7ERCIN.js.map +1 -0
  69. package/dist/{uk-I7ML6RGG.js → uk-ZFWDKLUE.js} +45 -4
  70. package/dist/uk-ZFWDKLUE.js.map +1 -0
  71. package/dist/{vi-FGWECASQ.js → vi-UIOSP2JG.js} +45 -4
  72. package/dist/vi-UIOSP2JG.js.map +1 -0
  73. package/dist/{zh-L5FN73XC.js → zh-6R6YUY46.js} +45 -4
  74. package/dist/zh-6R6YUY46.js.map +1 -0
  75. package/package.json +18 -9
  76. package/src/components/AnnotateReferencesProgressWidget.tsx +35 -16
  77. package/src/components/ResourceTagsInline.tsx +94 -11
  78. package/src/components/StatusDisplay.css +36 -0
  79. package/src/components/__tests__/AnnotateReferencesProgressWidget.test.tsx +41 -91
  80. package/src/components/__tests__/ResourceTagsInline.test.tsx +101 -3
  81. package/src/components/annotation/AnnotateToolbar.tsx +53 -28
  82. package/src/components/annotation/__tests__/AnnotateToolbar.display.test.tsx +76 -0
  83. package/src/components/annotation/__tests__/AnnotateToolbar.test.tsx +124 -360
  84. package/src/components/image-annotation/SvgDrawingCanvas.tsx +5 -1
  85. package/src/components/modals/ConfigureGatherStep.tsx +102 -0
  86. package/src/components/modals/ContextSummary.tsx +15 -6
  87. package/src/components/modals/GatherContextStep.tsx +104 -59
  88. package/src/components/modals/ResourceGenerateModal.tsx +258 -0
  89. package/src/components/modals/__tests__/ConfigureGatherStep.test.tsx +82 -0
  90. package/src/components/modals/__tests__/GatherContextStep.resource.test.tsx +107 -0
  91. package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +197 -0
  92. package/src/components/pdf-annotation/PdfAnnotationCanvas.tsx +5 -0
  93. package/src/components/pdf-annotation/__tests__/PdfAnnotationCanvas.test.tsx +6 -6
  94. package/src/components/resource/AnnotateView.tsx +59 -38
  95. package/src/components/resource/BrowseView.tsx +159 -133
  96. package/src/components/resource/ResourceViewer.tsx +119 -128
  97. package/src/components/resource/__tests__/AnnotateView.embeddable.test.tsx +55 -0
  98. package/src/components/resource/__tests__/BrowseView.embeddable.test.tsx +49 -0
  99. package/src/components/resource/__tests__/BrowseView.links.test.tsx +60 -0
  100. package/src/components/resource/__tests__/BrowseView.overlay-async.test.tsx +113 -0
  101. package/src/components/resource/__tests__/BrowseView.reference-hover.test.tsx +161 -0
  102. package/src/components/resource/__tests__/BrowseView.registry.test.tsx +43 -0
  103. package/src/components/resource/__tests__/BrowseView.test.tsx +35 -22
  104. package/src/components/resource/__tests__/ResourceViewer.embeddable.test.tsx +63 -0
  105. package/src/components/resource/__tests__/ResourceViewer.mode-switch.test.tsx +12 -7
  106. package/src/components/resource/__tests__/ResourceViewer.toolbar-opt-out.test.tsx +135 -0
  107. package/src/components/resource/__tests__/ResourceViewer.toolbar-prefs.test.tsx +200 -0
  108. package/src/components/resource/__tests__/browse-renderers.test.tsx +34 -0
  109. package/src/components/resource/__tests__/inline-embedding.test.tsx +81 -0
  110. package/src/components/resource/__tests__/media-completeness.test.tsx +88 -0
  111. package/src/components/resource/browse-renderers.tsx +63 -0
  112. package/src/components/resource/panels/AssessmentPanel.tsx +4 -0
  113. package/src/components/resource/panels/CommentsPanel.tsx +4 -0
  114. package/src/components/resource/panels/HighlightPanel.tsx +4 -0
  115. package/src/components/resource/panels/ReferencesPanel.tsx +14 -0
  116. package/src/components/resource/panels/ResourceInfoPanel.tsx +22 -0
  117. package/src/components/resource/panels/TaggingPanel.tsx +4 -0
  118. package/src/components/resource/panels/UnifiedAnnotationsPanel.tsx +5 -2
  119. package/src/components/resource/panels/__tests__/AssessmentPanel.test.tsx +1 -0
  120. package/src/components/resource/panels/__tests__/CommentsPanel.test.tsx +1 -0
  121. package/src/components/resource/panels/__tests__/HighlightPanel.annotationProgress.test.tsx +16 -16
  122. package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +1 -1
  123. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +1 -0
  124. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +26 -0
  125. package/src/components/resource/panels/__tests__/TaggingPanel.test.tsx +1 -0
  126. package/src/components/toolbar/Toolbar.css +14 -0
  127. package/src/features/admin-exchange/state/__tests__/exchange-state-unit.test.ts +14 -0
  128. package/src/features/admin-exchange/state/exchange-state-unit.ts +4 -2
  129. package/src/features/admin-security/state/__tests__/admin-security-state-unit.test.ts +14 -0
  130. package/src/features/admin-security/state/admin-security-state-unit.ts +4 -2
  131. package/src/features/admin-users/state/__tests__/admin-users-state-unit.test.ts +12 -5
  132. package/src/features/admin-users/state/admin-users-state-unit.ts +4 -2
  133. package/src/features/auth-welcome/state/__tests__/welcome-state-unit.test.ts +11 -0
  134. package/src/features/auth-welcome/state/welcome-state-unit.ts +1 -1
  135. package/src/features/moderate-entity-tags/state/__tests__/entity-tags-state-unit.test.ts +14 -0
  136. package/src/features/moderate-entity-tags/state/entity-tags-state-unit.ts +4 -2
  137. package/src/features/resource-compose/components/ResourceComposePage.tsx +13 -7
  138. package/src/features/resource-compose/state/__tests__/compose-page-state-unit.test.ts +13 -0
  139. package/src/features/resource-compose/state/compose-page-state-unit.ts +5 -2
  140. package/src/features/resource-discovery/state/__tests__/discover-state-unit.test.ts +15 -9
  141. package/src/features/resource-discovery/state/discover-state-unit.ts +5 -2
  142. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +7 -17
  143. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +119 -17
  144. package/src/features/resource-viewer/state/__tests__/resource-loader-state-unit.test.ts +11 -0
  145. package/src/features/resource-viewer/state/__tests__/resource-viewer-page-state-unit.test.ts +30 -0
  146. package/src/features/resource-viewer/state/resource-loader-state-unit.ts +1 -1
  147. package/src/features/resource-viewer/state/resource-viewer-page-state-unit.ts +7 -16
  148. package/src/styles/features/resource-viewer.css +19 -0
  149. package/translations/ar.json +41 -2
  150. package/translations/bn.json +41 -2
  151. package/translations/cs.json +41 -2
  152. package/translations/da.json +41 -2
  153. package/translations/de.json +41 -2
  154. package/translations/el.json +41 -2
  155. package/translations/en.json +39 -0
  156. package/translations/es.json +41 -2
  157. package/translations/fa.json +41 -2
  158. package/translations/fi.json +41 -2
  159. package/translations/fr.json +41 -2
  160. package/translations/he.json +41 -2
  161. package/translations/hi.json +41 -2
  162. package/translations/id.json +41 -2
  163. package/translations/it.json +41 -2
  164. package/translations/ja.json +41 -2
  165. package/translations/ko.json +41 -2
  166. package/translations/ms.json +41 -2
  167. package/translations/nl.json +41 -2
  168. package/translations/no.json +41 -2
  169. package/translations/pl.json +41 -2
  170. package/translations/pt.json +41 -2
  171. package/translations/ro.json +41 -2
  172. package/translations/sv.json +41 -2
  173. package/translations/th.json +41 -2
  174. package/translations/tr.json +41 -2
  175. package/translations/uk.json +41 -2
  176. package/translations/vi.json +41 -2
  177. package/translations/zh.json +41 -2
  178. package/dist/PdfAnnotationCanvas.client-U4EVBZEV.js.map +0 -1
  179. package/dist/ar-WA47UUWA.js.map +0 -1
  180. package/dist/bn-5ANDRIU6.js.map +0 -1
  181. package/dist/chunk-GERSK2DY.js.map +0 -1
  182. package/dist/cs-3RU7F4JX.js.map +0 -1
  183. package/dist/da-GSW5P5HG.js.map +0 -1
  184. package/dist/de-JUAUYF4Z.js.map +0 -1
  185. package/dist/el-JNLWCKEC.js.map +0 -1
  186. package/dist/es-WPOX225H.js.map +0 -1
  187. package/dist/fa-TAEP6N77.js.map +0 -1
  188. package/dist/fi-ZVZHANSP.js.map +0 -1
  189. package/dist/fr-VLZW7M4N.js.map +0 -1
  190. package/dist/he-QFAFYA77.js.map +0 -1
  191. package/dist/hi-AO3DQPCV.js.map +0 -1
  192. package/dist/id-GTXF42WM.js.map +0 -1
  193. package/dist/it-AS5GM232.js.map +0 -1
  194. package/dist/ja-GZ4HLUOF.js.map +0 -1
  195. package/dist/ko-A4XUXFGJ.js.map +0 -1
  196. package/dist/ms-YBAO3S6K.js.map +0 -1
  197. package/dist/nl-3TJGIIIU.js.map +0 -1
  198. package/dist/no-4AXIQPRQ.js.map +0 -1
  199. package/dist/pl-5GP6GKXO.js.map +0 -1
  200. package/dist/pt-26Y6JFG5.js.map +0 -1
  201. package/dist/ro-C7UXFRWJ.js.map +0 -1
  202. package/dist/sv-44DVD76T.js.map +0 -1
  203. package/dist/th-GIQRTBOY.js.map +0 -1
  204. package/dist/tr-WMQWO4D6.js.map +0 -1
  205. package/dist/uk-I7ML6RGG.js.map +0 -1
  206. package/dist/vi-FGWECASQ.js.map +0 -1
  207. package/dist/zh-L5FN73XC.js.map +0 -1
  208. /package/dist/{chunk-YBGN4ACY.js.map → chunk-O23TLLXW.js.map} +0 -0
  209. /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 { GatheredContext } from '@semiont/core';
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
- const graphContext = context.graphContext;
20
- const connections = graphContext?.connections ?? [];
21
- const citedBy = graphContext?.citedBy ?? [];
22
- const citedByCount = graphContext?.citedByCount ?? 0;
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
- {graphContext && (connections.length > 0 || citedByCount > 0) && (
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
- userHint: string;
13
- onUserHintChange: (value: string) => void;
14
- onBind: () => void;
15
- onGenerate: () => void;
16
- onCompose: () => void;
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 sourceContext = context?.sourceContext;
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
- {sourceContext && (
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}{context.sourceResource?.name ? ` "${context.sourceResource.name}"` : ''}
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
- {sourceContext.before && <span>{sourceContext.before}</span>}
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
- {sourceContext.selected}
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
- {context.annotation?.motivation && (
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
- {context.annotation.motivation}
102
+ {focus.annotation.motivation}
101
103
  </span>
102
104
  )}
103
- {sourceContext.after && <span>{sourceContext.after}</span>}
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: annotation metadata */}
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
- <div className="semiont-gather__right">
126
- <div className="semiont-form__field">
127
- <label className="semiont-form__label">
128
- {t.userHintLabel}
129
- </label>
130
- <textarea
131
- value={userHint}
132
- onChange={(e) => onUserHintChange(e.target.value)}
133
- placeholder={t.userHintPlaceholder}
134
- className="semiont-search-modal__search-input semiont-gather__hint-textarea"
135
- style={{ resize: 'vertical', fontFamily: 'inherit' }}
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
- </div>
181
+ )}
139
182
  </div>
140
183
 
141
- {/* Full-width footer: resolution strategy */}
142
- <div className="semiont-gather__footer">
143
- <div className="semiont-gather__footer-label">{t.resolutionStrategyLabel}</div>
144
- <div className="semiont-gather__actions">
145
- <button
146
- type="button"
147
- onClick={onBind}
148
- disabled={!contextReady}
149
- className="semiont-button--primary semiont-button--flex"
150
- >
151
- 🔍 {t.search}…
152
- </button>
153
- <button
154
- type="button"
155
- onClick={onGenerate}
156
- disabled={!contextReady}
157
- className="semiont-button--primary semiont-button--flex"
158
- >
159
- ✨ {t.generate}…
160
- </button>
161
- <button
162
- type="button"
163
- onClick={onCompose}
164
- disabled={!contextReady}
165
- className="semiont-button--secondary semiont-button--flex"
166
- >
167
- ✍️ {t.compose}
168
- </button>
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
- </div>
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
+ }