@vertesia/ui 1.6.0-dev.20261004.085740Z → 1.6.0-dev.20261008.064737Z
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/lib/boot/vite.d.ts +3 -0
- package/lib/boot/vite.d.ts.map +1 -1
- package/lib/boot/vite.js +12 -0
- package/lib/boot/vite.js.map +1 -1
- package/lib/core/components/shadcn/tabs.d.ts +2 -1
- package/lib/core/components/shadcn/tabs.d.ts.map +1 -1
- package/lib/core/components/shadcn/tabs.js +6 -4
- package/lib/core/components/shadcn/tabs.js.map +1 -1
- package/lib/env/index.d.ts +8 -0
- package/lib/env/index.d.ts.map +1 -1
- package/lib/env/index.js.map +1 -1
- package/lib/features/agent/chat/AgentRequestInputOverlay.d.ts +1 -1
- package/lib/features/agent/chat/AgentRequestInputOverlay.d.ts.map +1 -1
- package/lib/features/agent/chat/AgentRequestInputOverlay.js +16 -10
- package/lib/features/agent/chat/AgentRequestInputOverlay.js.map +1 -1
- package/lib/features/agent/chat/AskUserWidget.d.ts +7 -1
- package/lib/features/agent/chat/AskUserWidget.d.ts.map +1 -1
- package/lib/features/agent/chat/AskUserWidget.js +15 -15
- package/lib/features/agent/chat/AskUserWidget.js.map +1 -1
- package/lib/features/media-viewer/ImagePanel.d.ts +1 -1
- package/lib/features/media-viewer/ImagePanel.d.ts.map +1 -1
- package/lib/features/media-viewer/ImagePanel.js +40 -10
- package/lib/features/media-viewer/ImagePanel.js.map +1 -1
- package/lib/features/store/types/IntakePolicyEditor.d.ts.map +1 -1
- package/lib/features/store/types/IntakePolicyEditor.js +1 -1
- package/lib/features/store/types/IntakePolicyEditor.js.map +1 -1
- package/lib/i18n/locales/ar.json +14 -0
- package/lib/i18n/locales/de.json +14 -0
- package/lib/i18n/locales/en.json +14 -0
- package/lib/i18n/locales/es.json +14 -0
- package/lib/i18n/locales/fr.json +14 -0
- package/lib/i18n/locales/it.json +14 -0
- package/lib/i18n/locales/ja.json +14 -0
- package/lib/i18n/locales/ko.json +14 -0
- package/lib/i18n/locales/pt.json +14 -0
- package/lib/i18n/locales/ru.json +14 -0
- package/lib/i18n/locales/tr.json +14 -0
- package/lib/i18n/locales/zh-TW.json +14 -0
- package/lib/i18n/locales/zh.json +14 -0
- package/lib/session/auth/oauth.d.ts.map +1 -1
- package/lib/session/auth/oauth.js +5 -3
- package/lib/session/auth/oauth.js.map +1 -1
- package/lib/shell/AuthScreenPreview.d.ts.map +1 -1
- package/lib/shell/AuthScreenPreview.js +1 -0
- package/lib/shell/AuthScreenPreview.js.map +1 -1
- package/lib/shell/login/SignInFlow.d.ts +3 -1
- package/lib/shell/login/SignInFlow.d.ts.map +1 -1
- package/lib/shell/login/SignInFlow.js +13 -4
- package/lib/shell/login/SignInFlow.js.map +1 -1
- package/lib/shell/login/SignInIcons.d.ts.map +1 -1
- package/lib/shell/login/SignInIcons.js +2 -1
- package/lib/shell/login/SignInIcons.js.map +1 -1
- package/lib/shell/login/SignInPasswordStep.d.ts +9 -0
- package/lib/shell/login/SignInPasswordStep.d.ts.map +1 -0
- package/lib/shell/login/SignInPasswordStep.js +58 -0
- package/lib/shell/login/SignInPasswordStep.js.map +1 -0
- package/lib/shell/login/SignInPrimitives.d.ts +13 -0
- package/lib/shell/login/SignInPrimitives.d.ts.map +1 -1
- package/lib/shell/login/SignInPrimitives.js +11 -1
- package/lib/shell/login/SignInPrimitives.js.map +1 -1
- package/lib/shell/login/SignInProvidersStep.d.ts +3 -1
- package/lib/shell/login/SignInProvidersStep.d.ts.map +1 -1
- package/lib/shell/login/SignInProvidersStep.js +4 -2
- package/lib/shell/login/SignInProvidersStep.js.map +1 -1
- package/lib/shell/login/SignInReturningStep.d.ts +3 -1
- package/lib/shell/login/SignInReturningStep.d.ts.map +1 -1
- package/lib/shell/login/SignInReturningStep.js +18 -8
- package/lib/shell/login/SignInReturningStep.js.map +1 -1
- package/lib/shell/login/SignInTenantStep.d.ts +3 -1
- package/lib/shell/login/SignInTenantStep.d.ts.map +1 -1
- package/lib/shell/login/SignInTenantStep.js +2 -2
- package/lib/shell/login/SignInTenantStep.js.map +1 -1
- package/lib/shell/login/SignInWithProviderButton.d.ts +5 -3
- package/lib/shell/login/SignInWithProviderButton.d.ts.map +1 -1
- package/lib/shell/login/SignInWithProviderButton.js +8 -7
- package/lib/shell/login/SignInWithProviderButton.js.map +1 -1
- package/lib/shell/login/SigninScreen.d.ts.map +1 -1
- package/lib/shell/login/SigninScreen.js +9 -3
- package/lib/shell/login/SigninScreen.js.map +1 -1
- package/lib/shell/login/signInUtils.d.ts +24 -4
- package/lib/shell/login/signInUtils.d.ts.map +1 -1
- package/lib/shell/login/signInUtils.js +53 -1
- package/lib/shell/login/signInUtils.js.map +1 -1
- package/lib/vertesia-ui-boot.js.map +1 -1
- package/lib/vertesia-ui-core.js +42 -42
- package/lib/vertesia-ui-core.js.map +1 -1
- package/lib/vertesia-ui-env.js.map +1 -1
- package/lib/vertesia-ui-features.js +339 -339
- package/lib/vertesia-ui-features.js.map +1 -1
- package/lib/vertesia-ui-i18n.B0orbJb6.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.B0orbJb6.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.BChwIQVA.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.BChwIQVA.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.BTEcAVQm.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.BTEcAVQm.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.BeJQKbv7.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.BeJQKbv7.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.BpGr0-5g.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.BpGr0-5g.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.Bs8x-1wI.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.Bs8x-1wI.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.BymQoOm0.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.BymQoOm0.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.CNT_wabV.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.CNT_wabV.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.Cxh1iVk0.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.Cxh1iVk0.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.DLYCM9fV.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.DLYCM9fV.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.QvAVguDs.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.QvAVguDs.chunk.js.map +1 -0
- package/lib/vertesia-ui-i18n.js +2 -2
- package/lib/vertesia-ui-i18n.js.map +1 -1
- package/lib/vertesia-ui-i18n.smozeSmI.chunk.js +5 -0
- package/lib/vertesia-ui-i18n.smozeSmI.chunk.js.map +1 -0
- package/lib/vertesia-ui-layout.js +15 -15
- package/lib/vertesia-ui-layout.js.map +1 -1
- package/lib/vertesia-ui-rich-text.js +40 -40
- package/lib/vertesia-ui-rich-text.js.map +1 -1
- package/lib/vertesia-ui-router.js.map +1 -1
- package/lib/vertesia-ui-session.js +1 -1
- package/lib/vertesia-ui-session.js.map +1 -1
- package/lib/vertesia-ui-shell.js +54 -47
- package/lib/vertesia-ui-shell.js.map +1 -1
- package/lib/vertesia-ui-widgets.js +47 -47
- package/lib/vertesia-ui-widgets.js.map +1 -1
- package/package.json +20 -20
- package/src/boot/vite.ts +15 -0
- package/src/core/components/shadcn/tabs.tsx +9 -3
- package/src/env/index.ts +10 -1
- package/src/features/agent/chat/AgentRequestInputOverlay.tsx +73 -34
- package/src/features/agent/chat/AskUserWidget.tsx +222 -205
- package/src/features/media-viewer/ImagePanel.tsx +51 -10
- package/src/features/store/types/IntakePolicyEditor.tsx +2 -1
- package/src/i18n/locales/ar.json +14 -0
- package/src/i18n/locales/de.json +14 -0
- package/src/i18n/locales/en.json +14 -0
- package/src/i18n/locales/es.json +14 -0
- package/src/i18n/locales/fr.json +14 -0
- package/src/i18n/locales/it.json +14 -0
- package/src/i18n/locales/ja.json +14 -0
- package/src/i18n/locales/ko.json +14 -0
- package/src/i18n/locales/pt.json +14 -0
- package/src/i18n/locales/ru.json +14 -0
- package/src/i18n/locales/tr.json +14 -0
- package/src/i18n/locales/zh-TW.json +14 -0
- package/src/i18n/locales/zh.json +14 -0
- package/src/session/auth/oauth.ts +8 -3
- package/src/shell/AuthScreenPreview.tsx +1 -0
- package/src/shell/login/SignInFlow.tsx +23 -2
- package/src/shell/login/SignInIcons.tsx +2 -1
- package/src/shell/login/SignInPasswordStep.tsx +103 -0
- package/src/shell/login/SignInPrimitives.tsx +53 -1
- package/src/shell/login/SignInProvidersStep.tsx +8 -4
- package/src/shell/login/SignInReturningStep.tsx +20 -8
- package/src/shell/login/SignInTenantStep.tsx +6 -2
- package/src/shell/login/SignInWithProviderButton.tsx +11 -7
- package/src/shell/login/SigninScreen.tsx +8 -3
- package/src/shell/login/signInUtils.ts +68 -8
- package/lib/vertesia-ui-i18n.BCPusS2_.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BCPusS2_.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.BHWlHchf.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BHWlHchf.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.BkNx8SZu.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.BkNx8SZu.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.C1LasLrO.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.C1LasLrO.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.D8PQ5yKU.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.D8PQ5yKU.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DUtVV1Bq.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DUtVV1Bq.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DgA36_k5.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DgA36_k5.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DmG-fjjZ.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DmG-fjjZ.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.DreWvQIk.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.DreWvQIk.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.Pp8Tj48d.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.Pp8Tj48d.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.gais--x5.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.gais--x5.chunk.js.map +0 -1
- package/lib/vertesia-ui-i18n.xkqaKpY_.chunk.js +0 -5
- package/lib/vertesia-ui-i18n.xkqaKpY_.chunk.js.map +0 -1
|
@@ -64,6 +64,12 @@ export interface AskUserWidgetProps {
|
|
|
64
64
|
compact?: boolean;
|
|
65
65
|
/** Render as a resolved transcript prompt, without pending controls */
|
|
66
66
|
answered?: boolean;
|
|
67
|
+
/** Optional action beside the question, inside the existing card header. */
|
|
68
|
+
headerAction?: React.ReactNode;
|
|
69
|
+
/** ID of the response controls for an accessible disclosure action. */
|
|
70
|
+
responseControlsId?: string;
|
|
71
|
+
/** Hide response controls while keeping drafts and selections mounted. */
|
|
72
|
+
responseControlsHidden?: boolean;
|
|
67
73
|
|
|
68
74
|
// Styling props for full customization
|
|
69
75
|
/** Additional className for the outer container */
|
|
@@ -146,6 +152,9 @@ export function AskUserWidget({
|
|
|
146
152
|
hideBorder = false,
|
|
147
153
|
compact = false,
|
|
148
154
|
answered = false,
|
|
155
|
+
headerAction,
|
|
156
|
+
responseControlsId,
|
|
157
|
+
responseControlsHidden = false,
|
|
149
158
|
// Styling props
|
|
150
159
|
className,
|
|
151
160
|
cardClassName,
|
|
@@ -260,124 +269,127 @@ export function AskUserWidget({
|
|
|
260
269
|
)}
|
|
261
270
|
</div>
|
|
262
271
|
</div>
|
|
272
|
+
{headerAction}
|
|
263
273
|
</div>
|
|
264
274
|
</div>
|
|
265
275
|
|
|
266
|
-
{
|
|
267
|
-
|
|
268
|
-
{
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
>
|
|
284
|
-
<input
|
|
285
|
-
type="checkbox"
|
|
286
|
-
checked={selected}
|
|
287
|
-
onChange={() => toggleOption(option.id)}
|
|
288
|
-
disabled={isLoading}
|
|
289
|
-
className="mt-1 size-4 rounded border-border bg-background text-info focus:ring-info"
|
|
290
|
-
/>
|
|
291
|
-
<span className="min-w-0 flex-1">
|
|
292
|
-
<span className="flex items-center gap-2 text-sm font-medium leading-5 text-foreground">
|
|
293
|
-
{option.icon}
|
|
294
|
-
<span className="break-words">{option.label}</span>
|
|
295
|
-
</span>
|
|
296
|
-
{option.description && (
|
|
297
|
-
<span className="mt-0.5 block break-words text-xs leading-5 text-muted">
|
|
298
|
-
{option.description}
|
|
299
|
-
</span>
|
|
276
|
+
<div id={responseControlsId} hidden={responseControlsHidden}>
|
|
277
|
+
{safeOptions.length > 0 && (
|
|
278
|
+
<div className={cn('flex flex-col gap-1.5 px-3 pb-3 pt-0', optionsClassName)}>
|
|
279
|
+
{multiSelect ? (
|
|
280
|
+
<>
|
|
281
|
+
{safeOptions.map((option) => {
|
|
282
|
+
const selected = selectedOptions.has(option.id);
|
|
283
|
+
return (
|
|
284
|
+
<label
|
|
285
|
+
key={option.id}
|
|
286
|
+
className={cn(
|
|
287
|
+
'flex cursor-pointer items-start gap-2.5 rounded-md border px-3 py-2 text-start transition-colors',
|
|
288
|
+
selected
|
|
289
|
+
? 'border-info/60 bg-info/10'
|
|
290
|
+
: 'border-border bg-background/70 hover:bg-mixer-muted/15',
|
|
291
|
+
isLoading && 'cursor-not-allowed opacity-50',
|
|
292
|
+
buttonClassName,
|
|
300
293
|
)}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
294
|
+
>
|
|
295
|
+
<input
|
|
296
|
+
type="checkbox"
|
|
297
|
+
checked={selected}
|
|
298
|
+
onChange={() => toggleOption(option.id)}
|
|
299
|
+
disabled={isLoading}
|
|
300
|
+
className="mt-1 size-4 rounded border-border bg-background text-info focus:ring-info"
|
|
301
|
+
/>
|
|
302
|
+
<span className="min-w-0 flex-1">
|
|
303
|
+
<span className="flex items-center gap-2 text-sm font-medium leading-5 text-foreground">
|
|
304
|
+
{option.icon}
|
|
305
|
+
<span className="break-words">{option.label}</span>
|
|
306
|
+
</span>
|
|
307
|
+
{option.description && (
|
|
308
|
+
<span className="mt-0.5 block break-words text-xs leading-5 text-muted">
|
|
309
|
+
{option.description}
|
|
310
|
+
</span>
|
|
311
|
+
)}
|
|
312
|
+
</span>
|
|
313
|
+
</label>
|
|
314
|
+
);
|
|
315
|
+
})}
|
|
316
|
+
<div className="pt-1">
|
|
317
|
+
<Button
|
|
318
|
+
size="sm"
|
|
319
|
+
onClick={handleMultiSubmit}
|
|
320
|
+
disabled={isLoading || selectedOptions.size === 0}
|
|
321
|
+
className="inline-flex items-center gap-2"
|
|
322
|
+
>
|
|
323
|
+
<Send className="size-4" />
|
|
324
|
+
{selectedOptions.size > 0
|
|
325
|
+
? t('agent.submitSelectionCount', { count: selectedOptions.size })
|
|
326
|
+
: t('agent.submitSelection')}
|
|
327
|
+
</Button>
|
|
328
|
+
</div>
|
|
329
|
+
</>
|
|
330
|
+
) : (
|
|
331
|
+
safeOptions.map((option) => (
|
|
332
|
+
<button
|
|
333
|
+
type="button"
|
|
334
|
+
key={option.id}
|
|
335
|
+
onClick={() => onSelect?.(option.id)}
|
|
336
|
+
disabled={isLoading}
|
|
337
|
+
className={cn(
|
|
338
|
+
'flex w-full cursor-pointer items-start gap-2.5 rounded-md border border-border bg-background/70 px-3 py-2 text-start transition-colors',
|
|
339
|
+
'hover:bg-mixer-muted/15 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
|
|
340
|
+
isLoading && 'cursor-not-allowed opacity-50',
|
|
341
|
+
buttonClassName,
|
|
342
|
+
)}
|
|
311
343
|
>
|
|
312
|
-
<
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
344
|
+
<span
|
|
345
|
+
className="mt-1 size-4 flex-shrink-0 rounded-full border border-border"
|
|
346
|
+
aria-hidden="true"
|
|
347
|
+
/>
|
|
348
|
+
<span className="min-w-0 flex-1">
|
|
349
|
+
<span className="flex items-center gap-2 text-sm font-medium leading-5 text-foreground">
|
|
350
|
+
{option.icon}
|
|
351
|
+
<span className="break-words">{option.label}</span>
|
|
352
|
+
</span>
|
|
353
|
+
{option.description && (
|
|
354
|
+
<span className="mt-0.5 block break-words text-xs leading-5 text-muted">
|
|
355
|
+
{option.description}
|
|
356
|
+
</span>
|
|
357
|
+
)}
|
|
358
|
+
</span>
|
|
359
|
+
</button>
|
|
360
|
+
))
|
|
361
|
+
)}
|
|
362
|
+
</div>
|
|
363
|
+
)}
|
|
364
|
+
|
|
365
|
+
{allowFreeResponse && (
|
|
366
|
+
<div className={cn('px-3 pb-3 pt-0', inputContainerClassName)}>
|
|
367
|
+
<div className="flex gap-2">
|
|
368
|
+
<input
|
|
369
|
+
ref={inputRef}
|
|
370
|
+
type="text"
|
|
371
|
+
value={inputValue}
|
|
372
|
+
onChange={(e) => setInputValue(e.target.value)}
|
|
373
|
+
onKeyDown={handleKeyDown}
|
|
374
|
+
placeholder={resolvedPlaceholder}
|
|
325
375
|
disabled={isLoading}
|
|
326
376
|
className={cn(
|
|
327
|
-
'
|
|
328
|
-
|
|
329
|
-
isLoading && 'cursor-not-allowed opacity-50',
|
|
330
|
-
buttonClassName,
|
|
377
|
+
'min-w-0 flex-1 rounded-md border border-border bg-background/70 px-3 py-2 text-sm text-foreground focus:border-transparent focus:ring-2 focus:ring-ring',
|
|
378
|
+
inputClassName,
|
|
331
379
|
)}
|
|
380
|
+
/>
|
|
381
|
+
<Button
|
|
382
|
+
size="sm"
|
|
383
|
+
onClick={handleSubmit}
|
|
384
|
+
disabled={isLoading || !inputValue.trim()}
|
|
385
|
+
className={submitButtonClassName}
|
|
332
386
|
>
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
/>
|
|
337
|
-
<span className="min-w-0 flex-1">
|
|
338
|
-
<span className="flex items-center gap-2 text-sm font-medium leading-5 text-foreground">
|
|
339
|
-
{option.icon}
|
|
340
|
-
<span className="break-words">{option.label}</span>
|
|
341
|
-
</span>
|
|
342
|
-
{option.description && (
|
|
343
|
-
<span className="mt-0.5 block break-words text-xs leading-5 text-muted">
|
|
344
|
-
{option.description}
|
|
345
|
-
</span>
|
|
346
|
-
)}
|
|
347
|
-
</span>
|
|
348
|
-
</button>
|
|
349
|
-
))
|
|
350
|
-
)}
|
|
351
|
-
</div>
|
|
352
|
-
)}
|
|
353
|
-
|
|
354
|
-
{allowFreeResponse && (
|
|
355
|
-
<div className={cn('px-3 pb-3 pt-0', inputContainerClassName)}>
|
|
356
|
-
<div className="flex gap-2">
|
|
357
|
-
<input
|
|
358
|
-
ref={inputRef}
|
|
359
|
-
type="text"
|
|
360
|
-
value={inputValue}
|
|
361
|
-
onChange={(e) => setInputValue(e.target.value)}
|
|
362
|
-
onKeyDown={handleKeyDown}
|
|
363
|
-
placeholder={resolvedPlaceholder}
|
|
364
|
-
disabled={isLoading}
|
|
365
|
-
className={cn(
|
|
366
|
-
'min-w-0 flex-1 rounded-md border border-border bg-background/70 px-3 py-2 text-sm text-foreground focus:border-transparent focus:ring-2 focus:ring-ring',
|
|
367
|
-
inputClassName,
|
|
368
|
-
)}
|
|
369
|
-
/>
|
|
370
|
-
<Button
|
|
371
|
-
size="sm"
|
|
372
|
-
onClick={handleSubmit}
|
|
373
|
-
disabled={isLoading || !inputValue.trim()}
|
|
374
|
-
className={submitButtonClassName}
|
|
375
|
-
>
|
|
376
|
-
{isLoading ? '...' : resolvedSubmitLabel}
|
|
377
|
-
</Button>
|
|
387
|
+
{isLoading ? '...' : resolvedSubmitLabel}
|
|
388
|
+
</Button>
|
|
389
|
+
</div>
|
|
378
390
|
</div>
|
|
379
|
-
|
|
380
|
-
|
|
391
|
+
)}
|
|
392
|
+
</div>
|
|
381
393
|
</div>
|
|
382
394
|
</div>
|
|
383
395
|
);
|
|
@@ -408,124 +420,129 @@ export function AskUserWidget({
|
|
|
408
420
|
)}
|
|
409
421
|
</div>
|
|
410
422
|
</div>
|
|
423
|
+
{headerAction}
|
|
411
424
|
</div>
|
|
412
425
|
</div>
|
|
413
426
|
|
|
414
|
-
{
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
{
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
427
|
+
<div id={responseControlsId} hidden={responseControlsHidden}>
|
|
428
|
+
{/* Options */}
|
|
429
|
+
{safeOptions.length > 0 && (
|
|
430
|
+
<div className={`px-4 pb-3 pt-1 ${optionsClassName || ''}`}>
|
|
431
|
+
{multiSelect ? (
|
|
432
|
+
/* Multi-select mode with checkboxes */
|
|
433
|
+
<div className="space-y-2">
|
|
434
|
+
{safeOptions.map((option) => (
|
|
435
|
+
<label
|
|
436
|
+
key={option.id}
|
|
437
|
+
className={`flex items-start gap-3 px-4 py-3 rounded-lg cursor-pointer transition-colors
|
|
424
438
|
${
|
|
425
439
|
selectedOptions.has(option.id)
|
|
426
440
|
? 'bg-info border border-info'
|
|
427
441
|
: 'bg-card text-card-foreground border border-border hover:bg-muted'
|
|
428
442
|
}
|
|
429
443
|
${isLoading ? 'opacity-50 cursor-not-allowed' : ''}`}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
</div>
|
|
443
|
-
{option.description && (
|
|
444
|
-
<div className="mt-1 text-sm text-muted break-words whitespace-pre-wrap">
|
|
445
|
-
{option.description}
|
|
444
|
+
>
|
|
445
|
+
<input
|
|
446
|
+
type="checkbox"
|
|
447
|
+
checked={selectedOptions.has(option.id)}
|
|
448
|
+
onChange={() => toggleOption(option.id)}
|
|
449
|
+
disabled={isLoading}
|
|
450
|
+
className="mt-0.5 w-4 h-4 rounded border-border text-info focus:ring-info bg-background"
|
|
451
|
+
/>
|
|
452
|
+
<div className="flex-1 overflow-hidden">
|
|
453
|
+
<div className="flex items-center gap-2 text-sm font-medium text-foreground break-words">
|
|
454
|
+
{option.icon}
|
|
455
|
+
<span className="break-words">{option.label}</span>
|
|
446
456
|
</div>
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
457
|
+
{option.description && (
|
|
458
|
+
<div className="mt-1 text-sm text-muted break-words whitespace-pre-wrap">
|
|
459
|
+
{option.description}
|
|
460
|
+
</div>
|
|
461
|
+
)}
|
|
462
|
+
</div>
|
|
463
|
+
</label>
|
|
464
|
+
))}
|
|
465
|
+
<div className="pt-2">
|
|
466
|
+
<Button
|
|
467
|
+
size="sm"
|
|
468
|
+
onClick={handleMultiSubmit}
|
|
469
|
+
disabled={isLoading || selectedOptions.size === 0}
|
|
470
|
+
className="flex items-center gap-2"
|
|
471
|
+
>
|
|
472
|
+
<Send className="size-4" />
|
|
473
|
+
{selectedOptions.size > 0
|
|
474
|
+
? t('agent.submitSelectionCount', { count: selectedOptions.size })
|
|
475
|
+
: t('agent.submitSelection')}
|
|
476
|
+
</Button>
|
|
477
|
+
</div>
|
|
463
478
|
</div>
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
className={`w-full h-auto whitespace-normal text-start px-4 py-3 rounded-lg border border-border
|
|
479
|
+
) : (
|
|
480
|
+
/* Single-select mode - always use full-width card layout for clarity */
|
|
481
|
+
<div className="flex flex-col gap-2 w-full">
|
|
482
|
+
{safeOptions.map((option) => (
|
|
483
|
+
<Button
|
|
484
|
+
variant="unstyled"
|
|
485
|
+
key={option.id}
|
|
486
|
+
onClick={() => onSelect?.(option.id)}
|
|
487
|
+
disabled={isLoading}
|
|
488
|
+
className={`w-full h-auto whitespace-normal text-start px-4 py-3 rounded-lg border border-border
|
|
475
489
|
bg-card text-card-foreground
|
|
476
490
|
hover:bg-muted hover:border-border
|
|
477
491
|
focus:outline-none focus:ring-2 focus:ring-info focus:ring-offset-1
|
|
478
492
|
transition-colors
|
|
479
493
|
${isLoading ? 'opacity-50 cursor-not-allowed' : 'cursor-pointer'}
|
|
480
494
|
${buttonClassName || ''}`}
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
<div className="font-medium text-sm text-foreground break-words text-center">
|
|
488
|
-
{option.label}
|
|
489
|
-
</div>
|
|
490
|
-
{option.description && (
|
|
491
|
-
<div className="mt-1 text-sm text-muted break-words whitespace-pre-wrap">
|
|
492
|
-
{option.description}
|
|
493
|
-
</div>
|
|
495
|
+
>
|
|
496
|
+
<div className="flex items-start gap-3">
|
|
497
|
+
{option.icon && (
|
|
498
|
+
<span className="flex-shrink-0 mt-0.5 text-muted">
|
|
499
|
+
{option.icon}
|
|
500
|
+
</span>
|
|
494
501
|
)}
|
|
502
|
+
<div className="flex-1 overflow-hidden">
|
|
503
|
+
<div className="font-medium text-sm text-foreground break-words text-center">
|
|
504
|
+
{option.label}
|
|
505
|
+
</div>
|
|
506
|
+
{option.description && (
|
|
507
|
+
<div className="mt-1 text-sm text-muted break-words whitespace-pre-wrap">
|
|
508
|
+
{option.description}
|
|
509
|
+
</div>
|
|
510
|
+
)}
|
|
511
|
+
</div>
|
|
495
512
|
</div>
|
|
496
|
-
</
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
)}
|
|
513
|
+
</Button>
|
|
514
|
+
))}
|
|
515
|
+
</div>
|
|
516
|
+
)}
|
|
517
|
+
</div>
|
|
518
|
+
)}
|
|
503
519
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
520
|
+
{/* Free-form input */}
|
|
521
|
+
{allowFreeResponse && (
|
|
522
|
+
<div className={`px-4 pb-3 pt-1 ${inputContainerClassName || ''}`}>
|
|
523
|
+
<div className="flex gap-2">
|
|
524
|
+
<input
|
|
525
|
+
ref={inputRef}
|
|
526
|
+
type="text"
|
|
527
|
+
value={inputValue}
|
|
528
|
+
onChange={(e) => setInputValue(e.target.value)}
|
|
529
|
+
onKeyDown={handleKeyDown}
|
|
530
|
+
placeholder={resolvedPlaceholder}
|
|
531
|
+
disabled={isLoading}
|
|
532
|
+
className={`flex-1 px-3 py-2 text-sm border border-border rounded-md bg-card text-card-foreground focus:ring-2 focus:ring-info focus:border-transparent ${inputClassName || ''}`}
|
|
533
|
+
/>
|
|
534
|
+
<Button
|
|
535
|
+
size="sm"
|
|
536
|
+
onClick={handleSubmit}
|
|
537
|
+
disabled={isLoading || !inputValue.trim()}
|
|
538
|
+
className={submitButtonClassName}
|
|
539
|
+
>
|
|
540
|
+
{isLoading ? '...' : resolvedSubmitLabel}
|
|
541
|
+
</Button>
|
|
542
|
+
</div>
|
|
526
543
|
</div>
|
|
527
|
-
|
|
528
|
-
|
|
544
|
+
)}
|
|
545
|
+
</div>
|
|
529
546
|
</div>
|
|
530
547
|
</div>
|
|
531
548
|
);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ContentNature, type ContentObject, ImageRenditionFormat } from '@vertesia/common';
|
|
2
|
-
import { Spinner } from '@vertesia/ui/core';
|
|
2
|
+
import { Button, Spinner } from '@vertesia/ui/core';
|
|
3
|
+
import { useUITranslation } from '@vertesia/ui/i18n';
|
|
3
4
|
import { useUserSession } from '@vertesia/ui/session';
|
|
4
5
|
import { useEffect, useState } from 'react';
|
|
5
6
|
import { WEB_SUPPORTED_IMAGE_FORMATS } from './formats.js';
|
|
@@ -19,12 +20,27 @@ interface ImagePanelProps {
|
|
|
19
20
|
* Renders an image from a direct URL, a storage source path, or a Vertesia ContentObject.
|
|
20
21
|
* Resolution priority: `url` > `source` > `object`.
|
|
21
22
|
*/
|
|
22
|
-
export function ImagePanel(
|
|
23
|
+
export function ImagePanel(props: ImagePanelProps) {
|
|
24
|
+
const [attempt, setAttempt] = useState(0);
|
|
25
|
+
const identity =
|
|
26
|
+
props.url ??
|
|
27
|
+
props.source ??
|
|
28
|
+
`${props.object?.id}:${props.object?.content?.source}:${props.object?.content?.etag}`;
|
|
29
|
+
return (
|
|
30
|
+
<ImagePanelContent key={`${identity}:${attempt}`} {...props} onRetry={() => setAttempt((value) => value + 1)} />
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function ImagePanelContent({ url, source, object, className, onRetry }: ImagePanelProps & { onRetry: () => void }) {
|
|
35
|
+
const { t } = useUITranslation();
|
|
36
|
+
const [failed, setFailed] = useState(false);
|
|
23
37
|
const { client } = useUserSession();
|
|
24
38
|
const [imageUrl, setImageUrl] = useState<string | undefined>(url);
|
|
25
39
|
const [isLoading, setIsLoading] = useState<boolean>(!url && (!!source || !!object));
|
|
26
40
|
|
|
27
41
|
useEffect(() => {
|
|
42
|
+
let active = true;
|
|
43
|
+
setFailed(false);
|
|
28
44
|
if (url) {
|
|
29
45
|
setImageUrl(url);
|
|
30
46
|
setIsLoading(false);
|
|
@@ -37,17 +53,17 @@ export function ImagePanel({ url, source, object, className }: ImagePanelProps)
|
|
|
37
53
|
try {
|
|
38
54
|
if (source) {
|
|
39
55
|
const downloadUrl = await client.files.getDownloadUrl(source);
|
|
40
|
-
setImageUrl(downloadUrl.url);
|
|
56
|
+
if (active) setImageUrl(downloadUrl.url);
|
|
41
57
|
return;
|
|
42
58
|
}
|
|
43
59
|
|
|
44
60
|
if (!object) return;
|
|
45
61
|
|
|
46
|
-
const
|
|
62
|
+
const mime = object.content?.type?.split(';', 1)[0].trim().toLowerCase();
|
|
63
|
+
const isImage = object.metadata?.type === ContentNature.Image || mime?.startsWith('image/');
|
|
47
64
|
if (!isImage) return;
|
|
48
65
|
|
|
49
|
-
const isOriginalWebSupported =
|
|
50
|
-
object.content?.type && WEB_SUPPORTED_IMAGE_FORMATS.includes(object.content.type);
|
|
66
|
+
const isOriginalWebSupported = mime && WEB_SUPPORTED_IMAGE_FORMATS.includes(mime);
|
|
51
67
|
|
|
52
68
|
try {
|
|
53
69
|
const rendition = await client.objects.getRendition(object.id, {
|
|
@@ -56,7 +72,7 @@ export function ImagePanel({ url, source, object, className }: ImagePanelProps)
|
|
|
56
72
|
sign_url: true,
|
|
57
73
|
});
|
|
58
74
|
if (rendition.status === 'found' && rendition.renditions?.length) {
|
|
59
|
-
setImageUrl(rendition.renditions[0]);
|
|
75
|
+
if (active) setImageUrl(rendition.renditions[0]);
|
|
60
76
|
return;
|
|
61
77
|
}
|
|
62
78
|
} catch {
|
|
@@ -65,10 +81,14 @@ export function ImagePanel({ url, source, object, className }: ImagePanelProps)
|
|
|
65
81
|
|
|
66
82
|
if (isOriginalWebSupported && object.content?.source) {
|
|
67
83
|
const downloadUrl = await client.files.getDownloadUrl(object.content.source);
|
|
68
|
-
setImageUrl(downloadUrl.url);
|
|
84
|
+
if (active) setImageUrl(downloadUrl.url);
|
|
85
|
+
} else if (active) {
|
|
86
|
+
setFailed(true);
|
|
69
87
|
}
|
|
88
|
+
} catch {
|
|
89
|
+
if (active) setFailed(true);
|
|
70
90
|
} finally {
|
|
71
|
-
setIsLoading(false);
|
|
91
|
+
if (active) setIsLoading(false);
|
|
72
92
|
}
|
|
73
93
|
};
|
|
74
94
|
|
|
@@ -78,6 +98,9 @@ export function ImagePanel({ url, source, object, className }: ImagePanelProps)
|
|
|
78
98
|
} else {
|
|
79
99
|
setIsLoading(false);
|
|
80
100
|
}
|
|
101
|
+
return () => {
|
|
102
|
+
active = false;
|
|
103
|
+
};
|
|
81
104
|
}, [url, source, object, client]);
|
|
82
105
|
|
|
83
106
|
if (isLoading) {
|
|
@@ -88,9 +111,27 @@ export function ImagePanel({ url, source, object, className }: ImagePanelProps)
|
|
|
88
111
|
);
|
|
89
112
|
}
|
|
90
113
|
|
|
114
|
+
if (failed) {
|
|
115
|
+
return (
|
|
116
|
+
<div role="alert" className={`flex flex-col items-center gap-2 p-4 ${className ?? ''}`.trim()}>
|
|
117
|
+
<p>{t('store.failedToLoadDocument')}</p>
|
|
118
|
+
<Button variant="outline" onClick={onRetry}>
|
|
119
|
+
{t('agent.retry')}
|
|
120
|
+
</Button>
|
|
121
|
+
</div>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
91
125
|
if (!imageUrl) {
|
|
92
126
|
return null;
|
|
93
127
|
}
|
|
94
128
|
|
|
95
|
-
return
|
|
129
|
+
return (
|
|
130
|
+
<img
|
|
131
|
+
src={imageUrl}
|
|
132
|
+
alt={object?.name ?? t('intakePolicy.option.image')}
|
|
133
|
+
onError={() => setFailed(true)}
|
|
134
|
+
className={`w-full object-contain ${className ?? ''}`.trim()}
|
|
135
|
+
/>
|
|
136
|
+
);
|
|
96
137
|
}
|
|
@@ -547,6 +547,7 @@ export function IntakePolicyEditor({
|
|
|
547
547
|
<div className="flex h-full min-h-0 w-full flex-col gap-2">
|
|
548
548
|
<IntakeSummary policy={summaryPolicy} />
|
|
549
549
|
<Tabs
|
|
550
|
+
orientation="vertical"
|
|
550
551
|
tabs={leadingTabs ? [...leadingTabs, ...policyTabs] : policyTabs}
|
|
551
552
|
current={activeTab}
|
|
552
553
|
onTabChange={onTabChange}
|
|
@@ -554,7 +555,7 @@ export function IntakePolicyEditor({
|
|
|
554
555
|
fullHeight
|
|
555
556
|
className="px-0 min-h-0 flex-1 flex-row!"
|
|
556
557
|
>
|
|
557
|
-
<TabsBar className="py-2 w-48 shrink-0"
|
|
558
|
+
<TabsBar className="py-2 w-48 shrink-0" />
|
|
558
559
|
<TabsPanel className="min-h-0 min-w-0 flex-1 overflow-auto pb-0!" />
|
|
559
560
|
</Tabs>
|
|
560
561
|
</div>
|