@agentuity/workbench 0.0.93 → 0.0.95

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 (45) hide show
  1. package/dist/components/App.d.ts.map +1 -1
  2. package/dist/components/App.js +3 -1
  3. package/dist/components/App.js.map +1 -1
  4. package/dist/components/internal/Chat.d.ts +3 -1
  5. package/dist/components/internal/Chat.d.ts.map +1 -1
  6. package/dist/components/internal/Chat.js +5 -5
  7. package/dist/components/internal/Chat.js.map +1 -1
  8. package/dist/components/internal/Header.d.ts +4 -0
  9. package/dist/components/internal/Header.d.ts.map +1 -1
  10. package/dist/components/internal/Header.js +1 -1
  11. package/dist/components/internal/Header.js.map +1 -1
  12. package/dist/components/internal/InputSection.d.ts.map +1 -1
  13. package/dist/components/internal/InputSection.js +10 -10
  14. package/dist/components/internal/InputSection.js.map +1 -1
  15. package/dist/components/internal/MonacoJsonEditor.d.ts.map +1 -1
  16. package/dist/components/internal/MonacoJsonEditor.js +10 -1
  17. package/dist/components/internal/MonacoJsonEditor.js.map +1 -1
  18. package/dist/components/internal/WorkbenchProvider.d.ts +6 -2
  19. package/dist/components/internal/WorkbenchProvider.d.ts.map +1 -1
  20. package/dist/components/internal/WorkbenchProvider.js +117 -46
  21. package/dist/components/internal/WorkbenchProvider.js.map +1 -1
  22. package/dist/hooks/useAgentSchemas.d.ts.map +1 -1
  23. package/dist/hooks/useAgentSchemas.js +17 -4
  24. package/dist/hooks/useAgentSchemas.js.map +1 -1
  25. package/dist/hooks/useWorkbenchWebsocket.d.ts +1 -0
  26. package/dist/hooks/useWorkbenchWebsocket.d.ts.map +1 -1
  27. package/dist/hooks/useWorkbenchWebsocket.js +1 -1
  28. package/dist/hooks/useWorkbenchWebsocket.js.map +1 -1
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +1 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/types/config.d.ts +3 -0
  34. package/dist/types/config.d.ts.map +1 -1
  35. package/package.json +3 -4
  36. package/src/components/App.tsx +9 -4
  37. package/src/components/internal/Chat.tsx +100 -94
  38. package/src/components/internal/Header.tsx +1 -1
  39. package/src/components/internal/InputSection.tsx +107 -69
  40. package/src/components/internal/MonacoJsonEditor.tsx +8 -1
  41. package/src/components/internal/WorkbenchProvider.tsx +132 -50
  42. package/src/hooks/useAgentSchemas.ts +16 -4
  43. package/src/hooks/useWorkbenchWebsocket.ts +2 -1
  44. package/src/index.ts +2 -1
  45. package/src/types/config.ts +3 -0
@@ -19,13 +19,14 @@ export interface ChatProps {
19
19
  className?: string;
20
20
  schemaOpen: boolean;
21
21
  onSchemaToggle: () => void;
22
+ emptyState?: React.ReactNode;
22
23
  }
23
24
 
24
25
  /**
25
26
  * Chat component - conversation and input area (everything except header)
26
27
  * Must be used within WorkbenchProvider
27
28
  */
28
- export function Chat({ className: _className, schemaOpen, onSchemaToggle }: ChatProps) {
29
+ export function Chat({ className: _className, schemaOpen, onSchemaToggle, emptyState }: ChatProps) {
29
30
  const logger = useLogger('Chat');
30
31
  const {
31
32
  agents,
@@ -35,6 +36,7 @@ export function Chat({ className: _className, schemaOpen, onSchemaToggle }: Chat
35
36
  setSelectedAgent,
36
37
  isLoading,
37
38
  submitMessage,
39
+ connectionStatus,
38
40
  } = useWorkbench();
39
41
 
40
42
  const [value, setValue] = useState('');
@@ -63,116 +65,120 @@ export function Chat({ className: _className, schemaOpen, onSchemaToggle }: Chat
63
65
  return (
64
66
  <div className="flex flex-col h-full overflow-hidden">
65
67
  <Conversation className="flex-1 overflow-y-auto">
66
- <ConversationContent className="pb-0">
67
- {messages.map((message) => {
68
- const { role, parts, id } = message;
69
- const isStreaming = parts.some(
70
- (part) => part.type === 'text' && part.state === 'streaming'
71
- );
72
- const tokens =
73
- 'tokens' in message ? (message as { tokens?: string }).tokens : undefined;
74
- const duration =
75
- 'duration' in message
76
- ? (message as { duration?: string }).duration
77
- : undefined;
68
+ {connectionStatus === 'disconnected' && emptyState ? (
69
+ <div className="flex flex-col h-full">{emptyState}</div>
70
+ ) : (
71
+ <ConversationContent className="pb-0">
72
+ {messages.map((message) => {
73
+ const { role, parts, id } = message;
74
+ const isStreaming = parts.some(
75
+ (part) => part.type === 'text' && part.state === 'streaming'
76
+ );
77
+ const tokens =
78
+ 'tokens' in message ? (message as { tokens?: string }).tokens : undefined;
79
+ const duration =
80
+ 'duration' in message
81
+ ? (message as { duration?: string }).duration
82
+ : undefined;
78
83
 
79
- return (
80
- <div key={id} className="mb-2">
81
- {role === 'assistant' && (
82
- <div
83
- className={cn(
84
- 'w-fit flex items-center mb-2 text-muted-foreground text-sm transition-colors',
85
- !isStreaming && 'hover:text-foreground cursor-pointer'
86
- )}
87
- >
88
- <Loader
84
+ return (
85
+ <div key={id} className="mb-2">
86
+ {role === 'assistant' && (
87
+ <div
89
88
  className={cn(
90
- 'size-4 transition-all',
91
- isStreaming || isLoading ? 'animate-spin mr-2' : 'w-0 mr-2.5'
89
+ 'w-fit flex items-center mb-2 text-muted-foreground text-sm transition-colors',
90
+ !isStreaming && 'hover:text-foreground cursor-pointer'
92
91
  )}
93
- />
94
-
95
- {isStreaming || isLoading ? (
96
- <Shimmer duration={1}>Running...</Shimmer>
97
- ) : (
98
- <>
99
- {duration && (
100
- <>
101
- Ran for
102
- <span className="mx-1">{duration}</span>
103
- </>
92
+ >
93
+ <Loader
94
+ className={cn(
95
+ 'size-4 transition-all',
96
+ isStreaming ? 'animate-spin mr-2' : 'w-0 mr-2.5'
104
97
  )}
105
- {duration && tokens && ` and consumed ${tokens} tokens`}
106
- {(duration || tokens) && <ChevronRight className="size-4" />}
107
- </>
108
- )}
109
- </div>
110
- )}
98
+ />
111
99
 
112
- {(role === 'user' || !(isStreaming || isLoading)) && (
113
- <>
114
- <Message
115
- key={id}
116
- from={role as 'user' | 'system' | 'assistant'}
117
- className="p-0"
118
- >
119
- <MessageContent>
120
- {parts.map((part, index) => {
121
- switch (part.type) {
122
- case 'text':
123
- return (
124
- <div key={`${id}-${part.text}-${index}`}>
125
- {part.text || ''}
126
- </div>
127
- );
128
- }
129
- })}
130
- </MessageContent>
131
- </Message>
100
+ {isStreaming ? (
101
+ <Shimmer duration={1}>Running...</Shimmer>
102
+ ) : (
103
+ <>
104
+ {duration && (
105
+ <>
106
+ Ran for
107
+ <span className="mx-1">{duration}</span>
108
+ </>
109
+ )}
110
+ {duration && tokens && ` and consumed ${tokens} tokens`}
111
+ {(duration || tokens) && <ChevronRight className="size-4" />}
112
+ </>
113
+ )}
114
+ </div>
115
+ )}
116
+
117
+ {(role === 'user' || !isStreaming) && (
118
+ <>
119
+ <Message
120
+ key={id}
121
+ from={role as 'user' | 'system' | 'assistant'}
122
+ className="p-0"
123
+ >
124
+ <MessageContent>
125
+ {parts.map((part, index) => {
126
+ switch (part.type) {
127
+ case 'text':
128
+ return (
129
+ <div key={`${id}-${part.text}-${index}`}>
130
+ {part.text || ''}
131
+ </div>
132
+ );
133
+ }
134
+ })}
135
+ </MessageContent>
136
+ </Message>
137
+
138
+ <Actions
139
+ className={cn('mt-1 gap-0', role === 'user' && 'justify-end')}
140
+ >
141
+ {role === 'user' && (
142
+ <Action
143
+ label="Retry"
144
+ className="size-8 hover:bg-transparent!"
145
+ onClick={() =>
146
+ setValue(
147
+ parts
148
+ .filter((part) => part.type === 'text')
149
+ .map((part) => part.text)
150
+ .join('')
151
+ )
152
+ }
153
+ >
154
+ <RefreshCcw className="size-4" />
155
+ </Action>
156
+ )}
132
157
 
133
- <Actions
134
- className={cn('mt-1 gap-0', role === 'user' && 'justify-end')}
135
- >
136
- {role === 'user' && (
137
158
  <Action
138
- label="Retry"
139
- className="size-8 hover:bg-transparent!"
140
159
  onClick={() =>
141
- setValue(
160
+ navigator.clipboard.writeText(
142
161
  parts
143
162
  .filter((part) => part.type === 'text')
144
163
  .map((part) => part.text)
145
164
  .join('')
146
165
  )
147
166
  }
167
+ label="Copy"
168
+ className="size-8 hover:bg-transparent!"
148
169
  >
149
- <RefreshCcw className="size-4" />
170
+ <Copy className="size-4" />
150
171
  </Action>
151
- )}
152
-
153
- <Action
154
- onClick={() =>
155
- navigator.clipboard.writeText(
156
- parts
157
- .filter((part) => part.type === 'text')
158
- .map((part) => part.text)
159
- .join('')
160
- )
161
- }
162
- label="Copy"
163
- className="size-8 hover:bg-transparent!"
164
- >
165
- <Copy className="size-4" />
166
- </Action>
167
- </Actions>
168
- </>
169
- )}
170
- </div>
171
- );
172
- })}
173
- </ConversationContent>
172
+ </Actions>
173
+ </>
174
+ )}
175
+ </div>
176
+ );
177
+ })}
178
+ </ConversationContent>
179
+ )}
174
180
 
175
- <ConversationScrollButton />
181
+ {connectionStatus !== 'disconnected' && <ConversationScrollButton />}
176
182
  </Conversation>
177
183
  <InputSection
178
184
  value={value}
@@ -10,7 +10,7 @@ export interface HeaderProps {
10
10
  className?: string;
11
11
  }
12
12
 
13
- function StatusIndicator({ status }: { status: ConnectionStatus }) {
13
+ export function StatusIndicator({ status }: { status: ConnectionStatus }) {
14
14
  if (status === 'connected') {
15
15
  return (
16
16
  <div className="flex items-center gap-1.5 text-xs text-green-600 dark:text-green-400">
@@ -25,12 +25,13 @@ import {
25
25
  } from '../ui/command';
26
26
  import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
27
27
  import { Select, SelectContent, SelectItem, SelectTrigger } from '../ui/select';
28
+ import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
28
29
  import { cn } from '../../lib/utils';
29
30
  import type { AgentSchemaData } from '../../hooks/useAgentSchemas';
30
31
  import { useLogger } from '../../hooks/useLogger';
31
32
  import type { JSONSchema7 } from 'ai';
33
+ import { useWorkbench } from './WorkbenchProvider';
32
34
  import { convertJsonSchemaToZod } from 'zod-from-json-schema';
33
- import { zocker } from 'zocker';
34
35
 
35
36
  export interface InputSectionProps {
36
37
  value: string;
@@ -70,6 +71,7 @@ export function InputSection({
70
71
  onSchemaToggle,
71
72
  }: InputSectionProps) {
72
73
  const logger = useLogger('InputSection');
74
+ const { generateSample, isGeneratingSample, isAuthenticated } = useWorkbench();
73
75
  const [agentSelectOpen, setAgentSelectOpen] = useState(false);
74
76
  const [isValidInput, setIsValidInput] = useState(true);
75
77
  const [monacoHasErrors, setMonacoHasErrors] = useState<boolean | null>(null);
@@ -159,18 +161,14 @@ export function InputSection({
159
161
  monacoHasErrors,
160
162
  ]);
161
163
 
162
- const handleGenerateSample = () => {
163
- if (!selectedAgentData?.schema.input?.json || !isObjectSchema) return;
164
+ const handleGenerateSample = async () => {
165
+ if (!selectedAgentData?.schema.input?.json || !isObjectSchema || !selectedAgent) return;
164
166
 
165
167
  try {
166
- const jsonSchema = selectedAgentData.schema.input.json;
167
- const schemaObject = typeof jsonSchema === 'string' ? JSON.parse(jsonSchema) : jsonSchema;
168
-
169
- const zodSchema = convertJsonSchemaToZod(schemaObject);
170
- const sampleData = zocker(zodSchema).generate();
171
- const sampleJson = JSON.stringify(sampleData, null, 2);
168
+ const sampleJson = await generateSample(selectedAgent);
172
169
  onChange(sampleJson);
173
170
  } catch (error) {
171
+ logger.error('Failed to generate sample JSON:', error);
174
172
  console.error('Failed to generate sample JSON:', error);
175
173
  }
176
174
  };
@@ -268,17 +266,49 @@ export function InputSection({
268
266
  </Select>
269
267
  )}
270
268
 
271
- {isObjectSchema && (
272
- <Button
273
- aria-label="Generate Sample JSON"
274
- size="sm"
275
- variant="outline"
276
- className="bg-none font-normal"
277
- onClick={handleGenerateSample}
278
- >
279
- <Sparkles className="size-4" /> Sample
280
- </Button>
281
- )}
269
+ {isObjectSchema &&
270
+ (isAuthenticated ? (
271
+ <Button
272
+ aria-label="Generate Sample JSON"
273
+ size="sm"
274
+ variant="outline"
275
+ className="bg-none font-normal"
276
+ onClick={handleGenerateSample}
277
+ disabled={isGeneratingSample || !isAuthenticated}
278
+ >
279
+ {isGeneratingSample ? (
280
+ <Loader2Icon className="size-4 animate-spin" />
281
+ ) : (
282
+ <Sparkles className="size-4" />
283
+ )}{' '}
284
+ Sample
285
+ </Button>
286
+ ) : (
287
+ <Tooltip>
288
+ <TooltipTrigger asChild>
289
+ <span className="inline-flex">
290
+ <Button
291
+ aria-label="Generate Sample JSON"
292
+ size="sm"
293
+ variant="outline"
294
+ className="bg-none font-normal"
295
+ onClick={handleGenerateSample}
296
+ disabled={isGeneratingSample || !isAuthenticated}
297
+ >
298
+ {isGeneratingSample ? (
299
+ <Loader2Icon className="size-4 animate-spin" />
300
+ ) : (
301
+ <Sparkles className="size-4" />
302
+ )}{' '}
303
+ Sample
304
+ </Button>
305
+ </span>
306
+ </TooltipTrigger>
307
+ <TooltipContent>
308
+ <p>Login to generate a sample</p>
309
+ </TooltipContent>
310
+ </Tooltip>
311
+ ))}
282
312
 
283
313
  <Button
284
314
  aria-label={isSchemaOpen ? 'Hide Schema' : 'View Schema'}
@@ -293,58 +323,66 @@ export function InputSection({
293
323
 
294
324
  <PromptInput onSubmit={onSubmit} className="px-3 pb-3">
295
325
  <PromptInputBody>
296
- {(() => {
297
- switch (inputType) {
298
- case 'object':
299
- return (
300
- <MonacoJsonEditor
301
- value={value}
302
- onChange={onChange}
303
- schema={selectedAgentData?.schema.input?.json}
304
- schemaUri={`agentuity://schema/${selectedAgentData?.metadata.id}/input`}
305
- aria-invalid={!isValidInput}
306
- onValidationChange={setMonacoHasErrors}
307
- />
308
- );
326
+ {!selectedAgent ? (
327
+ <div className="flex flex-col items-center justify-center py-8 px-4 text-center">
328
+ <p className="text-sm text-muted-foreground">
329
+ Select an agent to get started.
330
+ </p>
331
+ </div>
332
+ ) : (
333
+ (() => {
334
+ switch (inputType) {
335
+ case 'object':
336
+ return (
337
+ <MonacoJsonEditor
338
+ value={value}
339
+ onChange={onChange}
340
+ schema={selectedAgentData?.schema.input?.json}
341
+ schemaUri={`agentuity://schema/${selectedAgentData?.metadata.id}/input`}
342
+ aria-invalid={!isValidInput}
343
+ onValidationChange={setMonacoHasErrors}
344
+ />
345
+ );
309
346
 
310
- case 'string':
311
- return (
312
- <PromptInputTextarea
313
- placeholder="Enter a message to send..."
314
- value={value}
315
- onChange={(e) => onChange(e.target.value)}
316
- />
317
- );
318
- default:
319
- return (
320
- <div className="flex flex-col items-center justify-center py-8 px-4 text-center ">
321
- <p className="text-sm text-muted-foreground">
322
- <span className="font-medium">
323
- This agent has no input schema.{' '}
324
- </span>
325
- </p>
326
- <Button
327
- aria-label="Run Agent"
328
- size="sm"
329
- variant="default"
330
- disabled={isLoading}
331
- onClick={onSubmit}
332
- className="mt-2"
333
- >
334
- {isLoading ? (
335
- <Loader2Icon className="size-4 animate-spin mr-2" />
336
- ) : (
337
- <SendIcon className="size-4 mr-2" />
338
- )}
339
- Run Agent
340
- </Button>
341
- </div>
342
- );
343
- }
344
- })()}
347
+ case 'string':
348
+ return (
349
+ <PromptInputTextarea
350
+ placeholder="Enter a message to send..."
351
+ value={value}
352
+ onChange={(e) => onChange(e.target.value)}
353
+ />
354
+ );
355
+ default:
356
+ return (
357
+ <div className="flex flex-col items-center justify-center py-8 px-4 text-center ">
358
+ <p className="text-sm text-muted-foreground">
359
+ <span className="font-medium">
360
+ This agent has no input schema.{' '}
361
+ </span>
362
+ </p>
363
+ <Button
364
+ aria-label="Run Agent"
365
+ size="sm"
366
+ variant="default"
367
+ disabled={isLoading}
368
+ onClick={onSubmit}
369
+ className="mt-2"
370
+ >
371
+ {isLoading ? (
372
+ <Loader2Icon className="size-4 animate-spin mr-2" />
373
+ ) : (
374
+ <SendIcon className="size-4 mr-2" />
375
+ )}
376
+ Run Agent
377
+ </Button>
378
+ </div>
379
+ );
380
+ }
381
+ })()
382
+ )}
345
383
  </PromptInputBody>
346
384
  <PromptInputFooter>
347
- {inputType !== 'none' && (
385
+ {selectedAgent && inputType !== 'none' && (
348
386
  <Button
349
387
  aria-label="Submit"
350
388
  size="icon"
@@ -167,7 +167,8 @@ export function MonacoJsonEditor({
167
167
  style={{ minHeight: '64px', maxHeight: '192px', height: `${editorHeight}px` }}
168
168
  >
169
169
  <Editor
170
- value={value || '{}'}
170
+ // Allow the editor to be truly empty. We intentionally do NOT coerce to `{}`.
171
+ value={value}
171
172
  onChange={(newValue) => onChange(newValue || '')}
172
173
  language="json"
173
174
  theme={resolvedTheme === 'light' ? 'custom-light' : 'custom-dark'}
@@ -243,6 +244,12 @@ export function MonacoJsonEditor({
243
244
  const checkValidationErrors = () => {
244
245
  const model = editor.getModel();
245
246
  if (model) {
247
+ // Treat an empty editor as a valid "empty state" (avoid Monaco's JSON parse error).
248
+ if (!model.getValue().trim()) {
249
+ onValidationChange(false);
250
+ return;
251
+ }
252
+
246
253
  const markers = monaco.editor.getModelMarkers({ resource: model.uri });
247
254
  const hasErrors = markers.some(
248
255
  (marker: monaco.editor.IMarker) =>