@semiont/react-ui 0.3.8 → 0.4.0
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/{EventBusContext-CLnb2LmB.d.mts → EventBusContext-DUIMowqQ.d.mts} +5 -2
- package/dist/index.d.mts +7 -13
- package/dist/index.mjs +47 -3
- package/dist/index.mjs.map +1 -1
- package/dist/test-utils.d.mts +2 -2
- package/package.json +1 -1
- package/src/components/modals/ConfigureGenerationStep.tsx +22 -0
- package/src/components/navigation/SortableResourceTab.tsx +4 -1
- package/src/features/resource-compose/__tests__/ResourceComposePage.test.tsx +12 -5
- package/src/features/resource-compose/components/ResourceComposePage.tsx +25 -0
- package/src/features/resource-viewer/components/ResourceViewerPage.tsx +2 -1
- package/src/styles/core/inputs.css +66 -0
package/dist/test-utils.d.mts
CHANGED
|
@@ -2,8 +2,8 @@ import { ReactElement } from 'react';
|
|
|
2
2
|
import { RenderOptions, RenderResult } from '@testing-library/react';
|
|
3
3
|
export * from '@testing-library/react';
|
|
4
4
|
import { QueryClient } from '@tanstack/react-query';
|
|
5
|
-
import { T as TranslationManager, S as SessionManager, O as OpenResourcesManager } from './EventBusContext-
|
|
6
|
-
export { r as resetEventBusForTesting } from './EventBusContext-
|
|
5
|
+
import { T as TranslationManager, S as SessionManager, O as OpenResourcesManager } from './EventBusContext-DUIMowqQ.mjs';
|
|
6
|
+
export { r as resetEventBusForTesting } from './EventBusContext-DUIMowqQ.mjs';
|
|
7
7
|
import { EventBus } from '@semiont/core';
|
|
8
8
|
export { vi } from 'vitest';
|
|
9
9
|
import 'react/jsx-runtime';
|
package/package.json
CHANGED
|
@@ -6,6 +6,7 @@ import { LOCALES } from '@semiont/api-client';
|
|
|
6
6
|
|
|
7
7
|
export interface GenerationConfig {
|
|
8
8
|
title: string;
|
|
9
|
+
storagePath: string;
|
|
9
10
|
prompt?: string;
|
|
10
11
|
language: string;
|
|
11
12
|
temperature: number;
|
|
@@ -48,6 +49,7 @@ export function ConfigureGenerationStep({
|
|
|
48
49
|
translations: t,
|
|
49
50
|
}: ConfigureGenerationStepProps) {
|
|
50
51
|
const [title, setTitle] = useState(defaultTitle);
|
|
52
|
+
const [storagePath, setStoragePath] = useState('');
|
|
51
53
|
const [prompt, setPrompt] = useState('');
|
|
52
54
|
const [language, setLanguage] = useState(locale);
|
|
53
55
|
const [temperature, setTemperature] = useState(0.7);
|
|
@@ -58,6 +60,7 @@ export function ConfigureGenerationStep({
|
|
|
58
60
|
const trimmedPrompt = prompt.trim();
|
|
59
61
|
onGenerate({
|
|
60
62
|
title,
|
|
63
|
+
storagePath: `file://${storagePath}`,
|
|
61
64
|
...(trimmedPrompt ? { prompt: trimmedPrompt } : {}),
|
|
62
65
|
language,
|
|
63
66
|
temperature,
|
|
@@ -84,6 +87,25 @@ export function ConfigureGenerationStep({
|
|
|
84
87
|
/>
|
|
85
88
|
</div>
|
|
86
89
|
|
|
90
|
+
{/* Storage URI */}
|
|
91
|
+
<div className="semiont-form__field">
|
|
92
|
+
<label htmlFor="wizard-storagePath" className="semiont-form__label">
|
|
93
|
+
Save location
|
|
94
|
+
</label>
|
|
95
|
+
<div className="semiont-input-addon">
|
|
96
|
+
<span className="semiont-input-addon__prefix">file://</span>
|
|
97
|
+
<input
|
|
98
|
+
id="wizard-storagePath"
|
|
99
|
+
type="text"
|
|
100
|
+
value={storagePath}
|
|
101
|
+
onChange={(e) => setStoragePath(e.target.value)}
|
|
102
|
+
required
|
|
103
|
+
className="semiont-input semiont-input--addon"
|
|
104
|
+
placeholder="generated/my-resource.md"
|
|
105
|
+
/>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
|
|
87
109
|
{/* Additional Instructions */}
|
|
88
110
|
<div className="semiont-form__field">
|
|
89
111
|
<label htmlFor="wizard-prompt" className="semiont-form__label">
|
|
@@ -46,6 +46,9 @@ export function SortableResourceTab({
|
|
|
46
46
|
|
|
47
47
|
const iconEmoji = getResourceIcon(resource.mediaType);
|
|
48
48
|
const isCurrentlyDragging = isSortableDragging || isDragging;
|
|
49
|
+
const tooltipText = resource.storageUri
|
|
50
|
+
? resource.storageUri.replace(/^file:\/\//, '')
|
|
51
|
+
: resource.name;
|
|
49
52
|
|
|
50
53
|
// Handle keyboard shortcuts for reordering (Alt + Up/Down)
|
|
51
54
|
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
@@ -76,7 +79,7 @@ export function SortableResourceTab({
|
|
|
76
79
|
<LinkComponent
|
|
77
80
|
href={href}
|
|
78
81
|
className="semiont-resource-tab__link"
|
|
79
|
-
title={
|
|
82
|
+
title={tooltipText}
|
|
80
83
|
>
|
|
81
84
|
<span className="semiont-resource-tab__icon" aria-hidden="true">
|
|
82
85
|
{iconEmoji}
|
|
@@ -324,6 +324,10 @@ describe('ResourceComposePage', () => {
|
|
|
324
324
|
const nameInput = screen.getByLabelText('Resource Name');
|
|
325
325
|
fireEvent.change(nameInput, { target: { value: 'Test Resource' } });
|
|
326
326
|
|
|
327
|
+
// Fill in save location
|
|
328
|
+
const storagePathInput = screen.getByLabelText('Save location');
|
|
329
|
+
fireEvent.change(storagePathInput, { target: { value: 'docs/test-resource.md' } });
|
|
330
|
+
|
|
327
331
|
// Fill in content
|
|
328
332
|
const editor = screen.getByTestId('code-editor');
|
|
329
333
|
fireEvent.change(editor, { target: { value: 'Test content' } });
|
|
@@ -336,15 +340,11 @@ describe('ResourceComposePage', () => {
|
|
|
336
340
|
expect(onSaveResource).toHaveBeenCalledWith({
|
|
337
341
|
mode: 'new',
|
|
338
342
|
name: 'Test Resource',
|
|
343
|
+
storageUri: 'file://docs/test-resource.md',
|
|
339
344
|
content: 'Test content',
|
|
340
|
-
file: undefined,
|
|
341
345
|
format: 'text/markdown',
|
|
342
|
-
charset: undefined,
|
|
343
346
|
entityTypes: [],
|
|
344
347
|
language: 'en',
|
|
345
|
-
archiveOriginal: undefined,
|
|
346
|
-
referenceId: undefined,
|
|
347
|
-
sourceDocumentId: undefined,
|
|
348
348
|
});
|
|
349
349
|
});
|
|
350
350
|
});
|
|
@@ -358,6 +358,10 @@ describe('ResourceComposePage', () => {
|
|
|
358
358
|
const nameInput = screen.getByLabelText('Resource Name');
|
|
359
359
|
fireEvent.change(nameInput, { target: { value: 'Test Resource' } });
|
|
360
360
|
|
|
361
|
+
// Fill in save location
|
|
362
|
+
const storagePathInput = screen.getByLabelText('Save location');
|
|
363
|
+
fireEvent.change(storagePathInput, { target: { value: 'docs/test-resource.md' } });
|
|
364
|
+
|
|
361
365
|
// Select entity type
|
|
362
366
|
const documentButton = screen.getByRole('button', { name: /Document entity type/ });
|
|
363
367
|
fireEvent.click(documentButton);
|
|
@@ -402,6 +406,9 @@ describe('ResourceComposePage', () => {
|
|
|
402
406
|
const nameInput = screen.getByLabelText('Resource Name');
|
|
403
407
|
fireEvent.change(nameInput, { target: { value: 'Test Resource' } });
|
|
404
408
|
|
|
409
|
+
const storagePathInput = screen.getByLabelText('Save location');
|
|
410
|
+
fireEvent.change(storagePathInput, { target: { value: 'docs/test-resource.md' } });
|
|
411
|
+
|
|
405
412
|
const submitButton = screen.getByRole('button', { name: 'Create Resource' });
|
|
406
413
|
fireEvent.click(submitButton);
|
|
407
414
|
|
|
@@ -96,6 +96,7 @@ export interface ResourceComposePageProps {
|
|
|
96
96
|
export interface SaveResourceParams {
|
|
97
97
|
mode: 'new' | 'clone' | 'reference';
|
|
98
98
|
name: string;
|
|
99
|
+
storageUri: string;
|
|
99
100
|
content?: string;
|
|
100
101
|
file?: File;
|
|
101
102
|
format?: string;
|
|
@@ -150,6 +151,9 @@ export function ResourceComposePage({
|
|
|
150
151
|
// Character encoding selection - default to UTF-8 (empty string means use default)
|
|
151
152
|
const [selectedCharset, setSelectedCharset] = useState<string>('');
|
|
152
153
|
|
|
154
|
+
// Working-tree path (bare, no protocol prefix) — converted to file:// URI on submit
|
|
155
|
+
const [storagePath, setStoragePath] = useState('');
|
|
156
|
+
|
|
153
157
|
// Archive original checkbox (for clones only)
|
|
154
158
|
const [archiveOriginal, setArchiveOriginal] = useState(true);
|
|
155
159
|
|
|
@@ -216,6 +220,7 @@ export function ResourceComposePage({
|
|
|
216
220
|
const params: SaveResourceParams = {
|
|
217
221
|
mode,
|
|
218
222
|
name: newResourceName,
|
|
223
|
+
storageUri: `file://${storagePath}`,
|
|
219
224
|
content: newResourceContent,
|
|
220
225
|
format: uploadedFile ? fileMimeType : selectedFormat,
|
|
221
226
|
entityTypes: selectedEntityTypes,
|
|
@@ -352,6 +357,26 @@ export function ResourceComposePage({
|
|
|
352
357
|
/>
|
|
353
358
|
</div>
|
|
354
359
|
|
|
360
|
+
{/* Storage URI */}
|
|
361
|
+
<div className="semiont-form__field">
|
|
362
|
+
<label htmlFor="storagePath" className="semiont-form__label">
|
|
363
|
+
Save location
|
|
364
|
+
</label>
|
|
365
|
+
<div className="semiont-input-addon">
|
|
366
|
+
<span className="semiont-input-addon__prefix">file://</span>
|
|
367
|
+
<input
|
|
368
|
+
id="storagePath"
|
|
369
|
+
type="text"
|
|
370
|
+
value={storagePath}
|
|
371
|
+
onChange={(e) => setStoragePath(e.target.value)}
|
|
372
|
+
placeholder="docs/my-resource.md"
|
|
373
|
+
required
|
|
374
|
+
className="semiont-input semiont-input--addon"
|
|
375
|
+
disabled={isCreating}
|
|
376
|
+
/>
|
|
377
|
+
</div>
|
|
378
|
+
</div>
|
|
379
|
+
|
|
355
380
|
{/* Entity Types Selection */}
|
|
356
381
|
{(!isReferenceCompletion || selectedEntityTypes.length === 0) && (
|
|
357
382
|
<div className="semiont-form__field semiont-form__entity-types">
|
|
@@ -195,6 +195,7 @@ export function ResourceViewerPage({
|
|
|
195
195
|
const handleWizardGenerateSubmit = useCallback((referenceId: string, config: GenerationConfig) => {
|
|
196
196
|
onGenerateDocument(referenceId, {
|
|
197
197
|
title: config.title,
|
|
198
|
+
storageUri: config.storagePath,
|
|
198
199
|
prompt: config.prompt,
|
|
199
200
|
language: config.language,
|
|
200
201
|
temperature: config.temperature,
|
|
@@ -253,7 +254,7 @@ export function ResourceViewerPage({
|
|
|
253
254
|
useEffect(() => {
|
|
254
255
|
if (resource && rUri) {
|
|
255
256
|
const mediaType = getPrimaryMediaType(resource);
|
|
256
|
-
addResource(rUri, resource.name, mediaType || undefined);
|
|
257
|
+
addResource(rUri, resource.name, mediaType || undefined, resource.storageUri);
|
|
257
258
|
if (typeof localStorage !== 'undefined') {
|
|
258
259
|
localStorage.setItem('lastViewedDocumentId', rUri);
|
|
259
260
|
}
|
|
@@ -105,6 +105,72 @@
|
|
|
105
105
|
font-size: var(--semiont-text-base);
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
+
/* Input with inline prefix (e.g. "file://") */
|
|
109
|
+
.semiont-input-addon {
|
|
110
|
+
display: flex;
|
|
111
|
+
align-items: stretch;
|
|
112
|
+
border: 1px solid var(--semiont-color-gray-300);
|
|
113
|
+
border-radius: var(--semiont-radius-md);
|
|
114
|
+
overflow: hidden;
|
|
115
|
+
background-color: var(--semiont-bg-primary);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
[data-theme="dark"] .semiont-input-addon {
|
|
119
|
+
border-color: var(--semiont-color-gray-700);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
.semiont-input-addon:focus-within {
|
|
123
|
+
outline: 2px solid var(--semiont-color-primary-500);
|
|
124
|
+
outline-offset: -1px;
|
|
125
|
+
border-color: var(--semiont-color-primary-500);
|
|
126
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
[data-theme="dark"] .semiont-input-addon:focus-within {
|
|
130
|
+
outline-color: var(--semiont-color-primary-400);
|
|
131
|
+
border-color: var(--semiont-color-primary-400);
|
|
132
|
+
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.semiont-input-addon__prefix {
|
|
136
|
+
display: flex;
|
|
137
|
+
align-items: center;
|
|
138
|
+
padding: 0.5rem 0.5rem 0.5rem 0.75rem;
|
|
139
|
+
font-size: var(--semiont-text-sm);
|
|
140
|
+
font-family: var(--semiont-font-mono, monospace);
|
|
141
|
+
color: var(--semiont-text-tertiary);
|
|
142
|
+
background-color: var(--semiont-color-gray-50);
|
|
143
|
+
border-right: 1px solid var(--semiont-color-gray-300);
|
|
144
|
+
user-select: none;
|
|
145
|
+
white-space: nowrap;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
[data-theme="dark"] .semiont-input-addon__prefix {
|
|
149
|
+
background-color: var(--semiont-color-gray-800);
|
|
150
|
+
border-right-color: var(--semiont-color-gray-700);
|
|
151
|
+
color: var(--semiont-text-tertiary);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
.semiont-input--addon {
|
|
155
|
+
border: none;
|
|
156
|
+
border-radius: 0;
|
|
157
|
+
outline: none;
|
|
158
|
+
box-shadow: none;
|
|
159
|
+
flex: 1;
|
|
160
|
+
min-width: 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
[data-theme="dark"] .semiont-input--addon {
|
|
164
|
+
border: none;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
.semiont-input--addon:focus-visible,
|
|
168
|
+
.semiont-input--addon:focus {
|
|
169
|
+
outline: none;
|
|
170
|
+
border: none;
|
|
171
|
+
box-shadow: none;
|
|
172
|
+
}
|
|
173
|
+
|
|
108
174
|
/* Placeholder */
|
|
109
175
|
.semiont-input::placeholder {
|
|
110
176
|
color: var(--semiont-text-tertiary);
|