@semiont/react-ui 0.5.28 → 0.5.29

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 (112) hide show
  1. package/README.md +3 -3
  2. package/dist/{PdfAnnotationCanvas.client-C2IGG4SW.js → PdfAnnotationCanvas.client-LSH3PZPY.js} +4 -4
  3. package/dist/{ar-FABPTJVB.js → ar-AZBRADEB.js} +5 -1
  4. package/dist/{bn-LKP6BDMH.js → bn-CCEW6AOS.js} +5 -1
  5. package/dist/{chunk-VXASXU4K.js → chunk-3ZBKCWUZ.js} +1 -1
  6. package/dist/{chunk-2YHEE63S.js → chunk-G4VHL4UA.js} +31 -31
  7. package/dist/{chunk-2GOHTTHW.js → chunk-SDKFZLEX.js} +5 -1
  8. package/dist/{cs-YQYNWHRG.js → cs-HXPF2CTE.js} +5 -1
  9. package/dist/{da-QHZOYKRY.js → da-K5NTK7LH.js} +5 -1
  10. package/dist/{de-GKA7RD7J.js → de-GMRQEDZI.js} +5 -1
  11. package/dist/{el-45T257AL.js → el-EUB4CI4G.js} +5 -1
  12. package/dist/{en-RVW5EQ66.js → en-R5XA26NF.js} +2 -2
  13. package/dist/{es-AVER7RU4.js → es-Z32DAZ6W.js} +5 -1
  14. package/dist/{fa-K3VPK3O2.js → fa-G7HN6B32.js} +5 -1
  15. package/dist/{fi-5NEKF7EB.js → fi-3YBLZO7I.js} +5 -1
  16. package/dist/{fr-FUMNDLBZ.js → fr-VUBH7BBS.js} +5 -1
  17. package/dist/{he-3JGNA3PR.js → he-WQQEY26B.js} +5 -1
  18. package/dist/{hi-IJY375DF.js → hi-HJZ6W4JB.js} +5 -1
  19. package/dist/{id-LWHKJ7ZD.js → id-ZOGZ37MI.js} +5 -1
  20. package/dist/index.d.ts +157 -257
  21. package/dist/index.js +763 -1209
  22. package/dist/{it-EBVOV4RW.js → it-DT6UW66F.js} +5 -1
  23. package/dist/{ja-KWRIX2NF.js → ja-RUKQCOLO.js} +5 -1
  24. package/dist/{ko-L5CJMFDS.js → ko-ZH5ADKZM.js} +5 -1
  25. package/dist/{ms-LSNMJDR2.js → ms-Y2AFPZTU.js} +5 -1
  26. package/dist/{nl-ZB32MB7U.js → nl-OQEWIXNA.js} +5 -1
  27. package/dist/{no-4GTNY7M5.js → no-KPAYJZWY.js} +5 -1
  28. package/dist/{pl-LUHBLTHB.js → pl-WSXK2UUD.js} +5 -1
  29. package/dist/{pt-C4H3UNQ6.js → pt-NWRJRUCK.js} +5 -1
  30. package/dist/{ro-GGITS5V4.js → ro-3CGVMOC6.js} +5 -1
  31. package/dist/{sv-T56RTLBP.js → sv-HGSEBA7S.js} +5 -1
  32. package/dist/test-utils.js +16 -7
  33. package/dist/{th-22X2IWCB.js → th-NZ4G352Z.js} +5 -1
  34. package/dist/{tr-77TFG62H.js → tr-LGGS4DLW.js} +5 -1
  35. package/dist/{uk-MIIL6NXT.js → uk-AIX536L4.js} +5 -1
  36. package/dist/{vi-7JYOIGNV.js → vi-V6CVQZLQ.js} +5 -1
  37. package/dist/{zh-ID6DRYU4.js → zh-AGKPGI3L.js} +5 -1
  38. package/package.json +12 -12
  39. package/src/components/StatusDisplay.tsx +13 -13
  40. package/src/components/__tests__/StatusDisplay.test.tsx +9 -9
  41. package/src/components/modals/ComposeStep.tsx +19 -4
  42. package/src/components/modals/ConfigureGenerationStep.tsx +143 -15
  43. package/src/components/modals/ReferenceWizardModal.tsx +14 -9
  44. package/src/components/modals/ResourceGenerateModal.tsx +10 -5
  45. package/src/components/modals/__tests__/ConfigureGenerationStep.test.tsx +171 -8
  46. package/src/components/modals/__tests__/ReferenceWizardModal.test.tsx +25 -1
  47. package/src/components/modals/__tests__/ResourceGenerateModal.test.tsx +24 -1
  48. package/src/components/modals/__tests__/WizardFooter.test.tsx +3 -5
  49. package/src/components/resource/BrowseView.tsx +3 -3
  50. package/src/components/resource/DownloadFileLink.tsx +1 -1
  51. package/src/components/resource/__tests__/BrowseView.reference-hover.test.tsx +1 -1
  52. package/src/components/resource/__tests__/BrowseView.test.tsx +2 -2
  53. package/src/components/resource/__tests__/download-link.test.tsx +6 -6
  54. package/src/components/resource/panels/ReferenceEntry.tsx +2 -2
  55. package/src/components/resource/panels/ReferencesPanel.tsx +5 -1
  56. package/src/components/resource/panels/__tests__/ReferencesPanel.observable-flow.test.tsx +1 -1
  57. package/src/components/resource/panels/__tests__/ReferencesPanel.test.tsx +51 -2
  58. package/src/components/resource/panels/__tests__/ResourceInfoPanel.test.tsx +1 -1
  59. package/src/features/auth/__tests__/SignInForm.a11y.test.tsx +12 -12
  60. package/src/features/auth/components/SignInForm.tsx +53 -53
  61. package/src/features/auth-welcome/state/__tests__/welcome-state-unit.test.ts +2 -2
  62. package/src/features/resource-compose/components/ResourceComposePage.tsx +18 -3
  63. package/src/features/resource-discovery/__tests__/ResourceDiscoveryPage.test.tsx +1 -1
  64. package/src/features/resource-discovery/components/ResourceDiscoveryPage.tsx +2 -2
  65. package/src/features/resource-discovery/state/discover-state-unit.ts +1 -1
  66. package/src/features/resource-viewer/__tests__/ResourceViewerPage.test.tsx +2 -2
  67. package/src/features/resource-viewer/__tests__/generation-options.test.ts +53 -0
  68. package/src/features/resource-viewer/components/ResourceViewerPage.tsx +16 -25
  69. package/src/features/resource-viewer/generation-options.ts +29 -0
  70. package/src/styles/index.css +0 -1
  71. package/translations/ar.json +4 -0
  72. package/translations/bn.json +4 -0
  73. package/translations/cs.json +4 -0
  74. package/translations/da.json +4 -0
  75. package/translations/de.json +4 -0
  76. package/translations/el.json +4 -0
  77. package/translations/en.json +4 -0
  78. package/translations/es.json +4 -0
  79. package/translations/fa.json +4 -0
  80. package/translations/fi.json +4 -0
  81. package/translations/fr.json +4 -0
  82. package/translations/he.json +4 -0
  83. package/translations/hi.json +4 -0
  84. package/translations/id.json +4 -0
  85. package/translations/it.json +4 -0
  86. package/translations/ja.json +4 -0
  87. package/translations/ko.json +4 -0
  88. package/translations/ms.json +4 -0
  89. package/translations/nl.json +4 -0
  90. package/translations/no.json +4 -0
  91. package/translations/pl.json +4 -0
  92. package/translations/pt.json +4 -0
  93. package/translations/ro.json +4 -0
  94. package/translations/sv.json +4 -0
  95. package/translations/th.json +4 -0
  96. package/translations/tr.json +4 -0
  97. package/translations/uk.json +4 -0
  98. package/translations/vi.json +4 -0
  99. package/translations/zh.json +4 -0
  100. package/src/features/admin-exchange/__tests__/AdminExchangePage.test.tsx +0 -137
  101. package/src/features/admin-exchange/__tests__/ExportCard.test.tsx +0 -41
  102. package/src/features/admin-exchange/__tests__/ImportCard.test.tsx +0 -148
  103. package/src/features/admin-exchange/__tests__/ImportProgress.test.tsx +0 -117
  104. package/src/features/admin-exchange/components/AdminExchangePage.tsx +0 -117
  105. package/src/features/admin-exchange/components/ExportCard.tsx +0 -35
  106. package/src/features/admin-exchange/components/ImportCard.tsx +0 -183
  107. package/src/features/admin-exchange/components/ImportProgress.tsx +0 -80
  108. package/src/features/admin-exchange/state/__tests__/exchange-state-unit.test.ts +0 -185
  109. package/src/features/admin-exchange/state/exchange-state-unit.ts +0 -133
  110. package/src/features/moderation-linked-data/__tests__/LinkedDataPage.test.tsx +0 -118
  111. package/src/features/moderation-linked-data/components/LinkedDataPage.tsx +0 -118
  112. package/src/styles/features/exchange.css +0 -374
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
 
3
3
  import React, { useState } from 'react';
4
- import { LOCALES } from '@semiont/core';
4
+ import { LOCALES, proposeStoragePath } from '@semiont/core';
5
5
  import { CodeMirrorRenderer } from '../CodeMirrorRenderer';
6
6
  import { WizardFooter } from './WizardFooter';
7
7
 
@@ -37,6 +37,12 @@ export interface ComposeParams {
37
37
  export interface ComposeStepProps {
38
38
  draft: ComposeDraft;
39
39
  onDraftChange: (patch: Partial<ComposeDraft>) => void;
40
+ /**
41
+ * Folder of the resource being composed FROM, so the new file lands beside
42
+ * its source (D11). Compose is always `text/markdown`, so the proposal's
43
+ * extension is fixed.
44
+ */
45
+ defaultFolder?: string;
40
46
  /** Entity types fixed when the reference was created — read-only tags when
41
47
  * non-empty (D6): they are not this step's to change. */
42
48
  referenceEntityTypes: string[];
@@ -65,6 +71,7 @@ export interface ComposeStepProps {
65
71
  export function ComposeStep({
66
72
  draft,
67
73
  onDraftChange,
74
+ defaultFolder = '',
68
75
  referenceEntityTypes,
69
76
  entityTypeOptions,
70
77
  showLineNumbers,
@@ -73,17 +80,25 @@ export function ComposeStep({
73
80
  onCompose,
74
81
  translations: t,
75
82
  }: ComposeStepProps) {
83
+ // D11 — the Save location starts filled and follows the title until the
84
+ // user takes it over; `pathTouched` is derived (see ConfigureGenerationStep),
85
+ // so clearing the field restores the proposal. Compose writes markdown.
86
+ const pathTouched = draft.storagePath !== '';
87
+ const effectivePath = pathTouched
88
+ ? draft.storagePath
89
+ : proposeStoragePath(defaultFolder, draft.name, 'text/markdown');
90
+
76
91
  const [isCreating, setIsCreating] = useState(false);
77
92
  const fixedTypes = referenceEntityTypes.length > 0;
78
93
 
79
94
  const handleSubmit = async (e: React.FormEvent) => {
80
95
  e.preventDefault();
81
- if (!draft.name.trim() || !draft.storagePath.trim()) return;
96
+ if (!draft.name.trim() || !effectivePath.trim()) return;
82
97
  setIsCreating(true);
83
98
  try {
84
99
  await onCompose({
85
100
  name: draft.name,
86
- storagePath: `file://${draft.storagePath}`,
101
+ storagePath: `file://${effectivePath}`,
87
102
  content: draft.content,
88
103
  entityTypes: fixedTypes ? referenceEntityTypes : draft.entityTypes,
89
104
  language: draft.language,
@@ -126,7 +141,7 @@ export function ComposeStep({
126
141
  <input
127
142
  id="compose-storagePath"
128
143
  type="text"
129
- value={draft.storagePath}
144
+ value={effectivePath}
130
145
  onChange={(e) => onDraftChange({ storagePath: e.target.value })}
131
146
  placeholder="people/my-resource.md"
132
147
  required
@@ -2,8 +2,9 @@
2
2
 
3
3
  import React, { useEffect } from 'react';
4
4
  import { WizardFooter } from './WizardFooter';
5
- import type { CollaboratorEntry, GatheredContext } from '@semiont/core';
6
- import { LOCALES } from '@semiont/core';
5
+ import type { CollaboratorEntry, GatheredContext, SupportedMediaType } from '@semiont/core';
6
+ import type { GenerationOptions } from '@semiont/sdk';
7
+ import { LOCALES, GENERATABLE_MEDIA_TYPES, capabilitiesOf, proposeStoragePath } from '@semiont/core';
7
8
 
8
9
  /**
9
10
  * Bounds for the max-length control when no ceiling is known. These are the
@@ -15,23 +16,82 @@ const DEFAULT_MAX_TOKENS_CEILING = 4000;
15
16
  /** The step's form values, owned by the wizard so Back cannot discard them (D3). */
16
17
  export interface GenerationDraft {
17
18
  title: string;
19
+ /**
20
+ * The path FRAGMENT the user types beside the `file://` prefix — not a URI.
21
+ * (`GenerationConfig.storageUri` is the assembled one; the names differ
22
+ * because the things differ.)
23
+ */
18
24
  storagePath: string;
19
25
  prompt: string;
20
26
  language: string;
21
27
  temperature: number;
22
28
  /** Text, not number: the field is user-editable and may be mid-edit or empty. */
23
29
  maxTokensText: string;
30
+ /** The artifact's media type. Seeded `text/markdown` by both hosts (D2). */
31
+ outputMediaType: SupportedMediaType;
24
32
  }
25
33
 
26
- export interface GenerationConfig {
27
- title: string;
28
- storagePath: string;
29
- prompt?: string;
30
- language: string;
31
- temperature: number;
32
- maxTokens: number;
33
- context: GatheredContext;
34
- }
34
+ /**
35
+ * What this form fills in. Named here, but TYPED by the SDK (P2b).
36
+ *
37
+ * `storageUri` is on this list rather than a local `storagePath: string`
38
+ * because the two were the same thing under different names — a divergence
39
+ * that survived precisely because nothing tied them together (D8).
40
+ */
41
+ type FormFilled =
42
+ | 'title'
43
+ | 'storageUri'
44
+ | 'language'
45
+ | 'temperature'
46
+ | 'maxTokens'
47
+ | 'outputMediaType';
48
+
49
+ /**
50
+ * The submitted payload: the SDK's own generation options, DERIVED rather
51
+ * than restated (P2b), plus the grounding context.
52
+ *
53
+ * This was a hand-written interface listing the same fields with independent
54
+ * types — the shape `## Duplicated Shape` forbids — and it drifted exactly as
55
+ * predicted: `storagePath: string` held a `storageUri`, so every consumer
56
+ * re-mapped it by hand and silently dropped whatever the mapping forgot.
57
+ * Deriving means a rename or retype in `GenerationOptions` is a compile
58
+ * error here instead of a quiet gap at the wire.
59
+ *
60
+ * NARROWED on purpose: the SDK also carries `task`, `structure`, `cite` and
61
+ * `stallDeadlineMs`, which this form does not collect, and a payload type
62
+ * that advertised them would promise more than the UI delivers. Surfacing one
63
+ * later is a one-word edit to `FormFilled` plus its control — the door is
64
+ * unlocked, not open.
65
+ *
66
+ * `Required` because the form always fills these, even where the SDK lets
67
+ * them be absent; `prompt` stays optional, because a blank instruction field
68
+ * is omitted rather than sent empty.
69
+ */
70
+ export type GenerationConfig =
71
+ & Required<Pick<GenerationOptions, FormFilled>>
72
+ & Pick<GenerationOptions, 'prompt'>
73
+ & { context: GatheredContext };
74
+
75
+ /** The one place a format's required extension is read (D7). */
76
+ const extensionFor = (format: SupportedMediaType): string =>
77
+ capabilitiesOf(format)?.extension ?? '';
78
+
79
+ /**
80
+ * A fresh draft, defined ONCE for both hosts (D8b). The two modals carried
81
+ * byte-identical literals — magic numbers included — so every new field meant
82
+ * two edits and an opportunity to seed only one of them.
83
+ */
84
+ export const freshGenerationDraft = (title: string, locale: string): GenerationDraft => ({
85
+ title,
86
+ storagePath: '',
87
+ prompt: '',
88
+ language: locale,
89
+ temperature: 0.7,
90
+ maxTokensText: '500',
91
+ // D2: markdown is the DEFAULT, not an assumption — the worker would also
92
+ // default to it, but a control must send what it shows.
93
+ outputMediaType: 'text/markdown',
94
+ });
35
95
 
36
96
  export interface ConfigureGenerationStepProps {
37
97
  context: GatheredContext;
@@ -43,6 +103,12 @@ export interface ConfigureGenerationStepProps {
43
103
  /** Absent in a single-stack host (GATHER-AT-THE-TOP D6) — the footer then renders no retreat. */
44
104
  onBack?: () => void;
45
105
  onGenerate: (config: GenerationConfig) => void;
106
+ /**
107
+ * Folder of the resource being generated FROM, so the artifact lands beside
108
+ * its source (D11). The page derives it; this component stays
109
+ * presentational, exactly as it already receives the default title.
110
+ */
111
+ defaultFolder?: string;
46
112
  translations: {
47
113
  resourceTitle: string;
48
114
  resourceTitlePlaceholder: string;
@@ -62,6 +128,15 @@ export interface ConfigureGenerationStepProps {
62
128
  * and `{{model}}`.
63
129
  */
64
130
  maxLengthCeiling: string;
131
+ /** Label for the output-format select. */
132
+ outputFormat: string;
133
+ /**
134
+ * Shown when the Save location's extension contradicts the chosen format,
135
+ * which the form REFUSES to submit (D7 — the worker is faithful and will
136
+ * write a PDF to a `.md` path, so this is the only gate). Interpolates
137
+ * `{{extension}}`.
138
+ */
139
+ formatExtensionMismatch: string;
65
140
  back?: string;
66
141
  generate: string;
67
142
  };
@@ -82,6 +157,7 @@ export function ConfigureGenerationStep({
82
157
  onConfigChange,
83
158
  onBack,
84
159
  onGenerate,
160
+ defaultFolder = '',
85
161
  translations: t,
86
162
  generationAgent,
87
163
  }: ConfigureGenerationStepProps) {
@@ -90,9 +166,28 @@ export function ConfigureGenerationStep({
90
166
  // both sliders. `maxTokensText` stays TEXT rather than a number so the field can
91
167
  // be cleared mid-edit — `parseInt` on an empty field yields NaN, which React
92
168
  // warns about and which used to travel into the job config.
93
- const { title, storagePath, prompt, language, temperature, maxTokensText } = config;
169
+ const { title, storagePath, prompt, language, temperature, maxTokensText, outputMediaType } = config;
94
170
  const set = (patch: Partial<GenerationDraft>) => onConfigChange({ ...config, ...patch });
95
171
 
172
+ // D11 — the Save location starts filled and FOLLOWS the title and format
173
+ // until the user takes it over. `pathTouched` is DERIVED rather than stored:
174
+ // it is exactly "the field holds the user's own text", which makes clearing
175
+ // the field un-touch it for free, keeps it reset-on-open and Back-safe
176
+ // because `storagePath` already is, and leaves no second field to desync.
177
+ const pathTouched = storagePath !== '';
178
+ const proposedPath = proposeStoragePath(defaultFolder, title, outputMediaType);
179
+ const effectivePath = pathTouched ? storagePath : proposedPath;
180
+
181
+ // D7 — the GUI is where a format/extension mismatch is caught, because it is
182
+ // where the person who can fix it is standing. An EMPTY path is not a
183
+ // mismatch: the field is `required`, so emptiness is already refused, and
184
+ // telling an untouched form it is wrong is not a welcome. Under D11 a
185
+ // PROPOSED path always matches, so this now fires only on hand-edits.
186
+ const requiredExtension = extensionFor(outputMediaType);
187
+ const extensionMismatch =
188
+ effectivePath !== '' &&
189
+ !effectivePath.toLowerCase().endsWith(requiredExtension.toLowerCase());
190
+
96
191
  const ceiling = generationAgent?.limits?.maxOutputTokens ?? DEFAULT_MAX_TOKENS_CEILING;
97
192
 
98
193
  /**
@@ -119,14 +214,16 @@ export function ConfigureGenerationStep({
119
214
 
120
215
  const handleSubmit = (e: React.FormEvent) => {
121
216
  e.preventDefault();
217
+ if (extensionMismatch) return;
122
218
  const trimmedPrompt = prompt.trim();
123
219
  onGenerate({
124
220
  title,
125
- storagePath: `file://${storagePath}`,
221
+ storageUri: `file://${effectivePath}`,
126
222
  ...(trimmedPrompt ? { prompt: trimmedPrompt } : {}),
127
223
  language,
128
224
  temperature,
129
225
  maxTokens: submittedMaxTokens,
226
+ outputMediaType,
130
227
  context,
131
228
  });
132
229
  };
@@ -167,13 +264,44 @@ export function ConfigureGenerationStep({
167
264
  <input
168
265
  id="wizard-storagePath"
169
266
  type="text"
170
- value={storagePath}
267
+ value={effectivePath}
171
268
  onChange={(e) => set({ storagePath: e.target.value })}
172
269
  required
270
+ aria-invalid={extensionMismatch}
271
+ {...(extensionMismatch ? { 'aria-describedby': 'wizard-format-mismatch' } : {})}
173
272
  className="semiont-input semiont-input--addon"
174
273
  placeholder="generated/my-resource.md"
175
274
  />
176
275
  </div>
276
+ {/* The reason sits with the field, not on the button: the message
277
+ belongs where the fix is. */}
278
+ {extensionMismatch && (
279
+ <p id="wizard-format-mismatch" className="semiont-form__error" role="alert">
280
+ {t.formatExtensionMismatch.replace('{{extension}}', requiredExtension)}
281
+ </p>
282
+ )}
283
+ </div>
284
+
285
+ {/* Output format — with the artifact's IDENTITY (title, location), not
286
+ among the model knobs below (D5). The options are DERIVED from the
287
+ registry (D1): promote a fourth row to `generatable` and it appears
288
+ here, with no list to forget to update. */}
289
+ <div className="semiont-form__field">
290
+ <label htmlFor="wizard-outputFormat" className="semiont-form__label">
291
+ {t.outputFormat}
292
+ </label>
293
+ <select
294
+ id="wizard-outputFormat"
295
+ value={outputMediaType}
296
+ onChange={(e) => set({ outputMediaType: e.target.value as SupportedMediaType })}
297
+ className="semiont-select"
298
+ >
299
+ {GENERATABLE_MEDIA_TYPES.map((format) => (
300
+ <option key={format} value={format}>
301
+ {capabilitiesOf(format)?.label ?? format}
302
+ </option>
303
+ ))}
304
+ </select>
177
305
  </div>
178
306
 
179
307
  {/* Additional Instructions */}
@@ -275,7 +403,7 @@ export function ConfigureGenerationStep({
275
403
 
276
404
  <WizardFooter
277
405
  {...(onBack && t.back ? { backLabel: t.back, onBack } : {})}
278
- primary={{ label: t.generate, type: 'submit' }}
406
+ primary={{ label: t.generate, type: 'submit', disabled: extensionMismatch }}
279
407
  />
280
408
  </form>
281
409
  );
@@ -8,7 +8,7 @@ import { useSemiont } from '../../session/SemiontProvider';
8
8
  import { useObservable } from '../../hooks/useObservable';
9
9
  import { useEventSubscription } from '../../contexts/useEventSubscription';
10
10
  import { GatherContextStep } from './GatherContextStep';
11
- import { ConfigureGenerationStep } from './ConfigureGenerationStep';
11
+ import { ConfigureGenerationStep, freshGenerationDraft } from './ConfigureGenerationStep';
12
12
  import type { GenerationConfig } from './ConfigureGenerationStep';
13
13
  import { ConfigureSearchStep } from './ConfigureSearchStep';
14
14
  import type { SearchConfig } from './ConfigureSearchStep';
@@ -42,6 +42,8 @@ export interface ReferenceWizardModalProps {
42
42
  resourceId: string | null;
43
43
  /** Default title (selected text) */
44
44
  defaultTitle: string;
45
+ /** Folder of the source resource, so new files land beside it (D11). */
46
+ defaultFolder?: string;
45
47
  /** Entity types from the annotation */
46
48
  entityTypes: string[];
47
49
  /**
@@ -111,6 +113,8 @@ export interface ReferenceWizardModalProps {
111
113
  maxLength: string;
112
114
  maxLengthHelp: string;
113
115
  maxLengthCeiling: string;
116
+ outputFormat: string;
117
+ formatExtensionMismatch: string;
114
118
  maxResults: string;
115
119
  semanticScoring: string;
116
120
  semanticScoringHelp: string;
@@ -131,6 +135,7 @@ export function ReferenceWizardModal({
131
135
  annotationId,
132
136
  resourceId,
133
137
  defaultTitle,
138
+ defaultFolder,
134
139
  entityTypes,
135
140
  resourceName,
136
141
  locale,
@@ -151,10 +156,9 @@ export function ReferenceWizardModal({
151
156
  // back unmounts a step; if the step owned its values, Back would silently discard
152
157
  // everything typed — which is exactly what it used to do.
153
158
  const [searchConfig, setSearchConfig] = useState<SearchConfig>({ limit: 10, useSemanticScoring: true });
154
- const [generationDraft, setGenerationDraft] = useState<GenerationDraft>({
155
- title: defaultTitle, storagePath: '', prompt: '', language: locale,
156
- temperature: 0.7, maxTokensText: '500',
157
- });
159
+ const [generationDraft, setGenerationDraft] = useState<GenerationDraft>(
160
+ () => freshGenerationDraft(defaultTitle, locale),
161
+ );
158
162
  const [isSearching, setIsSearching] = useState(false);
159
163
  const [searchError, setSearchError] = useState<string | null>(null);
160
164
  const [userHint, setUserHint] = useState('');
@@ -179,10 +183,7 @@ export function ReferenceWizardModal({
179
183
  seededTitle.current = defaultTitle;
180
184
  setWizardStep({ step: 'gather' });
181
185
  setSearchConfig({ limit: 10, useSemanticScoring: true });
182
- setGenerationDraft({
183
- title: defaultTitle, storagePath: '', prompt: '', language: locale,
184
- temperature: 0.7, maxTokensText: '500',
185
- });
186
+ setGenerationDraft(freshGenerationDraft(defaultTitle, locale));
186
187
  setIsSearching(false);
187
188
  setSearchError(null);
188
189
  setUserHint('');
@@ -425,6 +426,7 @@ export function ReferenceWizardModal({
425
426
  <ComposeStep
426
427
  draft={composeDraft}
427
428
  onDraftChange={(patch) => setComposeDraft((d) => ({ ...d, ...patch }))}
429
+ defaultFolder={defaultFolder}
428
430
  referenceEntityTypes={entityTypes}
429
431
  entityTypeOptions={entityTypeOptions}
430
432
  showLineNumbers={showLineNumbers}
@@ -461,6 +463,7 @@ export function ReferenceWizardModal({
461
463
  context={contextWithHint ?? context}
462
464
  config={generationDraft}
463
465
  onConfigChange={setGenerationDraft}
466
+ defaultFolder={defaultFolder}
464
467
  onBack={handleBackToGather}
465
468
  onGenerate={handleGenerateSubmit}
466
469
  translations={{
@@ -477,6 +480,8 @@ export function ReferenceWizardModal({
477
480
  maxLength: t.maxLength,
478
481
  maxLengthHelp: t.maxLengthHelp,
479
482
  maxLengthCeiling: t.maxLengthCeiling,
483
+ outputFormat: t.outputFormat,
484
+ formatExtensionMismatch: t.formatExtensionMismatch,
480
485
  back: t.back,
481
486
  generate: t.generate,
482
487
  }}
@@ -7,7 +7,7 @@ import { Dialog, DialogPanel, DialogTitle, Transition, TransitionChild } from '@
7
7
  import { ConfigureGatherStep, type ResourceGatherConfig } from './ConfigureGatherStep';
8
8
  import { DiscardPrompt } from './DiscardPrompt';
9
9
  import { GatherContextStep } from './GatherContextStep';
10
- import { ConfigureGenerationStep, type GenerationConfig, type GenerationDraft } from './ConfigureGenerationStep';
10
+ import { ConfigureGenerationStep, freshGenerationDraft, type GenerationConfig, type GenerationDraft } from './ConfigureGenerationStep';
11
11
 
12
12
  export interface ResourceGenerateModalTranslations {
13
13
  /** The ONE modal title — names the flow, not a step. */
@@ -51,6 +51,8 @@ export interface ResourceGenerateModalTranslations {
51
51
  maxLength: string;
52
52
  maxLengthHelp: string;
53
53
  maxLengthCeiling: string;
54
+ outputFormat: string;
55
+ formatExtensionMismatch: string;
54
56
  generate: string;
55
57
  // DiscardPrompt (GATHER-AT-THE-TOP P1)
56
58
  discardDraftPrompt: string;
@@ -69,6 +71,8 @@ export interface ResourceGenerateModalProps {
69
71
  onClose: () => void;
70
72
  resourceId: string;
71
73
  defaultTitle: string;
74
+ /** Folder of the source resource, so the artifact lands beside it (D11). */
75
+ defaultFolder?: string;
72
76
  locale: string;
73
77
  /**
74
78
  * Entity types offered in the exclusion picker. Owner-supplied so the
@@ -115,6 +119,7 @@ export function ResourceGenerateModal({
115
119
  onClose,
116
120
  resourceId,
117
121
  defaultTitle,
122
+ defaultFolder,
118
123
  locale,
119
124
  entityTypeOptions = [],
120
125
  onGenerateSubmit,
@@ -127,10 +132,7 @@ export function ResourceGenerateModal({
127
132
  }: ResourceGenerateModalProps) {
128
133
  // Same draft ownership as the wizard (WIZARD-NAVIGATION D3): the step is
129
134
  // controlled, so stepping back through this modal keeps what was typed.
130
- const freshDraft = (): GenerationDraft => ({
131
- title: defaultTitle, storagePath: '', prompt: '', language: locale,
132
- temperature: 0.7, maxTokensText: '500',
133
- });
135
+ const freshDraft = (): GenerationDraft => freshGenerationDraft(defaultTitle, locale);
134
136
  const [generationDraft, setGenerationDraft] = useState<GenerationDraft>(freshDraft);
135
137
 
136
138
  // "Has THIS run gathered yet?" — the page's gather slots survive across
@@ -358,6 +360,7 @@ export function ResourceGenerateModal({
358
360
  context={gatherContext}
359
361
  config={generationDraft}
360
362
  onConfigChange={setGenerationDraft}
363
+ defaultFolder={defaultFolder}
361
364
  onGenerate={handleGenerate}
362
365
  translations={{
363
366
  resourceTitle: t.resourceTitle,
@@ -373,6 +376,8 @@ export function ResourceGenerateModal({
373
376
  maxLength: t.maxLength,
374
377
  maxLengthHelp: t.maxLengthHelp,
375
378
  maxLengthCeiling: t.maxLengthCeiling,
379
+ outputFormat: t.outputFormat,
380
+ formatExtensionMismatch: t.formatExtensionMismatch,
376
381
  generate: t.generate,
377
382
  }}
378
383
  />