@agentuity/workbench 0.0.86 → 0.0.88

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 (271) hide show
  1. package/dist/components/App.d.ts.map +1 -1
  2. package/dist/components/App.js +18 -2
  3. package/dist/components/App.js.map +1 -1
  4. package/dist/components/ai-elements/code-block.d.ts +3 -3
  5. package/dist/components/ai-elements/code-block.d.ts.map +1 -1
  6. package/dist/components/ai-elements/code-block.js +29 -7
  7. package/dist/components/ai-elements/code-block.js.map +1 -1
  8. package/dist/components/internal/Chat.d.ts +3 -2
  9. package/dist/components/internal/Chat.d.ts.map +1 -1
  10. package/dist/components/internal/Chat.js +23 -24
  11. package/dist/components/internal/Chat.js.map +1 -1
  12. package/dist/components/internal/InputSection.d.ts +3 -2
  13. package/dist/components/internal/InputSection.d.ts.map +1 -1
  14. package/dist/components/internal/InputSection.js +68 -6
  15. package/dist/components/internal/InputSection.js.map +1 -1
  16. package/dist/components/internal/MonacoJsonEditor.d.ts +3 -1
  17. package/dist/components/internal/MonacoJsonEditor.d.ts.map +1 -1
  18. package/dist/components/internal/MonacoJsonEditor.js +41 -57
  19. package/dist/components/internal/MonacoJsonEditor.js.map +1 -1
  20. package/dist/components/internal/Schema.d.ts +1 -2
  21. package/dist/components/internal/Schema.d.ts.map +1 -1
  22. package/dist/components/internal/Schema.js +2 -3
  23. package/dist/components/internal/Schema.js.map +1 -1
  24. package/dist/components/internal/WorkbenchProvider.d.ts.map +1 -1
  25. package/dist/components/internal/WorkbenchProvider.js +55 -8
  26. package/dist/components/internal/WorkbenchProvider.js.map +1 -1
  27. package/dist/components/ui/button.d.ts +1 -1
  28. package/dist/components/ui/input-group.js +2 -2
  29. package/dist/components/ui/input-group.js.map +1 -1
  30. package/dist/components/ui/input.d.ts.map +1 -1
  31. package/dist/components/ui/input.js +1 -1
  32. package/dist/components/ui/input.js.map +1 -1
  33. package/dist/index.d.ts +3 -5
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +4 -6
  36. package/dist/index.js.map +1 -1
  37. package/dist/{styles.css → standalone.css} +206 -1554
  38. package/package.json +29 -29
  39. package/src/{styles.css → base.css} +36 -52
  40. package/src/components/App.tsx +41 -5
  41. package/src/components/ai-elements/code-block.tsx +42 -10
  42. package/src/components/internal/Chat.tsx +112 -119
  43. package/src/components/internal/InputSection.tsx +79 -9
  44. package/src/components/internal/MonacoJsonEditor.tsx +59 -65
  45. package/src/components/internal/Schema.tsx +74 -86
  46. package/src/components/internal/WorkbenchProvider.tsx +69 -10
  47. package/src/components/ui/input-group.tsx +2 -2
  48. package/src/components/ui/input.tsx +2 -3
  49. package/src/index.ts +5 -14
  50. package/src/integration.css +15 -0
  51. package/src/standalone.css +25 -0
  52. package/dist/components/ConnectionStatus.d.ts +0 -7
  53. package/dist/components/ConnectionStatus.d.ts.map +0 -1
  54. package/dist/components/ConnectionStatus.js +0 -52
  55. package/dist/components/ConnectionStatus.js.map +0 -1
  56. package/dist/components/Inline.d.ts +0 -10
  57. package/dist/components/Inline.d.ts.map +0 -1
  58. package/dist/components/Inline.js +0 -11
  59. package/dist/components/Inline.js.map +0 -1
  60. package/dist/components/ai-elements/artifact.d.ts +0 -24
  61. package/dist/components/ai-elements/artifact.d.ts.map +0 -1
  62. package/dist/components/ai-elements/artifact.js +0 -21
  63. package/dist/components/ai-elements/artifact.js.map +0 -1
  64. package/dist/components/ai-elements/branch.d.ts +0 -21
  65. package/dist/components/ai-elements/branch.d.ts.map +0 -1
  66. package/dist/components/ai-elements/branch.js +0 -71
  67. package/dist/components/ai-elements/branch.js.map +0 -1
  68. package/dist/components/ai-elements/canvas.d.ts +0 -9
  69. package/dist/components/ai-elements/canvas.d.ts.map +0 -1
  70. package/dist/components/ai-elements/canvas.js +0 -6
  71. package/dist/components/ai-elements/canvas.js.map +0 -1
  72. package/dist/components/ai-elements/chain-of-thought.d.ts +0 -30
  73. package/dist/components/ai-elements/chain-of-thought.d.ts.map +0 -1
  74. package/dist/components/ai-elements/chain-of-thought.js +0 -52
  75. package/dist/components/ai-elements/chain-of-thought.js.map +0 -1
  76. package/dist/components/ai-elements/confirmation.d.ts +0 -27
  77. package/dist/components/ai-elements/confirmation.d.ts.map +0 -1
  78. package/dist/components/ai-elements/confirmation.js +0 -57
  79. package/dist/components/ai-elements/confirmation.js.map +0 -1
  80. package/dist/components/ai-elements/connection.d.ts +0 -3
  81. package/dist/components/ai-elements/connection.d.ts.map +0 -1
  82. package/dist/components/ai-elements/connection.js +0 -4
  83. package/dist/components/ai-elements/connection.js.map +0 -1
  84. package/dist/components/ai-elements/context.d.ts +0 -33
  85. package/dist/components/ai-elements/context.d.ts.map +0 -1
  86. package/dist/components/ai-elements/context.js +0 -167
  87. package/dist/components/ai-elements/context.js.map +0 -1
  88. package/dist/components/ai-elements/controls.d.ts +0 -5
  89. package/dist/components/ai-elements/controls.d.ts.map +0 -1
  90. package/dist/components/ai-elements/controls.js +0 -6
  91. package/dist/components/ai-elements/controls.js.map +0 -1
  92. package/dist/components/ai-elements/edge.d.ts +0 -6
  93. package/dist/components/ai-elements/edge.d.ts.map +0 -1
  94. package/dist/components/ai-elements/edge.js +0 -83
  95. package/dist/components/ai-elements/edge.js.map +0 -1
  96. package/dist/components/ai-elements/image.d.ts +0 -7
  97. package/dist/components/ai-elements/image.d.ts.map +0 -1
  98. package/dist/components/ai-elements/image.js +0 -4
  99. package/dist/components/ai-elements/image.js.map +0 -1
  100. package/dist/components/ai-elements/inline-citation.d.ts +0 -39
  101. package/dist/components/ai-elements/inline-citation.d.ts.map +0 -1
  102. package/dist/components/ai-elements/inline-citation.js +0 -62
  103. package/dist/components/ai-elements/inline-citation.js.map +0 -1
  104. package/dist/components/ai-elements/loader.d.ts +0 -6
  105. package/dist/components/ai-elements/loader.d.ts.map +0 -1
  106. package/dist/components/ai-elements/loader.js +0 -5
  107. package/dist/components/ai-elements/loader.js.map +0 -1
  108. package/dist/components/ai-elements/node.d.ts +0 -22
  109. package/dist/components/ai-elements/node.d.ts.map +0 -1
  110. package/dist/components/ai-elements/node.js +0 -12
  111. package/dist/components/ai-elements/node.js.map +0 -1
  112. package/dist/components/ai-elements/open-in-chat.d.ts +0 -29
  113. package/dist/components/ai-elements/open-in-chat.d.ts.map +0 -1
  114. package/dist/components/ai-elements/open-in-chat.js +0 -97
  115. package/dist/components/ai-elements/open-in-chat.js.map +0 -1
  116. package/dist/components/ai-elements/panel.d.ts +0 -6
  117. package/dist/components/ai-elements/panel.d.ts.map +0 -1
  118. package/dist/components/ai-elements/panel.js +0 -5
  119. package/dist/components/ai-elements/panel.js.map +0 -1
  120. package/dist/components/ai-elements/plan.d.ts +0 -26
  121. package/dist/components/ai-elements/plan.d.ts.map +0 -1
  122. package/dist/components/ai-elements/plan.js +0 -32
  123. package/dist/components/ai-elements/plan.js.map +0 -1
  124. package/dist/components/ai-elements/queue.d.ts +0 -62
  125. package/dist/components/ai-elements/queue.d.ts.map +0 -1
  126. package/dist/components/ai-elements/queue.js +0 -25
  127. package/dist/components/ai-elements/queue.js.map +0 -1
  128. package/dist/components/ai-elements/reasoning.d.ts +0 -17
  129. package/dist/components/ai-elements/reasoning.d.ts.map +0 -1
  130. package/dist/components/ai-elements/reasoning.js +0 -77
  131. package/dist/components/ai-elements/reasoning.js.map +0 -1
  132. package/dist/components/ai-elements/response.d.ts +0 -6
  133. package/dist/components/ai-elements/response.d.ts.map +0 -1
  134. package/dist/components/ai-elements/response.js +0 -8
  135. package/dist/components/ai-elements/response.js.map +0 -1
  136. package/dist/components/ai-elements/sources.d.ts +0 -13
  137. package/dist/components/ai-elements/sources.d.ts.map +0 -1
  138. package/dist/components/ai-elements/sources.js +0 -10
  139. package/dist/components/ai-elements/sources.js.map +0 -1
  140. package/dist/components/ai-elements/suggestion.d.ts +0 -11
  141. package/dist/components/ai-elements/suggestion.d.ts.map +0 -1
  142. package/dist/components/ai-elements/suggestion.js +0 -13
  143. package/dist/components/ai-elements/suggestion.js.map +0 -1
  144. package/dist/components/ai-elements/task.d.ts +0 -15
  145. package/dist/components/ai-elements/task.d.ts.map +0 -1
  146. package/dist/components/ai-elements/task.js +0 -11
  147. package/dist/components/ai-elements/task.js.map +0 -1
  148. package/dist/components/ai-elements/tool.d.ts +0 -24
  149. package/dist/components/ai-elements/tool.d.ts.map +0 -1
  150. package/dist/components/ai-elements/tool.js +0 -47
  151. package/dist/components/ai-elements/tool.js.map +0 -1
  152. package/dist/components/ai-elements/toolbar.d.ts +0 -6
  153. package/dist/components/ai-elements/toolbar.d.ts.map +0 -1
  154. package/dist/components/ai-elements/toolbar.js +0 -5
  155. package/dist/components/ai-elements/toolbar.js.map +0 -1
  156. package/dist/components/ai-elements/web-preview.d.ts +0 -35
  157. package/dist/components/ai-elements/web-preview.d.ts.map +0 -1
  158. package/dist/components/ai-elements/web-preview.js +0 -63
  159. package/dist/components/ai-elements/web-preview.js.map +0 -1
  160. package/dist/components/ui/alert.d.ts +0 -10
  161. package/dist/components/ui/alert.d.ts.map +0 -1
  162. package/dist/components/ui/alert.js +0 -25
  163. package/dist/components/ui/alert.js.map +0 -1
  164. package/dist/components/ui/badge.d.ts +0 -10
  165. package/dist/components/ui/badge.d.ts.map +0 -1
  166. package/dist/components/ui/badge.js +0 -23
  167. package/dist/components/ui/badge.js.map +0 -1
  168. package/dist/components/ui/card.d.ts +0 -10
  169. package/dist/components/ui/card.d.ts.map +0 -1
  170. package/dist/components/ui/card.js +0 -25
  171. package/dist/components/ui/card.js.map +0 -1
  172. package/dist/components/ui/carousel.d.ts +0 -20
  173. package/dist/components/ui/carousel.d.ts.map +0 -1
  174. package/dist/components/ui/carousel.js +0 -92
  175. package/dist/components/ui/carousel.js.map +0 -1
  176. package/dist/components/ui/checkbox.d.ts +0 -5
  177. package/dist/components/ui/checkbox.d.ts.map +0 -1
  178. package/dist/components/ui/checkbox.js +0 -9
  179. package/dist/components/ui/checkbox.js.map +0 -1
  180. package/dist/components/ui/collapsible.d.ts +0 -6
  181. package/dist/components/ui/collapsible.d.ts.map +0 -1
  182. package/dist/components/ui/collapsible.js +0 -14
  183. package/dist/components/ui/collapsible.js.map +0 -1
  184. package/dist/components/ui/field.d.ts +0 -25
  185. package/dist/components/ui/field.d.ts.map +0 -1
  186. package/dist/components/ui/field.js +0 -74
  187. package/dist/components/ui/field.js.map +0 -1
  188. package/dist/components/ui/form.d.ts +0 -25
  189. package/dist/components/ui/form.d.ts.map +0 -1
  190. package/dist/components/ui/form.js +0 -58
  191. package/dist/components/ui/form.js.map +0 -1
  192. package/dist/components/ui/label.d.ts +0 -5
  193. package/dist/components/ui/label.d.ts.map +0 -1
  194. package/dist/components/ui/label.js +0 -9
  195. package/dist/components/ui/label.js.map +0 -1
  196. package/dist/components/ui/progress.d.ts +0 -5
  197. package/dist/components/ui/progress.d.ts.map +0 -1
  198. package/dist/components/ui/progress.js +0 -9
  199. package/dist/components/ui/progress.js.map +0 -1
  200. package/dist/components/ui/separator.d.ts +0 -5
  201. package/dist/components/ui/separator.d.ts.map +0 -1
  202. package/dist/components/ui/separator.js +0 -9
  203. package/dist/components/ui/separator.js.map +0 -1
  204. package/dist/components/ui/switch.d.ts +0 -5
  205. package/dist/components/ui/switch.d.ts.map +0 -1
  206. package/dist/components/ui/switch.js +0 -8
  207. package/dist/components/ui/switch.js.map +0 -1
  208. package/dist/components/ui/tabs.d.ts +0 -8
  209. package/dist/components/ui/tabs.d.ts.map +0 -1
  210. package/dist/components/ui/tabs.js +0 -17
  211. package/dist/components/ui/tabs.js.map +0 -1
  212. package/dist/components/ui/toggle.d.ts +0 -10
  213. package/dist/components/ui/toggle.d.ts.map +0 -1
  214. package/dist/components/ui/toggle.js +0 -26
  215. package/dist/components/ui/toggle.js.map +0 -1
  216. package/dist/components.d.ts +0 -12
  217. package/dist/components.d.ts.map +0 -1
  218. package/dist/components.js +0 -13
  219. package/dist/components.js.map +0 -1
  220. package/dist/hooks/index.d.ts +0 -7
  221. package/dist/hooks/index.d.ts.map +0 -1
  222. package/dist/hooks/index.js +0 -5
  223. package/dist/hooks/index.js.map +0 -1
  224. package/dist/hooks/useWorkbenchSchemas.d.ts +0 -56
  225. package/dist/hooks/useWorkbenchSchemas.d.ts.map +0 -1
  226. package/dist/hooks/useWorkbenchSchemas.js +0 -63
  227. package/dist/hooks/useWorkbenchSchemas.js.map +0 -1
  228. package/src/components/ConnectionStatus.tsx +0 -67
  229. package/src/components/Inline.tsx +0 -16
  230. package/src/components/ai-elements/artifact.tsx +0 -118
  231. package/src/components/ai-elements/branch.tsx +0 -187
  232. package/src/components/ai-elements/canvas.tsx +0 -24
  233. package/src/components/ai-elements/chain-of-thought.tsx +0 -198
  234. package/src/components/ai-elements/confirmation.tsx +0 -119
  235. package/src/components/ai-elements/connection.tsx +0 -16
  236. package/src/components/ai-elements/context.tsx +0 -357
  237. package/src/components/ai-elements/controls.tsx +0 -18
  238. package/src/components/ai-elements/edge.tsx +0 -131
  239. package/src/components/ai-elements/image.tsx +0 -16
  240. package/src/components/ai-elements/inline-citation.tsx +0 -246
  241. package/src/components/ai-elements/loader.tsx +0 -88
  242. package/src/components/ai-elements/node.tsx +0 -66
  243. package/src/components/ai-elements/open-in-chat.tsx +0 -333
  244. package/src/components/ai-elements/panel.tsx +0 -12
  245. package/src/components/ai-elements/plan.tsx +0 -123
  246. package/src/components/ai-elements/queue.tsx +0 -231
  247. package/src/components/ai-elements/reasoning.tsx +0 -163
  248. package/src/components/ai-elements/response.tsx +0 -19
  249. package/src/components/ai-elements/sources.tsx +0 -53
  250. package/src/components/ai-elements/suggestion.tsx +0 -47
  251. package/src/components/ai-elements/task.tsx +0 -64
  252. package/src/components/ai-elements/tool.tsx +0 -136
  253. package/src/components/ai-elements/toolbar.tsx +0 -13
  254. package/src/components/ai-elements/web-preview.tsx +0 -238
  255. package/src/components/ui/alert.tsx +0 -60
  256. package/src/components/ui/badge.tsx +0 -40
  257. package/src/components/ui/card.tsx +0 -41
  258. package/src/components/ui/carousel.tsx +0 -234
  259. package/src/components/ui/checkbox.tsx +0 -27
  260. package/src/components/ui/collapsible.tsx +0 -21
  261. package/src/components/ui/field.tsx +0 -234
  262. package/src/components/ui/form.tsx +0 -154
  263. package/src/components/ui/label.tsx +0 -21
  264. package/src/components/ui/progress.tsx +0 -28
  265. package/src/components/ui/separator.tsx +0 -28
  266. package/src/components/ui/switch.tsx +0 -26
  267. package/src/components/ui/tabs.tsx +0 -52
  268. package/src/components/ui/toggle.tsx +0 -44
  269. package/src/components.tsx +0 -29
  270. package/src/hooks/index.ts +0 -20
  271. package/src/hooks/useWorkbenchSchemas.ts +0 -69
@@ -1,4 +1,4 @@
1
- import React, { useMemo, useState } from 'react';
1
+ import React, { useMemo, useState, useCallback, useEffect } from 'react';
2
2
  import {
3
3
  CheckIcon,
4
4
  ChevronsUpDownIcon,
@@ -41,7 +41,8 @@ export interface InputSectionProps {
41
41
  selectedAgent: string;
42
42
  setSelectedAgent: (agentId: string) => void;
43
43
  suggestions: string[];
44
- onSchemaOpen: () => void;
44
+ isSchemaOpen: boolean;
45
+ onSchemaToggle: () => void;
45
46
  }
46
47
 
47
48
  function isSchemaRootObject(schemaJson?: JSONSchema7): boolean {
@@ -65,10 +66,13 @@ export function InputSection({
65
66
  selectedAgent,
66
67
  setSelectedAgent,
67
68
  suggestions,
68
- onSchemaOpen,
69
+ isSchemaOpen,
70
+ onSchemaToggle,
69
71
  }: InputSectionProps) {
70
72
  const logger = useLogger('InputSection');
71
73
  const [agentSelectOpen, setAgentSelectOpen] = useState(false);
74
+ const [isValidInput, setIsValidInput] = useState(true);
75
+ const [monacoHasErrors, setMonacoHasErrors] = useState<boolean | null>(null);
72
76
 
73
77
  const selectedAgentData = Object.values(agents).find(
74
78
  (agent) => agent.metadata.agentId === selectedAgent
@@ -95,10 +99,66 @@ export function InputSection({
95
99
  return 'string'; // String schema
96
100
  }
97
101
  return 'none'; // Default to none for other types
98
- }, [selectedAgentData?.schema.input?.json]);
102
+ }, [selectedAgentData?.schema.input?.json, logger]);
99
103
 
100
104
  const isObjectSchema = inputType === 'object';
101
105
 
106
+ // Validate JSON input against schema using zod (fallback for non-Monaco cases)
107
+ const validateInput = useCallback(
108
+ (inputValue: string, schema?: JSONSchema7): boolean => {
109
+ if (!schema || !isObjectSchema || !inputValue.trim()) {
110
+ return true; // No validation needed or empty input
111
+ }
112
+
113
+ try {
114
+ // Parse JSON first
115
+ const parsedJson = JSON.parse(inputValue);
116
+
117
+ // Convert schema to zod and validate
118
+ const schemaObject = typeof schema === 'string' ? JSON.parse(schema) : schema;
119
+ const zodSchema = convertJsonSchemaToZod(schemaObject);
120
+
121
+ // Validate with zod
122
+ const result = zodSchema.safeParse(parsedJson);
123
+ return result.success;
124
+ } catch {
125
+ // JSON parse error or schema validation error
126
+ return false;
127
+ }
128
+ },
129
+ [isObjectSchema]
130
+ );
131
+
132
+ // Reset Monaco error state when schema changes
133
+ useEffect(() => {
134
+ if (isObjectSchema) {
135
+ setMonacoHasErrors(null);
136
+ }
137
+ }, [selectedAgentData?.schema?.input?.json, isObjectSchema]);
138
+
139
+ // Update validation state - use Monaco errors if available, otherwise fall back to zod validation
140
+ useEffect(() => {
141
+ if (isObjectSchema) {
142
+ if (monacoHasErrors !== null) {
143
+ // Monaco is handling validation, use its error state
144
+ setIsValidInput(!monacoHasErrors);
145
+ } else {
146
+ // Monaco hasn't reported yet, use zod validation as fallback
147
+ const isValid = validateInput(value, selectedAgentData?.schema?.input?.json);
148
+ setIsValidInput(isValid);
149
+ }
150
+ } else {
151
+ // No schema or not object schema
152
+ setIsValidInput(true);
153
+ }
154
+ }, [
155
+ value,
156
+ selectedAgentData?.schema?.input?.json,
157
+ validateInput,
158
+ isObjectSchema,
159
+ monacoHasErrors,
160
+ ]);
161
+
102
162
  const handleGenerateSample = () => {
103
163
  if (!selectedAgentData?.schema.input?.json || !isObjectSchema) return;
104
164
 
@@ -115,6 +175,14 @@ export function InputSection({
115
175
  }
116
176
  };
117
177
 
178
+ // Memoized submit disabled condition for readability
179
+ const isSubmitDisabled = useMemo(() => {
180
+ if (isLoading) return true;
181
+ if (inputType === 'string' && !value.trim()) return true;
182
+ if (inputType === 'object' && (!isValidInput || !value.trim())) return true;
183
+ return false;
184
+ }, [isLoading, inputType, value, isValidInput]);
185
+
118
186
  return (
119
187
  <>
120
188
  <div className="flex items-center gap-2 py-2 px-3">
@@ -213,11 +281,11 @@ export function InputSection({
213
281
  )}
214
282
 
215
283
  <Button
216
- aria-label="View Schema"
284
+ aria-label={isSchemaOpen ? 'Hide Schema' : 'View Schema'}
217
285
  size="sm"
218
- variant="outline"
219
- className="bg-none font-normal"
220
- onClick={onSchemaOpen}
286
+ variant={isSchemaOpen ? 'default' : 'outline'}
287
+ className={cn('font-normal', isSchemaOpen ? 'bg-primary' : 'bg-none')}
288
+ onClick={onSchemaToggle}
221
289
  >
222
290
  <FileJson className="size-4" /> Schema
223
291
  </Button>
@@ -234,6 +302,8 @@ export function InputSection({
234
302
  onChange={onChange}
235
303
  schema={selectedAgentData?.schema.input?.json}
236
304
  schemaUri={`agentuity://schema/${selectedAgentData?.metadata.id}/input`}
305
+ aria-invalid={!isValidInput}
306
+ onValidationChange={setMonacoHasErrors}
237
307
  />
238
308
  );
239
309
 
@@ -279,7 +349,7 @@ export function InputSection({
279
349
  aria-label="Submit"
280
350
  size="icon"
281
351
  variant="default"
282
- disabled={isLoading || (inputType === 'string' && !value.trim())}
352
+ disabled={isSubmitDisabled}
283
353
  onClick={() => {
284
354
  logger.debug(
285
355
  '🔥 Submit button clicked! inputType:',
@@ -1,8 +1,11 @@
1
1
  import React, { useEffect, useState } from 'react';
2
2
  import Editor, { type Monaco, type OnMount } from '@monaco-editor/react';
3
3
  import { useTheme } from '../ui/theme-provider';
4
- import { bundledThemes } from 'shiki';
5
4
  import type { JSONSchema7 } from 'ai';
5
+ import type * as monaco from 'monaco-editor';
6
+ import type { ThemeRegistration } from 'shiki';
7
+ import oneLightModule from '@shikijs/themes/one-light';
8
+ import oneDarkProModule from '@shikijs/themes/one-dark-pro';
6
9
 
7
10
  interface MonacoJsonEditorProps {
8
11
  value: string;
@@ -10,6 +13,8 @@ interface MonacoJsonEditorProps {
10
13
  schema?: JSONSchema7;
11
14
  schemaUri?: string;
12
15
  className?: string;
16
+ 'aria-invalid'?: boolean;
17
+ onValidationChange?: (hasErrors: boolean) => void;
13
18
  }
14
19
 
15
20
  // Convert color value to valid hex for Monaco
@@ -104,6 +109,8 @@ export function MonacoJsonEditor({
104
109
  schema,
105
110
  schemaUri = 'agentuity://schema/default',
106
111
  className = '',
112
+ 'aria-invalid': ariaInvalid,
113
+ onValidationChange,
107
114
  }: MonacoJsonEditorProps) {
108
115
  const { theme } = useTheme();
109
116
  const [editorInstance, setEditorInstance] = useState<Parameters<OnMount>[0] | null>(null);
@@ -120,13 +127,16 @@ export function MonacoJsonEditor({
120
127
 
121
128
  // Configure JSON schema when schema or monacoInstance changes
122
129
  useEffect(() => {
123
- if (!monacoInstance || !schema) return;
130
+ if (!monacoInstance || !schema) {
131
+ return;
132
+ }
124
133
 
125
134
  const schemaObject = typeof schema === 'string' ? JSON.parse(schema) : schema;
126
135
 
127
136
  // Configure Monaco JSON language support for schema validation
128
137
  monacoInstance.languages.json.jsonDefaults.setDiagnosticsOptions({
129
138
  validate: true,
139
+ allowComments: false,
130
140
  schemas: [
131
141
  {
132
142
  uri: schemaUri,
@@ -134,6 +144,9 @@ export function MonacoJsonEditor({
134
144
  schema: schemaObject,
135
145
  },
136
146
  ],
147
+ enableSchemaRequest: true,
148
+ schemaRequest: 'error',
149
+ schemaValidation: 'error',
137
150
  });
138
151
  }, [monacoInstance, schema, schemaUri]);
139
152
 
@@ -148,6 +161,8 @@ export function MonacoJsonEditor({
148
161
 
149
162
  return (
150
163
  <div
164
+ data-slot="input-group-control"
165
+ aria-invalid={ariaInvalid}
151
166
  className={`w-full pl-3 pb-3 [&_.monaco-editor]:!bg-transparent [&_.monaco-editor-background]:!bg-transparent [&_.view-lines]:!bg-transparent [&_.monaco-editor]:!shadow-none [&_.monaco-scrollable-element]:!shadow-none [&_.overflow-guard]:!shadow-none [&_.monaco-scrollable-element>.shadow.top]:!hidden [&_.monaco-editor_.scroll-decoration]:!hidden [&_.shadow.top]:!hidden [&_.scroll-decoration]:!hidden ${className}`}
152
167
  style={{ minHeight: '64px', maxHeight: '192px', height: `${editorHeight}px` }}
153
168
  >
@@ -192,7 +207,7 @@ export function MonacoJsonEditor({
192
207
  },
193
208
  padding: { top: 12, bottom: 12 },
194
209
  // Additional background transparency options
195
- renderValidationDecorations: 'off',
210
+ renderValidationDecorations: 'on',
196
211
  guides: {
197
212
  indentation: false,
198
213
  highlightActiveIndentation: false,
@@ -223,6 +238,35 @@ export function MonacoJsonEditor({
223
238
  // Update height on content changes
224
239
  editor.onDidChangeModelContent(updateHeight);
225
240
 
241
+ // Listen to validation markers to detect schema errors
242
+ if (onValidationChange) {
243
+ const checkValidationErrors = () => {
244
+ const model = editor.getModel();
245
+ if (model) {
246
+ const markers = monaco.editor.getModelMarkers({ resource: model.uri });
247
+ const hasErrors = markers.some(
248
+ (marker: monaco.editor.IMarker) =>
249
+ marker.severity === monaco.MarkerSeverity.Error
250
+ );
251
+ onValidationChange(hasErrors);
252
+ }
253
+ };
254
+
255
+ // Check on model changes
256
+ editor.onDidChangeModelContent(checkValidationErrors);
257
+
258
+ // Check when markers change
259
+ monaco.editor.onDidChangeMarkers((uris: monaco.Uri[]) => {
260
+ const model = editor.getModel();
261
+ if (model && uris.includes(model.uri)) {
262
+ checkValidationErrors();
263
+ }
264
+ });
265
+
266
+ // Initial check
267
+ setTimeout(checkValidationErrors, 100);
268
+ }
269
+
226
270
  // Initial height update
227
271
  setTimeout(updateHeight, 0);
228
272
 
@@ -286,72 +330,22 @@ export function MonacoJsonEditor({
286
330
  }
287
331
  }, 0);
288
332
  }}
289
- beforeMount={async (monaco) => {
333
+ beforeMount={(monaco) => {
290
334
  setMonacoInstance(monaco);
291
335
 
292
- try {
293
- // Try to use actual Shiki themes
294
- const oneLightThemeModule = await bundledThemes['one-light']();
295
- const oneDarkProThemeModule = await bundledThemes['one-dark-pro']();
336
+ // Use the same direct theme imports as code-block
337
+ const oneLight = (
338
+ 'default' in oneLightModule ? oneLightModule.default : oneLightModule
339
+ ) as ThemeRegistration;
340
+ const oneDarkPro = (
341
+ 'default' in oneDarkProModule ? oneDarkProModule.default : oneDarkProModule
342
+ ) as ThemeRegistration;
296
343
 
297
- if (oneLightThemeModule?.default) {
298
- const lightMonacoTheme = convertShikiToMonaco(
299
- oneLightThemeModule.default,
300
- 'one-light'
301
- );
302
- monaco.editor.defineTheme('custom-light', lightMonacoTheme);
303
- }
344
+ const lightMonacoTheme = convertShikiToMonaco(oneLight, 'one-light');
345
+ monaco.editor.defineTheme('custom-light', lightMonacoTheme);
304
346
 
305
- if (oneDarkProThemeModule?.default) {
306
- const darkMonacoTheme = convertShikiToMonaco(
307
- oneDarkProThemeModule.default,
308
- 'one-dark-pro'
309
- );
310
- monaco.editor.defineTheme('custom-dark', darkMonacoTheme);
311
- }
312
- } catch (error) {
313
- console.warn(
314
- 'Failed to load Shiki themes, falling back to manual themes:',
315
- error
316
- );
317
-
318
- // Fallback to manual theme definitions
319
- monaco.editor.defineTheme('custom-light', {
320
- base: 'vs',
321
- inherit: true,
322
- rules: [
323
- { token: 'string.key.json', foreground: 'e45649' },
324
- { token: 'string.value.json', foreground: '50a14f' },
325
- { token: 'number.json', foreground: '986801' },
326
- { token: 'keyword.json', foreground: '986801' },
327
- { token: 'string', foreground: '50a14f' },
328
- { token: 'number', foreground: '986801' },
329
- { token: 'keyword', foreground: '986801' },
330
- ],
331
- colors: {
332
- 'editor.background': '#00000000',
333
- 'editor.foreground': '#383a42',
334
- },
335
- });
336
-
337
- monaco.editor.defineTheme('custom-dark', {
338
- base: 'vs-dark',
339
- inherit: true,
340
- rules: [
341
- { token: 'string.key.json', foreground: 'e06c75' },
342
- { token: 'string.value.json', foreground: '98c379' },
343
- { token: 'number.json', foreground: 'd19a66' },
344
- { token: 'keyword.json', foreground: 'c678dd' },
345
- { token: 'string', foreground: '98c379' },
346
- { token: 'number', foreground: 'd19a66' },
347
- { token: 'keyword', foreground: 'c678dd' },
348
- ],
349
- colors: {
350
- 'editor.background': '#00000000',
351
- 'editor.foreground': '#abb2bf',
352
- },
353
- });
354
- }
347
+ const darkMonacoTheme = convertShikiToMonaco(oneDarkPro, 'one-dark-pro');
348
+ monaco.editor.defineTheme('custom-dark', darkMonacoTheme);
355
349
  }}
356
350
  />
357
351
  </div>
@@ -3,109 +3,97 @@ import { X, FileJson } from 'lucide-react';
3
3
  import { Button } from '../ui/button';
4
4
  import { CodeBlock, CodeBlockCopyButton } from '../ai-elements/code-block';
5
5
  import { ScrollArea } from '../ui/scroll-area';
6
- import { cn } from '../../lib/utils';
7
6
  import { useWorkbench } from './WorkbenchProvider';
8
7
 
9
8
  export interface SchemaProps {
10
- open: boolean;
11
9
  onOpenChange: (open: boolean) => void;
12
10
  }
13
11
 
14
- export function Schema({ open, onOpenChange }: SchemaProps) {
12
+ export function Schema({ onOpenChange }: SchemaProps) {
15
13
  const { agents, selectedAgent, schemasLoading, schemasError } = useWorkbench();
16
14
 
17
15
  const selectedAgentData =
18
16
  Object.values(agents).find((agent) => agent.metadata.agentId === selectedAgent) || null;
19
17
 
20
18
  return (
21
- <>
22
- {/* Sidebar */}
23
- <div
24
- className={cn(
25
- 'absolute top-0 right-0 h-full w-[600px] max-w-[90vw] bg-background border-l border-border z-50 shadow-xl transition-transform duration-300 ease-in-out flex flex-col',
26
- open ? 'translate-x-0' : 'translate-x-full'
27
- )}
28
- >
29
- {/* Header */}
30
- <div className="flex items-center justify-between p-4 border-b border-border">
31
- <div className="flex items-center gap-2">
32
- <FileJson className="size-5 text-muted-foreground" />
33
- <div>
34
- <h2 className="text-lg font-semibold">Schema</h2>
35
- {selectedAgentData && (
36
- <p className="text-sm text-muted-foreground">
37
- {selectedAgentData.metadata.name}
38
- </p>
39
- )}
40
- </div>
19
+ <div className="h-full bg-background border-l border-border flex flex-col">
20
+ {/* Header */}
21
+ <div className="flex items-center justify-between p-4 border-b border-border">
22
+ <div className="flex items-center gap-2">
23
+ <FileJson className="size-5 text-muted-foreground" />
24
+ <div>
25
+ <h2 className="text-lg font-semibold">Schema</h2>
26
+ {selectedAgentData && (
27
+ <p className="text-sm text-muted-foreground">
28
+ {selectedAgentData.metadata.name}
29
+ </p>
30
+ )}
41
31
  </div>
42
- <Button
43
- variant="ghost"
44
- size="icon"
45
- onClick={() => onOpenChange(false)}
46
- className="size-8"
47
- >
48
- <X className="size-4" />
49
- </Button>
50
32
  </div>
51
- {/* Content */}
52
- <ScrollArea className="flex-1">
53
- <div className="p-6 space-y-6">
54
- {schemasLoading && (
55
- <div className="text-center text-muted-foreground py-8">
56
- Loading schemas...
57
- </div>
58
- )}
33
+ <Button
34
+ variant="ghost"
35
+ size="icon"
36
+ onClick={() => onOpenChange(false)}
37
+ className="size-8"
38
+ >
39
+ <X className="size-4" />
40
+ </Button>
41
+ </div>
42
+ {/* Content */}
43
+ <ScrollArea className="flex-1">
44
+ <div className="p-6 space-y-6">
45
+ {schemasLoading && (
46
+ <div className="text-center text-muted-foreground py-8">Loading schemas...</div>
47
+ )}
59
48
 
60
- {schemasError && (
61
- <div className="rounded-md bg-destructive/10 text-destructive p-4">
62
- <p className="font-medium">Error loading schemas</p>
63
- <p className="text-sm mt-1">{schemasError.message}</p>
64
- </div>
65
- )}
49
+ {schemasError && (
50
+ <div className="rounded-md bg-destructive/10 text-destructive p-4">
51
+ <p className="font-medium">Error loading schemas</p>
52
+ <p className="text-sm mt-1">{schemasError.message}</p>
53
+ </div>
54
+ )}
66
55
 
67
- {!schemasLoading && !schemasError && !selectedAgentData && (
68
- <div className="text-center text-muted-foreground py-8">
69
- <p>No schema available for selected agent</p>
70
- </div>
71
- )}
56
+ {!schemasLoading && !schemasError && !selectedAgentData && (
57
+ <div className="text-center text-muted-foreground py-8">
58
+ <p>No schema available for selected agent</p>
59
+ </div>
60
+ )}
72
61
 
73
- {!schemasLoading && !schemasError && selectedAgentData && (
74
- <>
75
- {/* Input Schema */}
76
- {selectedAgentData.schema.input?.code ? (
77
- <div className="space-y-2">
78
- <h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
79
- Input Schema
80
- </h3>
81
- <CodeBlock
82
- code={selectedAgentData.schema.input?.code}
83
- language="typescript"
84
- >
85
- <CodeBlockCopyButton />
86
- </CodeBlock>
87
- </div>
88
- ) : null}
89
- {/* Output Schema */}
90
- {selectedAgentData.schema.output?.code ? (
91
- <div className="space-y-2">
92
- <h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
93
- Output Schema
94
- </h3>
95
- <CodeBlock
96
- code={selectedAgentData.schema.output?.code}
97
- language="typescript"
98
- >
99
- <CodeBlockCopyButton />
100
- </CodeBlock>
101
- </div>
102
- ) : null}
103
- </>
104
- )}
105
- </div>
106
- </ScrollArea>
107
- </div>
108
- </>
62
+ {!schemasLoading && !schemasError && selectedAgentData && (
63
+ <>
64
+ {/* Input Schema */}
65
+ {selectedAgentData.schema.input?.code ? (
66
+ <div className="space-y-2">
67
+ <h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
68
+ Input Schema
69
+ </h3>
70
+ <CodeBlock
71
+ code={selectedAgentData.schema.input?.code}
72
+ language="typescript"
73
+ >
74
+ <CodeBlockCopyButton />
75
+ </CodeBlock>
76
+ </div>
77
+ ) : null}
78
+ {/* Output Schema */}
79
+ {selectedAgentData.schema.output?.code ? (
80
+ <div className="space-y-2">
81
+ <h3 className="text-sm font-semibold text-muted-foreground uppercase tracking-wide">
82
+ Output Schema
83
+ </h3>
84
+ <CodeBlock
85
+ code={selectedAgentData.schema.output?.code}
86
+ language="typescript"
87
+ >
88
+ <CodeBlockCopyButton />
89
+ </CodeBlock>
90
+ </div>
91
+ ) : null}
92
+ </>
93
+ )}
94
+ </div>
95
+ </ScrollArea>
96
+ </div>
109
97
  );
110
98
  }
111
99
 
@@ -1,4 +1,4 @@
1
- import React, { createContext, useContext, useEffect, useState } from 'react';
1
+ import React, { createContext, useContext, useEffect, useState, useMemo, useCallback } from 'react';
2
2
  import type { UIMessage } from 'ai';
3
3
  import type { WorkbenchConfig } from '@agentuity/core/workbench';
4
4
  import type { WorkbenchContextType, ConnectionStatus } from '../../types/config';
@@ -25,6 +25,43 @@ interface WorkbenchProviderProps {
25
25
  export function WorkbenchProvider({ config, children }: WorkbenchProviderProps) {
26
26
  const logger = useLogger('WorkbenchProvider');
27
27
 
28
+ // Generate project identifier from config for localStorage scoping
29
+ const projectId = useMemo(() => {
30
+ // Use a combination of baseUrl and apiKey hash to create unique project identifier
31
+ const configHash = btoa(
32
+ JSON.stringify({
33
+ route: config.route,
34
+ apiKey: config.apiKey?.substring(0, 8), // Only first 8 chars for uniqueness without exposing key
35
+ })
36
+ )
37
+ .replace(/[^a-zA-Z0-9]/g, '')
38
+ .substring(0, 16);
39
+ return `project_${configHash}`;
40
+ }, [config]);
41
+
42
+ // localStorage utilities scoped by project
43
+ const getStorageKey = useCallback((key: string) => `agentuity_${projectId}_${key}`, [projectId]);
44
+
45
+ const saveSelectedAgent = useCallback(
46
+ (agentId: string) => {
47
+ try {
48
+ localStorage.setItem(getStorageKey('selected_agent'), agentId);
49
+ } catch (error) {
50
+ console.warn('Failed to save selected agent to localStorage:', error);
51
+ }
52
+ },
53
+ [getStorageKey]
54
+ );
55
+
56
+ const loadSelectedAgent = useCallback((): string | null => {
57
+ try {
58
+ return localStorage.getItem(getStorageKey('selected_agent'));
59
+ } catch (error) {
60
+ console.warn('Failed to load selected agent from localStorage:', error);
61
+ return null;
62
+ }
63
+ }, [getStorageKey]);
64
+
28
65
  const [messages, setMessages] = useState<UIMessage[]>([]);
29
66
  const [selectedAgent, setSelectedAgent] = useState<string>('');
30
67
  const [inputMode, setInputMode] = useState<'text' | 'form'>('text');
@@ -103,15 +140,36 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
103
140
  useEffect(() => {
104
141
  if (agents && Object.keys(agents).length > 0 && !selectedAgent) {
105
142
  logger.debug('🔍 Available agents:', agents);
106
- const sortedAgents = Object.values(agents).sort((a, b) =>
107
- a.metadata.name.localeCompare(b.metadata.name)
108
- );
109
- const firstAgent = sortedAgents[0];
110
- logger.debug('🎯 First agent (alphabetically):', firstAgent);
111
- logger.debug('🆔 Setting selectedAgent to:', firstAgent.metadata.agentId);
112
- setSelectedAgent(firstAgent.metadata.agentId);
143
+
144
+ // Try to load previously selected agent from localStorage
145
+ const savedAgentId = loadSelectedAgent();
146
+ logger.debug('💾 Saved agent from localStorage:', savedAgentId);
147
+
148
+ // Check if saved agent still exists in available agents
149
+ const savedAgent = savedAgentId
150
+ ? Object.values(agents).find((agent) => agent.metadata.agentId === savedAgentId)
151
+ : null;
152
+
153
+ if (savedAgent && savedAgentId) {
154
+ logger.debug('✅ Restoring saved agent:', savedAgent.metadata.name);
155
+ setSelectedAgent(savedAgentId);
156
+ } else {
157
+ // Fallback to first agent alphabetically
158
+ const sortedAgents = Object.values(agents).sort((a, b) =>
159
+ a.metadata.name.localeCompare(b.metadata.name)
160
+ );
161
+ const firstAgent = sortedAgents[0];
162
+ logger.debug(
163
+ '🎯 No saved agent found, using first agent (alphabetically):',
164
+ firstAgent
165
+ );
166
+ logger.debug('🆔 Setting selectedAgent to:', firstAgent.metadata.agentId);
167
+ setSelectedAgent(firstAgent.metadata.agentId);
168
+ // Save this selection for next time
169
+ saveSelectedAgent(firstAgent.metadata.agentId);
170
+ }
113
171
  }
114
- }, [agents, selectedAgent]);
172
+ }, [agents, selectedAgent, loadSelectedAgent, saveSelectedAgent, logger]);
115
173
 
116
174
  // Fetch suggestions from API if endpoint is provided
117
175
  useEffect(() => {
@@ -279,7 +337,8 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
279
337
  const handleAgentSelect = async (agentId: string) => {
280
338
  logger.debug('🔄 handleAgentSelect called with:', agentId);
281
339
  setSelectedAgent(agentId);
282
- // No handlers configured for now
340
+ // Save selection to localStorage for persistence across sessions
341
+ saveSelectedAgent(agentId);
283
342
  };
284
343
 
285
344
  const contextValue: WorkbenchContextType = {
@@ -26,8 +26,8 @@ function InputGroup({ className, ...props }: React.ComponentProps<'div'>) {
26
26
  // Focus state.
27
27
  'has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]',
28
28
 
29
- // Error state.
30
- 'has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40',
29
+ // Error state - use destructive theme color.
30
+ 'has-[[data-slot][aria-invalid=true]]:border-destructive',
31
31
 
32
32
  className
33
33
  )}
@@ -8,9 +8,8 @@ function Input({ className, type, ...props }: React.ComponentProps<'input'>) {
8
8
  type={type}
9
9
  data-slot="input"
10
10
  className={cn(
11
- 'file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
12
- 'focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]',
13
- 'aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive',
11
+ 'border-input file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium focus-visible:ring-[3px] disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
12
+ 'aria-invalid:focus-visible:ring-0',
14
13
  className
15
14
  )}
16
15
  {...props}