@agentuity/workbench 0.0.80 → 0.0.85

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.
@@ -0,0 +1,359 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import Editor, { type Monaco, type OnMount } from '@monaco-editor/react';
3
+ import { useTheme } from '../ui/theme-provider';
4
+ import { bundledThemes } from 'shiki';
5
+ import type { JSONSchema7 } from 'ai';
6
+
7
+ interface MonacoJsonEditorProps {
8
+ value: string;
9
+ onChange: (value: string) => void;
10
+ schema?: JSONSchema7;
11
+ schemaUri?: string;
12
+ className?: string;
13
+ }
14
+
15
+ // Convert color value to valid hex for Monaco
16
+ function normalizeColorForMonaco(color: string | undefined, isDark: boolean): string {
17
+ if (!color) return isDark ? 'abb2bf' : '383a42'; // Default foreground colors
18
+
19
+ // Remove # prefix if present
20
+ let normalized = color.replace('#', '');
21
+
22
+ // Handle common color names that might appear in themes
23
+ const colorMap: Record<string, string> = {
24
+ white: isDark ? 'ffffff' : '383a42',
25
+ black: isDark ? '000000' : 'abb2bf',
26
+ red: 'e45649',
27
+ green: '50a14f',
28
+ blue: '4078f2',
29
+ yellow: '986801',
30
+ cyan: '0184bc',
31
+ magenta: 'a626a4',
32
+ };
33
+
34
+ if (colorMap[normalized.toLowerCase()]) {
35
+ normalized = colorMap[normalized.toLowerCase()];
36
+ }
37
+
38
+ // Validate it's a proper hex color (3 or 6 characters)
39
+ if (!/^[0-9a-fA-F]{3}$|^[0-9a-fA-F]{6}$/.test(normalized)) {
40
+ return isDark ? 'abb2bf' : '383a42'; // Fallback to default
41
+ }
42
+
43
+ return normalized;
44
+ }
45
+
46
+ // Convert Shiki theme to Monaco theme
47
+ function convertShikiToMonaco(
48
+ shikiTheme: {
49
+ colors?: Record<string, string>;
50
+ tokenColors?: Array<{
51
+ scope?: string | string[];
52
+ settings?: { foreground?: string; fontStyle?: string };
53
+ }>;
54
+ },
55
+ themeName: string
56
+ ) {
57
+ const colors = shikiTheme.colors || {};
58
+ const tokenColors = shikiTheme.tokenColors || [];
59
+ const isDark = themeName.includes('dark');
60
+
61
+ // Convert token colors to Monaco rules
62
+ const rules: Array<{ token: string; foreground: string; fontStyle?: string }> = [];
63
+ tokenColors.forEach((tokenColor) => {
64
+ if (tokenColor.scope && tokenColor.settings?.foreground) {
65
+ const scopes = Array.isArray(tokenColor.scope) ? tokenColor.scope : [tokenColor.scope];
66
+ scopes.forEach((scope: string) => {
67
+ // Map common scopes to Monaco tokens
68
+ let token = scope;
69
+ if (scope.includes('string.quoted.double.json')) token = 'string.value.json';
70
+ if (scope.includes('support.type.property-name.json')) token = 'string.key.json';
71
+ if (scope.includes('constant.numeric.json')) token = 'number.json';
72
+ if (scope.includes('constant.language.json')) token = 'keyword.json';
73
+ if (scope.includes('punctuation.definition.string.json'))
74
+ token = 'delimiter.bracket.json';
75
+
76
+ const normalizedColor = normalizeColorForMonaco(
77
+ tokenColor.settings?.foreground,
78
+ isDark
79
+ );
80
+
81
+ rules.push({
82
+ token,
83
+ foreground: normalizedColor,
84
+ fontStyle: tokenColor.settings?.fontStyle || undefined,
85
+ });
86
+ });
87
+ }
88
+ });
89
+
90
+ return {
91
+ base: isDark ? 'vs-dark' : 'vs',
92
+ inherit: true,
93
+ rules,
94
+ colors: {
95
+ 'editor.background': '#00000000', // Always transparent
96
+ 'editor.foreground': normalizeColorForMonaco(colors['editor.foreground'], isDark),
97
+ },
98
+ };
99
+ }
100
+
101
+ export function MonacoJsonEditor({
102
+ value,
103
+ onChange,
104
+ schema,
105
+ schemaUri = 'agentuity://schema/default',
106
+ className = '',
107
+ }: MonacoJsonEditorProps) {
108
+ const { theme } = useTheme();
109
+ const [editorInstance, setEditorInstance] = useState<Parameters<OnMount>[0] | null>(null);
110
+ const [monacoInstance, setMonacoInstance] = useState<Monaco | null>(null);
111
+ const [editorHeight, setEditorHeight] = useState(120);
112
+
113
+ // Get resolved theme (similar to useTheme's resolvedTheme from next-themes)
114
+ const resolvedTheme = React.useMemo(() => {
115
+ if (theme === 'system') {
116
+ return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
117
+ }
118
+ return theme;
119
+ }, [theme]);
120
+
121
+ // Configure JSON schema when schema or monacoInstance changes
122
+ useEffect(() => {
123
+ if (!monacoInstance || !schema) return;
124
+
125
+ const schemaObject = typeof schema === 'string' ? JSON.parse(schema) : schema;
126
+
127
+ // Configure Monaco JSON language support for schema validation
128
+ monacoInstance.languages.json.jsonDefaults.setDiagnosticsOptions({
129
+ validate: true,
130
+ schemas: [
131
+ {
132
+ uri: schemaUri,
133
+ fileMatch: ['*'],
134
+ schema: schemaObject,
135
+ },
136
+ ],
137
+ });
138
+ }, [monacoInstance, schema, schemaUri]);
139
+
140
+ // Handle theme changes for existing editor instance
141
+ useEffect(() => {
142
+ if (editorInstance && monacoInstance) {
143
+ editorInstance.updateOptions({
144
+ theme: resolvedTheme === 'light' ? 'custom-light' : 'custom-dark',
145
+ });
146
+ }
147
+ }, [resolvedTheme, editorInstance, monacoInstance]);
148
+
149
+ return (
150
+ <div
151
+ 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
+ style={{ minHeight: '64px', maxHeight: '192px', height: `${editorHeight}px` }}
153
+ >
154
+ <Editor
155
+ value={value || '{}'}
156
+ onChange={(newValue) => onChange(newValue || '')}
157
+ language="json"
158
+ theme={resolvedTheme === 'light' ? 'custom-light' : 'custom-dark'}
159
+ height="100%"
160
+ options={{
161
+ minimap: { enabled: false },
162
+ lineNumbers: 'off',
163
+ folding: false,
164
+ scrollBeyondLastLine: false,
165
+ wordWrap: 'on',
166
+ renderLineHighlight: 'none',
167
+ overviewRulerBorder: false,
168
+ overviewRulerLanes: 0,
169
+ hideCursorInOverviewRuler: true,
170
+ fixedOverflowWidgets: true,
171
+ roundedSelection: false,
172
+ occurrencesHighlight: 'off',
173
+ selectionHighlight: false,
174
+ renderWhitespace: 'none',
175
+ fontSize: 14,
176
+ fontWeight: '400',
177
+ formatOnPaste: true,
178
+ formatOnType: true,
179
+ autoIndent: 'full',
180
+ glyphMargin: false,
181
+ lineDecorationsWidth: 0,
182
+ lineNumbersMinChars: 0,
183
+ automaticLayout: true,
184
+ scrollbar: {
185
+ vertical: 'auto',
186
+ horizontal: 'auto',
187
+ verticalScrollbarSize: 10,
188
+ horizontalScrollbarSize: 10,
189
+ // Disable scroll shadows
190
+ verticalHasArrows: false,
191
+ horizontalHasArrows: false,
192
+ },
193
+ padding: { top: 12, bottom: 12 },
194
+ // Additional background transparency options
195
+ renderValidationDecorations: 'off',
196
+ guides: {
197
+ indentation: false,
198
+ highlightActiveIndentation: false,
199
+ },
200
+ // Disable sticky scroll feature
201
+ stickyScroll: { enabled: false },
202
+ // Disable scroll decorations/shadows
203
+ scrollBeyondLastColumn: 0,
204
+ renderLineHighlightOnlyWhenFocus: true,
205
+ }}
206
+ onMount={(editor, monaco) => {
207
+ setEditorInstance(editor);
208
+ setMonacoInstance(monaco);
209
+ editor.focus();
210
+
211
+ // Auto-resize based on content
212
+ const updateHeight = () => {
213
+ const contentHeight = editor.getContentHeight();
214
+ const maxHeight = 192; // max-h-48 = 12rem = 192px
215
+ const minHeight = 64; // min-h-16 = 4rem = 64px
216
+ const newHeight = Math.min(Math.max(contentHeight + 24, minHeight), maxHeight);
217
+ setEditorHeight(newHeight);
218
+
219
+ // Layout after height change
220
+ setTimeout(() => editor.layout(), 0);
221
+ };
222
+
223
+ // Update height on content changes
224
+ editor.onDidChangeModelContent(updateHeight);
225
+
226
+ // Initial height update
227
+ setTimeout(updateHeight, 0);
228
+
229
+ // Ensure background transparency and remove shadows
230
+ setTimeout(() => {
231
+ const editorElement = editor.getDomNode();
232
+ if (editorElement) {
233
+ // Set transparent backgrounds on all relevant elements
234
+ const elementsToMakeTransparent = [
235
+ '.monaco-editor',
236
+ '.monaco-editor .monaco-editor-background',
237
+ '.monaco-editor .view-lines',
238
+ '.monaco-editor .margin',
239
+ '.monaco-editor .monaco-scrollable-element',
240
+ '.monaco-editor .overflow-guard',
241
+ '.view-overlays',
242
+ '.decorationsOverviewRuler',
243
+ ];
244
+
245
+ elementsToMakeTransparent.forEach((selector) => {
246
+ const element = editorElement.querySelector(selector);
247
+ if (element) {
248
+ (element as HTMLElement).style.backgroundColor = 'transparent';
249
+ (element as HTMLElement).style.boxShadow = 'none';
250
+ }
251
+ });
252
+
253
+ // Remove scroll shadows specifically - target the exact classes
254
+ const shadowTop = editorElement.querySelector(
255
+ '.monaco-scrollable-element > .shadow.top'
256
+ );
257
+ if (shadowTop) {
258
+ (shadowTop as HTMLElement).style.display = 'none';
259
+ }
260
+
261
+ const scrollDecorations = editorElement.querySelectorAll(
262
+ '.monaco-editor .scroll-decoration, .scroll-decoration'
263
+ );
264
+ scrollDecorations.forEach((decoration) => {
265
+ (decoration as HTMLElement).style.display = 'none';
266
+ });
267
+
268
+ const scrollableElement = editorElement.querySelector(
269
+ '.monaco-scrollable-element'
270
+ );
271
+ if (scrollableElement) {
272
+ (scrollableElement as HTMLElement).style.setProperty(
273
+ '--scroll-shadow',
274
+ 'none'
275
+ );
276
+ (scrollableElement as HTMLElement).style.setProperty(
277
+ 'box-shadow',
278
+ 'none',
279
+ 'important'
280
+ );
281
+ }
282
+
283
+ // Also set transparent and remove shadow on the editor element itself
284
+ (editorElement as HTMLElement).style.backgroundColor = 'transparent';
285
+ (editorElement as HTMLElement).style.boxShadow = 'none';
286
+ }
287
+ }, 0);
288
+ }}
289
+ beforeMount={async (monaco) => {
290
+ setMonacoInstance(monaco);
291
+
292
+ try {
293
+ // Try to use actual Shiki themes
294
+ const oneLightThemeModule = await bundledThemes['one-light']();
295
+ const oneDarkProThemeModule = await bundledThemes['one-dark-pro']();
296
+
297
+ if (oneLightThemeModule?.default) {
298
+ const lightMonacoTheme = convertShikiToMonaco(
299
+ oneLightThemeModule.default,
300
+ 'one-light'
301
+ );
302
+ monaco.editor.defineTheme('custom-light', lightMonacoTheme);
303
+ }
304
+
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
+ }
355
+ }}
356
+ />
357
+ </div>
358
+ );
359
+ }
@@ -14,7 +14,8 @@ export interface SchemaProps {
14
14
  export function Schema({ open, onOpenChange }: SchemaProps) {
15
15
  const { agents, selectedAgent, schemasLoading, schemasError } = useWorkbench();
16
16
 
17
- const selectedAgentData = Object.values(agents).find(agent => agent.metadata.agentId === selectedAgent) || null;
17
+ const selectedAgentData =
18
+ Object.values(agents).find((agent) => agent.metadata.agentId === selectedAgent) || null;
18
19
 
19
20
  return (
20
21
  <>
@@ -103,8 +103,11 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
103
103
  useEffect(() => {
104
104
  if (agents && Object.keys(agents).length > 0 && !selectedAgent) {
105
105
  logger.debug('🔍 Available agents:', agents);
106
- const firstAgent = Object.values(agents)[0];
107
- logger.debug('🎯 First agent:', firstAgent);
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);
108
111
  logger.debug('🆔 Setting selectedAgent to:', firstAgent.metadata.agentId);
109
112
  setSelectedAgent(firstAgent.metadata.agentId);
110
113
  }
@@ -123,7 +126,9 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
123
126
  if (!selectedAgent) return;
124
127
 
125
128
  logger.debug('🚀 Submitting message with selectedAgent:', selectedAgent);
126
- const selectedAgentData = agents ? Object.values(agents).find(agent => agent.metadata.agentId === selectedAgent) : undefined;
129
+ const selectedAgentData = agents
130
+ ? Object.values(agents).find((agent) => agent.metadata.agentId === selectedAgent)
131
+ : undefined;
127
132
  logger.debug('📊 Found selectedAgentData:', selectedAgentData);
128
133
  const hasInputSchema = selectedAgentData?.schema?.input?.json;
129
134
  logger.debug('📝 hasInputSchema:', hasInputSchema, 'value:', value);
package/src/index.ts CHANGED
@@ -16,4 +16,5 @@ export { Input } from './components/ui/input';
16
16
  // Export components
17
17
  export { default as App } from './components/App';
18
18
  export { default as Inline } from './components/Inline';
19
+ export { MonacoJsonEditor } from './components/internal/MonacoJsonEditor';
19
20
  export { createWorkbench } from './workbench';