@agentuity/workbench 0.0.94 → 0.0.96
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/dist/components/App.d.ts.map +1 -1
- package/dist/components/App.js +3 -1
- package/dist/components/App.js.map +1 -1
- package/dist/components/internal/Chat.d.ts.map +1 -1
- package/dist/components/internal/Chat.js +3 -3
- package/dist/components/internal/Chat.js.map +1 -1
- package/dist/components/internal/InputSection.d.ts +2 -1
- package/dist/components/internal/InputSection.d.ts.map +1 -1
- package/dist/components/internal/InputSection.js +11 -11
- package/dist/components/internal/InputSection.js.map +1 -1
- package/dist/components/internal/MonacoJsonEditor.d.ts.map +1 -1
- package/dist/components/internal/MonacoJsonEditor.js +10 -1
- package/dist/components/internal/MonacoJsonEditor.js.map +1 -1
- package/dist/components/internal/WorkbenchProvider.d.ts +2 -1
- package/dist/components/internal/WorkbenchProvider.d.ts.map +1 -1
- package/dist/components/internal/WorkbenchProvider.js +218 -26
- package/dist/components/internal/WorkbenchProvider.js.map +1 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +4 -3
- package/dist/lib/utils.js.map +1 -1
- package/dist/standalone.css +7 -0
- package/dist/types/config.d.ts +4 -0
- package/dist/types/config.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/components/App.tsx +9 -4
- package/src/components/internal/Chat.tsx +5 -3
- package/src/components/internal/InputSection.tsx +65 -20
- package/src/components/internal/MonacoJsonEditor.tsx +8 -1
- package/src/components/internal/WorkbenchProvider.tsx +260 -26
- package/src/lib/utils.ts +4 -3
- package/src/types/config.ts +5 -0
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
SendIcon,
|
|
7
7
|
Loader2Icon,
|
|
8
8
|
Sparkles,
|
|
9
|
+
Trash2,
|
|
9
10
|
} from 'lucide-react';
|
|
10
11
|
import { MonacoJsonEditor } from './MonacoJsonEditor';
|
|
11
12
|
import {
|
|
@@ -25,12 +26,13 @@ import {
|
|
|
25
26
|
} from '../ui/command';
|
|
26
27
|
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
|
|
27
28
|
import { Select, SelectContent, SelectItem, SelectTrigger } from '../ui/select';
|
|
29
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from '../ui/tooltip';
|
|
28
30
|
import { cn } from '../../lib/utils';
|
|
29
31
|
import type { AgentSchemaData } from '../../hooks/useAgentSchemas';
|
|
30
32
|
import { useLogger } from '../../hooks/useLogger';
|
|
31
33
|
import type { JSONSchema7 } from 'ai';
|
|
34
|
+
import { useWorkbench } from './WorkbenchProvider';
|
|
32
35
|
import { convertJsonSchemaToZod } from 'zod-from-json-schema';
|
|
33
|
-
import { zocker } from 'zocker';
|
|
34
36
|
|
|
35
37
|
export interface InputSectionProps {
|
|
36
38
|
value: string;
|
|
@@ -43,6 +45,7 @@ export interface InputSectionProps {
|
|
|
43
45
|
suggestions: string[];
|
|
44
46
|
isSchemaOpen: boolean;
|
|
45
47
|
onSchemaToggle: () => void;
|
|
48
|
+
clearAgentState?: (agentId: string) => Promise<void>;
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
function isSchemaRootObject(schemaJson?: JSONSchema7): boolean {
|
|
@@ -68,8 +71,10 @@ export function InputSection({
|
|
|
68
71
|
suggestions,
|
|
69
72
|
isSchemaOpen,
|
|
70
73
|
onSchemaToggle,
|
|
74
|
+
clearAgentState,
|
|
71
75
|
}: InputSectionProps) {
|
|
72
76
|
const logger = useLogger('InputSection');
|
|
77
|
+
const { generateSample, isGeneratingSample, isAuthenticated } = useWorkbench();
|
|
73
78
|
const [agentSelectOpen, setAgentSelectOpen] = useState(false);
|
|
74
79
|
const [isValidInput, setIsValidInput] = useState(true);
|
|
75
80
|
const [monacoHasErrors, setMonacoHasErrors] = useState<boolean | null>(null);
|
|
@@ -159,18 +164,14 @@ export function InputSection({
|
|
|
159
164
|
monacoHasErrors,
|
|
160
165
|
]);
|
|
161
166
|
|
|
162
|
-
const handleGenerateSample = () => {
|
|
163
|
-
if (!selectedAgentData?.schema.input?.json || !isObjectSchema) return;
|
|
167
|
+
const handleGenerateSample = async () => {
|
|
168
|
+
if (!selectedAgentData?.schema.input?.json || !isObjectSchema || !selectedAgent) return;
|
|
164
169
|
|
|
165
170
|
try {
|
|
166
|
-
const
|
|
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);
|
|
171
|
+
const sampleJson = await generateSample(selectedAgent);
|
|
172
172
|
onChange(sampleJson);
|
|
173
173
|
} catch (error) {
|
|
174
|
+
logger.error('Failed to generate sample JSON:', error);
|
|
174
175
|
console.error('Failed to generate sample JSON:', error);
|
|
175
176
|
}
|
|
176
177
|
};
|
|
@@ -268,17 +269,49 @@ export function InputSection({
|
|
|
268
269
|
</Select>
|
|
269
270
|
)}
|
|
270
271
|
|
|
271
|
-
{isObjectSchema &&
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
272
|
+
{isObjectSchema &&
|
|
273
|
+
(isAuthenticated ? (
|
|
274
|
+
<Button
|
|
275
|
+
aria-label="Generate Sample JSON"
|
|
276
|
+
size="sm"
|
|
277
|
+
variant="outline"
|
|
278
|
+
className="bg-none font-normal"
|
|
279
|
+
onClick={handleGenerateSample}
|
|
280
|
+
disabled={isGeneratingSample || !isAuthenticated}
|
|
281
|
+
>
|
|
282
|
+
{isGeneratingSample ? (
|
|
283
|
+
<Loader2Icon className="size-4 animate-spin" />
|
|
284
|
+
) : (
|
|
285
|
+
<Sparkles className="size-4" />
|
|
286
|
+
)}{' '}
|
|
287
|
+
Sample
|
|
288
|
+
</Button>
|
|
289
|
+
) : (
|
|
290
|
+
<Tooltip>
|
|
291
|
+
<TooltipTrigger asChild>
|
|
292
|
+
<span className="inline-flex">
|
|
293
|
+
<Button
|
|
294
|
+
aria-label="Generate Sample JSON"
|
|
295
|
+
size="sm"
|
|
296
|
+
variant="outline"
|
|
297
|
+
className="bg-none font-normal"
|
|
298
|
+
onClick={handleGenerateSample}
|
|
299
|
+
disabled={isGeneratingSample || !isAuthenticated}
|
|
300
|
+
>
|
|
301
|
+
{isGeneratingSample ? (
|
|
302
|
+
<Loader2Icon className="size-4 animate-spin" />
|
|
303
|
+
) : (
|
|
304
|
+
<Sparkles className="size-4" />
|
|
305
|
+
)}{' '}
|
|
306
|
+
Sample
|
|
307
|
+
</Button>
|
|
308
|
+
</span>
|
|
309
|
+
</TooltipTrigger>
|
|
310
|
+
<TooltipContent>
|
|
311
|
+
<p>Login to generate a sample</p>
|
|
312
|
+
</TooltipContent>
|
|
313
|
+
</Tooltip>
|
|
314
|
+
))}
|
|
282
315
|
|
|
283
316
|
<Button
|
|
284
317
|
aria-label={isSchemaOpen ? 'Hide Schema' : 'View Schema'}
|
|
@@ -289,6 +322,18 @@ export function InputSection({
|
|
|
289
322
|
>
|
|
290
323
|
<FileJson className="size-4" /> Schema
|
|
291
324
|
</Button>
|
|
325
|
+
|
|
326
|
+
{clearAgentState && selectedAgent && (
|
|
327
|
+
<Button
|
|
328
|
+
aria-label="Clear conversation history"
|
|
329
|
+
size="sm"
|
|
330
|
+
variant="outline"
|
|
331
|
+
className="bg-none font-normal text-muted-foreground hover:text-destructive"
|
|
332
|
+
onClick={() => clearAgentState(selectedAgent)}
|
|
333
|
+
>
|
|
334
|
+
<Trash2 className="size-4" /> Clear
|
|
335
|
+
</Button>
|
|
336
|
+
)}
|
|
292
337
|
</div>
|
|
293
338
|
|
|
294
339
|
<PromptInput onSubmit={onSubmit} className="px-3 pb-3">
|
|
@@ -167,7 +167,8 @@ export function MonacoJsonEditor({
|
|
|
167
167
|
style={{ minHeight: '64px', maxHeight: '192px', height: `${editorHeight}px` }}
|
|
168
168
|
>
|
|
169
169
|
<Editor
|
|
170
|
-
|
|
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) =>
|
|
@@ -22,10 +22,11 @@ interface WorkbenchProviderProps {
|
|
|
22
22
|
baseUrl?: string | null;
|
|
23
23
|
projectId?: string;
|
|
24
24
|
};
|
|
25
|
+
isAuthenticated: boolean;
|
|
25
26
|
children: React.ReactNode;
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
export function WorkbenchProvider({ config, children }: WorkbenchProviderProps) {
|
|
29
|
+
export function WorkbenchProvider({ config, isAuthenticated, children }: WorkbenchProviderProps) {
|
|
29
30
|
const logger = useLogger('WorkbenchProvider');
|
|
30
31
|
|
|
31
32
|
// localStorage utilities scoped by project
|
|
@@ -54,10 +55,51 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
|
|
|
54
55
|
}
|
|
55
56
|
}, [getStorageKey]);
|
|
56
57
|
|
|
58
|
+
const saveThreadId = useCallback(
|
|
59
|
+
(threadId: string) => {
|
|
60
|
+
try {
|
|
61
|
+
localStorage.setItem(getStorageKey('thread_id'), threadId);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
console.warn('Failed to save thread id to localStorage:', error);
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
[getStorageKey]
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const loadThreadId = useCallback((): string | null => {
|
|
70
|
+
try {
|
|
71
|
+
return localStorage.getItem(getStorageKey('thread_id'));
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.warn('Failed to load thread id from localStorage:', error);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}, [getStorageKey]);
|
|
77
|
+
|
|
78
|
+
const applyThreadIdHeader = useCallback(
|
|
79
|
+
(headers: Record<string, string>) => {
|
|
80
|
+
const threadId = loadThreadId();
|
|
81
|
+
if (threadId) {
|
|
82
|
+
headers['x-thread-id'] = threadId;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
[loadThreadId]
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const persistThreadIdFromResponse = useCallback(
|
|
89
|
+
(response: Response) => {
|
|
90
|
+
const threadId = response.headers.get('x-thread-id');
|
|
91
|
+
if (threadId) {
|
|
92
|
+
saveThreadId(threadId);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
[saveThreadId]
|
|
96
|
+
);
|
|
97
|
+
|
|
57
98
|
const [messages, setMessages] = useState<UIMessage[]>([]);
|
|
58
99
|
const [selectedAgent, setSelectedAgent] = useState<string>('');
|
|
59
100
|
const [inputMode, setInputMode] = useState<'text' | 'form'>('text');
|
|
60
101
|
const [isLoading, setIsLoading] = useState(false);
|
|
102
|
+
const [isGeneratingSample, setIsGeneratingSample] = useState(false);
|
|
61
103
|
const [connectionStatus, setConnectionStatus] = useState<ConnectionStatus>('connected'); // Default to connected when websocket is disabled
|
|
62
104
|
|
|
63
105
|
// Config values
|
|
@@ -148,25 +190,111 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
|
|
|
148
190
|
|
|
149
191
|
const [suggestions, _setSuggestions] = useState<string[]>([]);
|
|
150
192
|
|
|
193
|
+
// Fetch state for an agent
|
|
194
|
+
const fetchAgentState = useCallback(
|
|
195
|
+
async (agentId: string) => {
|
|
196
|
+
if (!baseUrl) {
|
|
197
|
+
logger.debug('⚠️ No baseUrl configured, skipping state fetch');
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (!agentId) {
|
|
202
|
+
logger.debug('⚠️ No agentId provided, skipping state fetch');
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const headers: Record<string, string> = {};
|
|
208
|
+
if (apiKey) {
|
|
209
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
210
|
+
}
|
|
211
|
+
applyThreadIdHeader(headers);
|
|
212
|
+
|
|
213
|
+
const url = `${baseUrl}/_agentuity/workbench/state?agentId=${encodeURIComponent(agentId)}`;
|
|
214
|
+
logger.debug('📡 Fetching state for agent:', agentId);
|
|
215
|
+
const response = await fetch(url, {
|
|
216
|
+
method: 'GET',
|
|
217
|
+
headers,
|
|
218
|
+
credentials: 'include',
|
|
219
|
+
});
|
|
220
|
+
persistThreadIdFromResponse(response);
|
|
221
|
+
|
|
222
|
+
if (response.ok) {
|
|
223
|
+
const data = await response.json();
|
|
224
|
+
const stateMessages = (data.messages || []) as Array<{
|
|
225
|
+
type: 'input' | 'output';
|
|
226
|
+
data: unknown;
|
|
227
|
+
}>;
|
|
228
|
+
|
|
229
|
+
// Convert state messages to UIMessage format
|
|
230
|
+
// Use stable IDs based on message index to prevent unnecessary re-renders
|
|
231
|
+
const uiMessages: UIMessage[] = stateMessages.map((msg, index) => {
|
|
232
|
+
const text =
|
|
233
|
+
typeof msg.data === 'object'
|
|
234
|
+
? JSON.stringify(msg.data, null, 2)
|
|
235
|
+
: String(msg.data);
|
|
236
|
+
// Use stable ID based on index and a hash of content to maintain identity
|
|
237
|
+
const contentHash = text.substring(0, 20).replace(/[^a-zA-Z0-9]/g, '');
|
|
238
|
+
return {
|
|
239
|
+
id: `state_${agentId}_${index}_${contentHash}`,
|
|
240
|
+
role: msg.type === 'input' ? 'user' : 'assistant',
|
|
241
|
+
parts: [{ type: 'text', text }],
|
|
242
|
+
};
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
setMessages(uiMessages);
|
|
246
|
+
logger.debug('✅ Loaded state messages:', uiMessages.length);
|
|
247
|
+
} else {
|
|
248
|
+
logger.debug('⚠️ Failed to fetch state, starting with empty messages');
|
|
249
|
+
setMessages([]);
|
|
250
|
+
}
|
|
251
|
+
} catch (error) {
|
|
252
|
+
logger.debug('⚠️ Error fetching state:', error);
|
|
253
|
+
setMessages([]);
|
|
254
|
+
}
|
|
255
|
+
},
|
|
256
|
+
[baseUrl, apiKey, logger, applyThreadIdHeader, persistThreadIdFromResponse]
|
|
257
|
+
);
|
|
258
|
+
|
|
151
259
|
// Set initial agent selection
|
|
152
260
|
useEffect(() => {
|
|
153
261
|
if (agents && Object.keys(agents).length > 0 && !selectedAgent) {
|
|
154
262
|
logger.debug('🔍 Available agents:', agents);
|
|
155
263
|
|
|
156
|
-
//
|
|
157
|
-
const
|
|
158
|
-
|
|
264
|
+
// First, check for agent query parameter in URL
|
|
265
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
266
|
+
const agentFromUrl = urlParams.get('agent');
|
|
267
|
+
logger.debug('🔗 Agent from URL query param:', agentFromUrl);
|
|
268
|
+
|
|
269
|
+
// Try to find agent by URL param (matches agentId only)
|
|
270
|
+
let agentToSelect: string | null = null;
|
|
271
|
+
if (agentFromUrl) {
|
|
272
|
+
const matchedAgent = Object.values(agents).find(
|
|
273
|
+
(agent) => agent.metadata.agentId === agentFromUrl
|
|
274
|
+
);
|
|
275
|
+
if (matchedAgent) {
|
|
276
|
+
logger.debug('✅ Found agent from URL param:', matchedAgent.metadata.name);
|
|
277
|
+
agentToSelect = matchedAgent.metadata.agentId;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// If no URL param match, try localStorage
|
|
282
|
+
if (!agentToSelect) {
|
|
283
|
+
const savedAgentId = loadSelectedAgent();
|
|
284
|
+
logger.debug('💾 Saved agent from localStorage:', savedAgentId);
|
|
159
285
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
: null;
|
|
286
|
+
const savedAgent = savedAgentId
|
|
287
|
+
? Object.values(agents).find((agent) => agent.metadata.agentId === savedAgentId)
|
|
288
|
+
: null;
|
|
164
289
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
290
|
+
if (savedAgent && savedAgentId) {
|
|
291
|
+
logger.debug('✅ Restoring saved agent:', savedAgent.metadata.name);
|
|
292
|
+
agentToSelect = savedAgentId;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Fallback to first agent alphabetically
|
|
297
|
+
if (!agentToSelect) {
|
|
170
298
|
const sortedAgents = Object.values(agents).sort((a, b) =>
|
|
171
299
|
a.metadata.name.localeCompare(b.metadata.name)
|
|
172
300
|
);
|
|
@@ -175,13 +303,15 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
|
|
|
175
303
|
'🎯 No saved agent found, using first agent (alphabetically):',
|
|
176
304
|
firstAgent
|
|
177
305
|
);
|
|
178
|
-
|
|
179
|
-
setSelectedAgent(firstAgent.metadata.agentId);
|
|
180
|
-
// Save this selection for next time
|
|
181
|
-
saveSelectedAgent(firstAgent.metadata.agentId);
|
|
306
|
+
agentToSelect = firstAgent.metadata.agentId;
|
|
182
307
|
}
|
|
308
|
+
|
|
309
|
+
logger.debug('🆔 Setting selectedAgent to:', agentToSelect);
|
|
310
|
+
setSelectedAgent(agentToSelect);
|
|
311
|
+
saveSelectedAgent(agentToSelect);
|
|
312
|
+
fetchAgentState(agentToSelect);
|
|
183
313
|
}
|
|
184
|
-
}, [agents, selectedAgent, loadSelectedAgent, saveSelectedAgent, logger]);
|
|
314
|
+
}, [agents, selectedAgent, loadSelectedAgent, saveSelectedAgent, logger, fetchAgentState]);
|
|
185
315
|
|
|
186
316
|
// Fetch suggestions from API if endpoint is provided
|
|
187
317
|
useEffect(() => {
|
|
@@ -212,23 +342,33 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
|
|
|
212
342
|
logger.debug('✅ Validation passed, continuing with message submission...');
|
|
213
343
|
|
|
214
344
|
// Add user message
|
|
345
|
+
// Note: We also add a placeholder assistant message so only the last message
|
|
346
|
+
// shows a loading state while the request is in-flight.
|
|
347
|
+
const now = Date.now();
|
|
215
348
|
const displayText = hasInputSchema
|
|
216
349
|
? value
|
|
217
350
|
: `Running ${selectedAgentData?.metadata.name || 'agent'}...`;
|
|
218
351
|
const userMessage: UIMessage = {
|
|
219
|
-
id:
|
|
352
|
+
id: now.toString(),
|
|
220
353
|
role: 'user',
|
|
221
354
|
parts: [{ type: 'text', text: displayText }],
|
|
222
355
|
};
|
|
223
356
|
|
|
224
|
-
|
|
357
|
+
const assistantMessageId = (now + 1).toString();
|
|
358
|
+
const placeholderAssistantMessage: UIMessage = {
|
|
359
|
+
id: assistantMessageId,
|
|
360
|
+
role: 'assistant',
|
|
361
|
+
parts: [{ type: 'text', text: '', state: 'streaming' }],
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
setMessages((prev) => [...prev, userMessage, placeholderAssistantMessage]);
|
|
225
365
|
setIsLoading(true);
|
|
226
366
|
|
|
227
367
|
logger.debug('🔗 baseUrl:', baseUrl, 'isBaseUrlNull:', isBaseUrlNull);
|
|
228
368
|
if (!baseUrl || isBaseUrlNull) {
|
|
229
369
|
logger.debug('❌ Message submission blocked - baseUrl is null or missing');
|
|
230
370
|
const errorMessage: UIMessage = {
|
|
231
|
-
id:
|
|
371
|
+
id: assistantMessageId,
|
|
232
372
|
role: 'assistant',
|
|
233
373
|
parts: [
|
|
234
374
|
{
|
|
@@ -237,7 +377,7 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
|
|
|
237
377
|
},
|
|
238
378
|
],
|
|
239
379
|
};
|
|
240
|
-
setMessages((prev) =>
|
|
380
|
+
setMessages((prev) => prev.map((m) => (m.id === assistantMessageId ? errorMessage : m)));
|
|
241
381
|
setIsLoading(false);
|
|
242
382
|
return;
|
|
243
383
|
}
|
|
@@ -264,6 +404,7 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
|
|
|
264
404
|
if (apiKey) {
|
|
265
405
|
headers.Authorization = `Bearer ${apiKey}`;
|
|
266
406
|
}
|
|
407
|
+
applyThreadIdHeader(headers);
|
|
267
408
|
|
|
268
409
|
const controller = new AbortController();
|
|
269
410
|
const timeoutId = setTimeout(() => controller.abort(), 30000); // 30s timeout
|
|
@@ -281,7 +422,9 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
|
|
|
281
422
|
headers,
|
|
282
423
|
body: JSON.stringify(requestPayload),
|
|
283
424
|
signal: controller.signal,
|
|
425
|
+
credentials: 'include',
|
|
284
426
|
});
|
|
427
|
+
persistThreadIdFromResponse(response);
|
|
285
428
|
clearTimeout(timeoutId);
|
|
286
429
|
|
|
287
430
|
if (!response.ok) {
|
|
@@ -320,14 +463,16 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
|
|
|
320
463
|
typeof result === 'object' ? JSON.stringify(result, null, 2) : String(result);
|
|
321
464
|
|
|
322
465
|
const assistantMessage: UIMessage & { tokens?: string; duration?: string } = {
|
|
323
|
-
id:
|
|
466
|
+
id: assistantMessageId,
|
|
324
467
|
role: 'assistant',
|
|
325
468
|
parts: [{ type: 'text', text: resultText }],
|
|
326
469
|
tokens: totalTokens?.toString(),
|
|
327
470
|
duration,
|
|
328
471
|
};
|
|
329
472
|
|
|
330
|
-
setMessages((prev) =>
|
|
473
|
+
setMessages((prev) =>
|
|
474
|
+
prev.map((m) => (m.id === assistantMessageId ? assistantMessage : m))
|
|
475
|
+
);
|
|
331
476
|
} catch (fetchError) {
|
|
332
477
|
clearTimeout(timeoutId);
|
|
333
478
|
throw fetchError;
|
|
@@ -342,7 +487,7 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
|
|
|
342
487
|
: 'Sorry, I encountered an error processing your message.';
|
|
343
488
|
|
|
344
489
|
const errorMessage: UIMessage = {
|
|
345
|
-
id:
|
|
490
|
+
id: assistantMessageId,
|
|
346
491
|
role: 'assistant',
|
|
347
492
|
parts: [
|
|
348
493
|
{
|
|
@@ -351,19 +496,103 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
|
|
|
351
496
|
},
|
|
352
497
|
],
|
|
353
498
|
};
|
|
354
|
-
setMessages((prev) =>
|
|
499
|
+
setMessages((prev) => prev.map((m) => (m.id === assistantMessageId ? errorMessage : m)));
|
|
355
500
|
} finally {
|
|
356
501
|
setIsLoading(false);
|
|
357
502
|
}
|
|
358
503
|
};
|
|
359
504
|
|
|
505
|
+
const generateSample = async (agentId: string): Promise<string> => {
|
|
506
|
+
if (!baseUrl || isBaseUrlNull) {
|
|
507
|
+
throw new Error('Base URL not configured');
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
setIsGeneratingSample(true);
|
|
511
|
+
try {
|
|
512
|
+
const url = `${baseUrl}/_agentuity/workbench/sample?agentId=${encodeURIComponent(agentId)}`;
|
|
513
|
+
const headers: HeadersInit = {
|
|
514
|
+
'Content-Type': 'application/json',
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
if (apiKey) {
|
|
518
|
+
headers['Authorization'] = `Bearer ${apiKey}`;
|
|
519
|
+
}
|
|
520
|
+
// Keep thread id stable across workbench endpoints.
|
|
521
|
+
if (typeof headers === 'object' && headers && !Array.isArray(headers)) {
|
|
522
|
+
applyThreadIdHeader(headers as Record<string, string>);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const response = await fetch(url, {
|
|
526
|
+
method: 'GET',
|
|
527
|
+
headers,
|
|
528
|
+
credentials: 'include',
|
|
529
|
+
});
|
|
530
|
+
persistThreadIdFromResponse(response);
|
|
531
|
+
|
|
532
|
+
if (!response.ok) {
|
|
533
|
+
let errorMessage = `Request failed with status ${response.status}`;
|
|
534
|
+
try {
|
|
535
|
+
const errorData = await response.json();
|
|
536
|
+
errorMessage = errorData.error || errorData.message || errorMessage;
|
|
537
|
+
} catch {
|
|
538
|
+
errorMessage = response.statusText || errorMessage;
|
|
539
|
+
}
|
|
540
|
+
throw new Error(errorMessage);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const sample = await response.json();
|
|
544
|
+
return JSON.stringify(sample, null, 2);
|
|
545
|
+
} catch (error) {
|
|
546
|
+
logger.error('Failed to generate sample JSON:', error);
|
|
547
|
+
throw error;
|
|
548
|
+
} finally {
|
|
549
|
+
setIsGeneratingSample(false);
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
|
|
360
553
|
const handleAgentSelect = async (agentId: string) => {
|
|
361
554
|
logger.debug('🔄 handleAgentSelect called with:', agentId);
|
|
362
555
|
setSelectedAgent(agentId);
|
|
363
556
|
// Save selection to localStorage for persistence across sessions
|
|
364
557
|
saveSelectedAgent(agentId);
|
|
558
|
+
// Fetch state for the selected agent
|
|
559
|
+
await fetchAgentState(agentId);
|
|
365
560
|
};
|
|
366
561
|
|
|
562
|
+
const clearAgentState = useCallback(
|
|
563
|
+
async (agentId: string) => {
|
|
564
|
+
if (!baseUrl) {
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
try {
|
|
569
|
+
const headers: Record<string, string> = {};
|
|
570
|
+
if (apiKey) {
|
|
571
|
+
headers.Authorization = `Bearer ${apiKey}`;
|
|
572
|
+
}
|
|
573
|
+
applyThreadIdHeader(headers);
|
|
574
|
+
|
|
575
|
+
const url = `${baseUrl}/_agentuity/workbench/state?agentId=${encodeURIComponent(agentId)}`;
|
|
576
|
+
const response = await fetch(url, {
|
|
577
|
+
method: 'DELETE',
|
|
578
|
+
headers,
|
|
579
|
+
credentials: 'include',
|
|
580
|
+
});
|
|
581
|
+
persistThreadIdFromResponse(response);
|
|
582
|
+
|
|
583
|
+
if (response.ok) {
|
|
584
|
+
setMessages([]);
|
|
585
|
+
logger.debug('✅ Cleared state for agent:', agentId);
|
|
586
|
+
} else {
|
|
587
|
+
logger.debug('⚠️ Failed to clear state');
|
|
588
|
+
}
|
|
589
|
+
} catch (error) {
|
|
590
|
+
logger.debug('⚠️ Error clearing state:', error);
|
|
591
|
+
}
|
|
592
|
+
},
|
|
593
|
+
[baseUrl, apiKey, logger, applyThreadIdHeader, persistThreadIdFromResponse]
|
|
594
|
+
);
|
|
595
|
+
|
|
367
596
|
const contextValue: WorkbenchContextType = {
|
|
368
597
|
config,
|
|
369
598
|
agents: agents || {},
|
|
@@ -376,6 +605,9 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
|
|
|
376
605
|
setInputMode,
|
|
377
606
|
isLoading: isLoading || !!schemasLoading,
|
|
378
607
|
submitMessage,
|
|
608
|
+
generateSample,
|
|
609
|
+
isGeneratingSample,
|
|
610
|
+
isAuthenticated,
|
|
379
611
|
// Schema data from API
|
|
380
612
|
schemas: schemaData,
|
|
381
613
|
schemasLoading: !!schemasLoading,
|
|
@@ -383,6 +615,8 @@ export function WorkbenchProvider({ config, children }: WorkbenchProviderProps)
|
|
|
383
615
|
refetchSchemas,
|
|
384
616
|
// Connection status
|
|
385
617
|
connectionStatus,
|
|
618
|
+
// Clear agent state
|
|
619
|
+
clearAgentState,
|
|
386
620
|
};
|
|
387
621
|
|
|
388
622
|
return <WorkbenchContext.Provider value={contextValue}>{children}</WorkbenchContext.Provider>;
|
package/src/lib/utils.ts
CHANGED
|
@@ -40,12 +40,13 @@ export function getTotalTokens(tokens: Record<string, number>): number {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
export const getProcessEnv = (key: string): string | undefined => {
|
|
43
|
-
|
|
44
|
-
return process.env[key];
|
|
45
|
-
}
|
|
43
|
+
// Prioritize import.meta.env for browser/Vite environments
|
|
46
44
|
if (typeof import.meta.env !== 'undefined') {
|
|
47
45
|
return import.meta.env[key];
|
|
48
46
|
}
|
|
47
|
+
if (typeof process !== 'undefined' && process.env) {
|
|
48
|
+
return process.env[key];
|
|
49
|
+
}
|
|
49
50
|
return undefined;
|
|
50
51
|
};
|
|
51
52
|
|
package/src/types/config.ts
CHANGED
|
@@ -24,6 +24,9 @@ export interface WorkbenchContextType {
|
|
|
24
24
|
setInputMode: (mode: 'text' | 'form') => void;
|
|
25
25
|
isLoading: boolean;
|
|
26
26
|
submitMessage: (value: string, mode?: 'text' | 'form') => Promise<void>;
|
|
27
|
+
generateSample: (agentId: string) => Promise<string>;
|
|
28
|
+
isGeneratingSample: boolean;
|
|
29
|
+
isAuthenticated: boolean;
|
|
27
30
|
// Schema data from API
|
|
28
31
|
schemas: AgentSchemasResponse | null;
|
|
29
32
|
schemasLoading: boolean;
|
|
@@ -31,4 +34,6 @@ export interface WorkbenchContextType {
|
|
|
31
34
|
refetchSchemas: () => void;
|
|
32
35
|
// Connection status
|
|
33
36
|
connectionStatus: ConnectionStatus;
|
|
37
|
+
// Clear agent state
|
|
38
|
+
clearAgentState: (agentId: string) => Promise<void>;
|
|
34
39
|
}
|