@seed-ship/mcp-ui-solid 2.10.2 → 2.11.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/README.md +11 -11
- package/dist/components/ScratchpadPanel.cjs +350 -247
- package/dist/components/ScratchpadPanel.cjs.map +1 -1
- package/dist/components/ScratchpadPanel.d.ts +4 -0
- package/dist/components/ScratchpadPanel.d.ts.map +1 -1
- package/dist/components/ScratchpadPanel.js +350 -247
- package/dist/components/ScratchpadPanel.js.map +1 -1
- package/dist/types/chat-bus.d.ts +11 -1
- package/dist/types/chat-bus.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/ScratchpadPanel.tsx +83 -32
- package/src/types/chat-bus.ts +7 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -15,6 +15,10 @@ export interface ScratchpadPanelProps {
|
|
|
15
15
|
onFilterChange?: (filters: Record<string, string | string[]>) => void
|
|
16
16
|
onAction?: (action: string, data?: unknown) => void
|
|
17
17
|
onSectionEdit?: (sectionId: string, content: unknown) => void
|
|
18
|
+
/** Dedicated callback for form submissions (cleaner than onAction) */
|
|
19
|
+
onSubmit?: (sectionId: string, values: Record<string, unknown>) => void
|
|
20
|
+
/** Called when user clicks retry on error state */
|
|
21
|
+
onRetry?: () => void
|
|
18
22
|
onClose?: () => void
|
|
19
23
|
closable?: boolean
|
|
20
24
|
autoCloseDelay?: number
|
|
@@ -28,6 +32,7 @@ const STATUS_BADGES: Record<ScratchpadState['status'], { label: string; class: s
|
|
|
28
32
|
waiting_human: { label: 'Your turn', class: 'bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400 animate-pulse' },
|
|
29
33
|
processing: { label: 'Processing...', class: 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400' },
|
|
30
34
|
complete: { label: 'Complete', class: 'bg-green-100 text-green-600 dark:bg-green-900/30 dark:text-green-400' },
|
|
35
|
+
error: { label: 'Error', class: 'bg-red-100 text-red-600 dark:bg-red-900/30 dark:text-red-400' },
|
|
31
36
|
}
|
|
32
37
|
|
|
33
38
|
export const ScratchpadPanel: Component<ScratchpadPanelProps> = (props) => {
|
|
@@ -59,8 +64,8 @@ export const ScratchpadPanel: Component<ScratchpadPanelProps> = (props) => {
|
|
|
59
64
|
previewTimer = setTimeout(async () => {
|
|
60
65
|
try {
|
|
61
66
|
const res = await fetch(endpoint, {
|
|
62
|
-
method: 'POST',
|
|
63
|
-
headers: { 'Content-Type': 'application/json' },
|
|
67
|
+
method: props.state.previewMethod || 'POST',
|
|
68
|
+
headers: { 'Content-Type': 'application/json', ...props.state.previewHeaders },
|
|
64
69
|
credentials: 'include',
|
|
65
70
|
body: JSON.stringify({ filters }),
|
|
66
71
|
})
|
|
@@ -141,6 +146,7 @@ export const ScratchpadPanel: Component<ScratchpadPanelProps> = (props) => {
|
|
|
141
146
|
onFilterChange={props.onFilterChange}
|
|
142
147
|
onAction={props.onAction}
|
|
143
148
|
onSectionEdit={props.onSectionEdit}
|
|
149
|
+
onSubmit={props.onSubmit}
|
|
144
150
|
/>
|
|
145
151
|
)}
|
|
146
152
|
</For>
|
|
@@ -193,6 +199,35 @@ export const ScratchpadPanel: Component<ScratchpadPanelProps> = (props) => {
|
|
|
193
199
|
</div>
|
|
194
200
|
</Show>
|
|
195
201
|
|
|
202
|
+
{/* Error state with retry */}
|
|
203
|
+
<Show when={props.state.status === 'error' && props.state.error}>
|
|
204
|
+
<div class="px-4 py-3 border-t border-red-100 dark:border-red-900/30 bg-red-50 dark:bg-red-900/10">
|
|
205
|
+
<div class="flex items-start gap-2 text-sm text-red-700 dark:text-red-400">
|
|
206
|
+
<span class="flex-shrink-0 mt-0.5">⚠️</span>
|
|
207
|
+
<div class="flex-1">
|
|
208
|
+
<p class="font-medium">{props.state.error!.message}</p>
|
|
209
|
+
<Show when={props.state.error!.code}>
|
|
210
|
+
<p class="text-xs text-red-500 dark:text-red-500 mt-0.5">Code: {props.state.error!.code}</p>
|
|
211
|
+
</Show>
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
<div class="flex gap-2 mt-2">
|
|
215
|
+
<Show when={props.state.error!.retryable !== false}>
|
|
216
|
+
<button type="button" onClick={() => props.onRetry?.()}
|
|
217
|
+
class="px-3 py-1.5 text-sm font-medium rounded-lg bg-red-600 text-white hover:bg-red-700 transition-colors flex items-center gap-1">
|
|
218
|
+
🔄 Retry
|
|
219
|
+
</button>
|
|
220
|
+
</Show>
|
|
221
|
+
<Show when={props.onClose}>
|
|
222
|
+
<button type="button" onClick={() => props.onClose?.()}
|
|
223
|
+
class="px-3 py-1.5 text-sm font-medium rounded-lg border border-gray-200 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
|
|
224
|
+
Close
|
|
225
|
+
</button>
|
|
226
|
+
</Show>
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</Show>
|
|
230
|
+
|
|
196
231
|
{/* Search button when waiting_human */}
|
|
197
232
|
<Show when={props.state.status === 'waiting_human' && hasFilters()}>
|
|
198
233
|
<div class="px-4 py-3 border-t border-gray-100 dark:border-gray-700">
|
|
@@ -224,6 +259,7 @@ const SectionRenderer: Component<{
|
|
|
224
259
|
onFilterChange?: (filters: Record<string, string | string[]>) => void
|
|
225
260
|
onAction?: (action: string, data?: unknown) => void
|
|
226
261
|
onSectionEdit?: (sectionId: string, content: unknown) => void
|
|
262
|
+
onSubmit?: (sectionId: string, values: Record<string, unknown>) => void
|
|
227
263
|
}> = (props) => {
|
|
228
264
|
return (
|
|
229
265
|
<div class="px-4 py-3">
|
|
@@ -234,7 +270,7 @@ const SectionRenderer: Component<{
|
|
|
234
270
|
<Match when={props.section.type === 'message'}><p class="text-sm text-gray-700 dark:text-gray-300">{String(props.section.content)}</p></Match>
|
|
235
271
|
<Match when={props.section.type === 'action'}><ActionSection content={props.section.content} onAction={props.onAction} /></Match>
|
|
236
272
|
<Match when={props.section.type === 'steps'}><EnrichedStepsSection content={props.section.content} onAction={props.onAction} onFilterChange={props.onFilterChange} /></Match>
|
|
237
|
-
<Match when={props.section.type === 'form'}><EmbeddedFormSection content={props.section.content} sectionId={props.section.id} onAction={props.onAction} /></Match>
|
|
273
|
+
<Match when={props.section.type === 'form'}><EmbeddedFormSection content={props.section.content} sectionId={props.section.id} onAction={props.onAction} onSubmit={props.onSubmit} /></Match>
|
|
238
274
|
<Match when={props.section.type === 'understanding'}><UnderstandingSection content={props.section.content} /></Match>
|
|
239
275
|
<Match when={props.section.type === 'feedback'}><FeedbackSection content={props.section.content} onAction={props.onAction} /></Match>
|
|
240
276
|
<Match when={props.section.type === 'prompt'}><PromptSection content={props.section.content} onAction={props.onAction} /></Match>
|
|
@@ -362,6 +398,7 @@ const EmbeddedFormSection: Component<{
|
|
|
362
398
|
content: unknown
|
|
363
399
|
sectionId: string
|
|
364
400
|
onAction?: (action: string, data?: unknown) => void
|
|
401
|
+
onSubmit?: (sectionId: string, values: Record<string, unknown>) => void
|
|
365
402
|
}> = (props) => {
|
|
366
403
|
const [formData, setFormData] = createSignal<Record<string, any>>({})
|
|
367
404
|
const [dynamicOptions, setDynamicOptions] = createSignal<Record<string, Array<{ label: string; value: string }>>>({})
|
|
@@ -403,7 +440,12 @@ const EmbeddedFormSection: Component<{
|
|
|
403
440
|
|
|
404
441
|
const handleSubmit = (e: Event) => {
|
|
405
442
|
e.preventDefault()
|
|
406
|
-
|
|
443
|
+
// Use dedicated onSubmit if provided, fallback to onAction
|
|
444
|
+
if (props.onSubmit) {
|
|
445
|
+
props.onSubmit(props.sectionId, formData())
|
|
446
|
+
} else {
|
|
447
|
+
props.onAction?.('submit_form', { sectionId: props.sectionId, values: formData() })
|
|
448
|
+
}
|
|
407
449
|
}
|
|
408
450
|
|
|
409
451
|
return (
|
|
@@ -564,47 +606,56 @@ const FeedbackSection: Component<{
|
|
|
564
606
|
onAction?: (action: string, data?: unknown) => void
|
|
565
607
|
}> = (props) => {
|
|
566
608
|
const [comment, setComment] = createSignal('')
|
|
609
|
+
const [showComment, setShowComment] = createSignal(false)
|
|
567
610
|
const data = () => {
|
|
568
611
|
const c = props.content as any
|
|
612
|
+
// Support both formats: options array (universal) and approve/reject (simple)
|
|
613
|
+
const options = c?.options || [
|
|
614
|
+
{ value: c?.approve?.value || 'approve', label: c?.approve?.label || 'Yes', icon: '👍', variant: 'primary' },
|
|
615
|
+
{ value: c?.reject?.value || 'reject', label: c?.reject?.label || 'No', icon: '👎' },
|
|
616
|
+
]
|
|
569
617
|
return {
|
|
570
618
|
question: c?.question || '',
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
commentPlaceholder: c?.commentPlaceholder || 'Add a comment...',
|
|
619
|
+
options: options as Array<{ value: string; label: string; icon?: string; variant?: string; needsComment?: boolean }>,
|
|
620
|
+
allowFreeText: c?.allowFreeText ?? c?.allowComment ?? false,
|
|
621
|
+
placeholder: c?.placeholder || c?.commentPlaceholder || 'Add a comment...',
|
|
575
622
|
}
|
|
576
623
|
}
|
|
577
624
|
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
})
|
|
625
|
+
const handleOption = (option: any) => {
|
|
626
|
+
if (option.needsComment) {
|
|
627
|
+
setShowComment(true)
|
|
628
|
+
return
|
|
629
|
+
}
|
|
630
|
+
props.onAction?.('feedback', { option: option.value, comment: comment() })
|
|
585
631
|
}
|
|
586
632
|
|
|
587
633
|
return (
|
|
588
634
|
<div class="space-y-3">
|
|
589
635
|
<p class="text-sm text-gray-700 dark:text-gray-300">{data().question}</p>
|
|
590
|
-
<div class="flex gap-2">
|
|
591
|
-
<
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
636
|
+
<div class="flex flex-wrap gap-2">
|
|
637
|
+
<For each={data().options}>
|
|
638
|
+
{(option) => (
|
|
639
|
+
<button type="button" onClick={() => handleOption(option)}
|
|
640
|
+
class={`px-3 py-1.5 text-sm font-medium rounded-lg transition-colors flex items-center gap-1 ${
|
|
641
|
+
option.variant === 'primary' ? 'bg-blue-600 text-white hover:bg-blue-700'
|
|
642
|
+
: option.variant === 'danger' ? 'bg-red-600 text-white hover:bg-red-700'
|
|
643
|
+
: 'border border-gray-200 dark:border-gray-600 text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700'
|
|
644
|
+
}`}>
|
|
645
|
+
<Show when={option.icon}><span>{option.icon}</span></Show>
|
|
646
|
+
{option.label}
|
|
647
|
+
</button>
|
|
648
|
+
)}
|
|
649
|
+
</For>
|
|
599
650
|
</div>
|
|
600
|
-
<Show when={data().
|
|
601
|
-
<
|
|
602
|
-
type="text"
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
651
|
+
<Show when={data().allowFreeText || showComment()}>
|
|
652
|
+
<div class="flex gap-1">
|
|
653
|
+
<input type="text" value={comment()} onInput={(e) => setComment(e.currentTarget.value)}
|
|
654
|
+
placeholder={data().placeholder} autofocus={showComment()}
|
|
655
|
+
class="flex-1 px-3 py-2 text-sm border border-gray-200 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-700 text-gray-900 dark:text-white focus:border-blue-400 outline-none" />
|
|
656
|
+
<button type="button" onClick={() => props.onAction?.('feedback', { option: 'comment', comment: comment() })}
|
|
657
|
+
class="px-3 py-2 text-sm bg-blue-600 text-white rounded-lg hover:bg-blue-700">Send</button>
|
|
658
|
+
</div>
|
|
608
659
|
</Show>
|
|
609
660
|
</div>
|
|
610
661
|
)
|
package/src/types/chat-bus.ts
CHANGED
|
@@ -339,11 +339,17 @@ export interface ScratchpadState {
|
|
|
339
339
|
preview?: { count: number; rows?: Record<string, unknown>[]; summary: string }
|
|
340
340
|
/** Agent messages (explanations, questions) */
|
|
341
341
|
agentMessages: Array<{ text: string; type: 'info' | 'question' | 'warning' }>
|
|
342
|
-
status: 'loading' | 'ready' | 'waiting_human' | 'processing' | 'complete'
|
|
342
|
+
status: 'loading' | 'ready' | 'waiting_human' | 'processing' | 'complete' | 'error'
|
|
343
|
+
/** Error details when status is 'error' */
|
|
344
|
+
error?: { message: string; code?: string; retryable?: boolean }
|
|
343
345
|
/** Endpoint for auto-refresh preview when filters change */
|
|
344
346
|
previewEndpoint?: string
|
|
345
347
|
/** Debounce delay for preview refresh (ms, default 500) */
|
|
346
348
|
previewDebounce?: number
|
|
349
|
+
/** HTTP method for preview (default POST) */
|
|
350
|
+
previewMethod?: 'GET' | 'POST'
|
|
351
|
+
/** Extra headers for preview fetch */
|
|
352
|
+
previewHeaders?: Record<string, string>
|
|
347
353
|
/** Current turn number (multi-tour) */
|
|
348
354
|
turn?: number
|
|
349
355
|
/** Total expected turns */
|