worksona-js 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +296 -0
- package/agents/interviewer-agent.json +24 -0
- package/agents/legal-agent.json +24 -0
- package/agents/marketing-agent.json +24 -0
- package/agents/prd-editor-agent.json +21 -0
- package/agents/research-analyst.json +24 -0
- package/package.json +82 -0
- package/worksona.d.ts +147 -0
- package/worksona.js +2241 -0
- package/worksona.min.js +1 -0
package/worksona.js
ADDED
|
@@ -0,0 +1,2241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Worksona.js - LLM Agent Management API
|
|
3
|
+
* Version: 0.1.2
|
|
4
|
+
*
|
|
5
|
+
* A lightweight, single-file solution for deploying and managing AI agents
|
|
6
|
+
* with distinct personalities across multiple LLM providers.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
// Agent class for managing agent state and history
|
|
12
|
+
class Agent {
|
|
13
|
+
constructor(config) {
|
|
14
|
+
this.id = config.id;
|
|
15
|
+
this.name = config.name;
|
|
16
|
+
this.description = config.description;
|
|
17
|
+
|
|
18
|
+
// Normalize config to prevent nesting problems
|
|
19
|
+
if (config.config) {
|
|
20
|
+
// If there's a nested config, flatten it
|
|
21
|
+
this.config = {
|
|
22
|
+
...config,
|
|
23
|
+
...config.config,
|
|
24
|
+
};
|
|
25
|
+
// Remove the nested config to avoid circular structure
|
|
26
|
+
delete this.config.config;
|
|
27
|
+
} else {
|
|
28
|
+
this.config = config;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Store system prompt and examples directly
|
|
32
|
+
this.systemPrompt = this.config.systemPrompt;
|
|
33
|
+
this.examples = this.config.examples || [];
|
|
34
|
+
|
|
35
|
+
// If traits are in config, move them to top level
|
|
36
|
+
if (this.config.traits) {
|
|
37
|
+
this.traits = this.config.traits;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
this.transactions = [];
|
|
41
|
+
this.metrics = {
|
|
42
|
+
totalQueries: 0,
|
|
43
|
+
avgResponseTime: 0,
|
|
44
|
+
lastActive: null,
|
|
45
|
+
successRate: 1.0,
|
|
46
|
+
errorCount: 0
|
|
47
|
+
};
|
|
48
|
+
this.state = {
|
|
49
|
+
isActive: true,
|
|
50
|
+
currentProvider: this.config.provider || 'openai',
|
|
51
|
+
currentModel: this.config.model,
|
|
52
|
+
lastError: null
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
addTransaction(transaction) {
|
|
57
|
+
this.transactions.push(transaction);
|
|
58
|
+
this.metrics.totalQueries++;
|
|
59
|
+
this.metrics.lastActive = new Date();
|
|
60
|
+
|
|
61
|
+
// Update average response time
|
|
62
|
+
if (transaction.duration) {
|
|
63
|
+
const totalTime = this.metrics.avgResponseTime * (this.metrics.totalQueries - 1) + transaction.duration;
|
|
64
|
+
this.metrics.avgResponseTime = totalTime / this.metrics.totalQueries;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Update success rate if there was an error
|
|
68
|
+
if (transaction.error) {
|
|
69
|
+
this.metrics.errorCount++;
|
|
70
|
+
this.metrics.successRate = (this.metrics.totalQueries - this.metrics.errorCount) / this.metrics.totalQueries;
|
|
71
|
+
this.state.lastError = transaction.error;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Keep only last 100 transactions to manage memory
|
|
75
|
+
if (this.transactions.length > 100) {
|
|
76
|
+
this.transactions = this.transactions.slice(-100);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
getHistory() {
|
|
81
|
+
return this.transactions;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getMetrics() {
|
|
85
|
+
return this.metrics;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getState() {
|
|
89
|
+
return this.state;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
(function(global) {
|
|
94
|
+
'use strict';
|
|
95
|
+
|
|
96
|
+
class Worksona {
|
|
97
|
+
constructor(options = {}) {
|
|
98
|
+
this.options = {
|
|
99
|
+
debug: false,
|
|
100
|
+
defaultProvider: 'openai',
|
|
101
|
+
defaultModel: 'gpt-3.5-turbo',
|
|
102
|
+
apiKeys: {},
|
|
103
|
+
...options
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
this.agents = new Map();
|
|
107
|
+
this.activeProvider = null;
|
|
108
|
+
this.controlPanelId = null;
|
|
109
|
+
this.eventHandlers = {};
|
|
110
|
+
|
|
111
|
+
this._initializeProviders();
|
|
112
|
+
|
|
113
|
+
// Initialize control panel if enabled
|
|
114
|
+
if (options.controlPanel !== false) {
|
|
115
|
+
// Create floating control panel by default
|
|
116
|
+
this.createFloatingControlPanel();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Initialize API clients for different providers
|
|
121
|
+
_initializeProviders() {
|
|
122
|
+
this.providers = {
|
|
123
|
+
openai: this.options.apiKeys.openai ? {
|
|
124
|
+
chat: async (agent, message) => {
|
|
125
|
+
try {
|
|
126
|
+
// Determine if this is a vision request
|
|
127
|
+
const isVisionRequest = message.content && message.content.type === 'image';
|
|
128
|
+
const modelName = (agent.config.model || this.options.defaultModel || 'gpt-4o').trim();
|
|
129
|
+
|
|
130
|
+
this._log(`Making OpenAI request with model: ${modelName}`, 'info');
|
|
131
|
+
|
|
132
|
+
let messages;
|
|
133
|
+
// Determine if this is a vision request - check both formats for backward compatibility
|
|
134
|
+
if (isVisionRequest || (message && message.type === 'image') || (message && message.content && typeof message.content === 'object' && message.content.type === 'image')) {
|
|
135
|
+
// Format vision-specific message
|
|
136
|
+
// Handle different possible formats for image data
|
|
137
|
+
let imageData;
|
|
138
|
+
|
|
139
|
+
if (isVisionRequest) {
|
|
140
|
+
imageData = message.content;
|
|
141
|
+
} else if (message.type === 'image') {
|
|
142
|
+
imageData = message;
|
|
143
|
+
} else if (message.content && message.content.type === 'image') {
|
|
144
|
+
imageData = message.content;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Ensure we have valid image data
|
|
148
|
+
if (!imageData || !imageData.imageUrl) {
|
|
149
|
+
throw new Error('Invalid image data: Missing required imageUrl property');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
messages = [
|
|
153
|
+
{
|
|
154
|
+
role: 'system',
|
|
155
|
+
content: agent.config.systemPrompt || 'You are a helpful vision analysis assistant.'
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
role: 'user',
|
|
159
|
+
content: [
|
|
160
|
+
{
|
|
161
|
+
type: 'text',
|
|
162
|
+
text: imageData.prompt || 'Please analyze this image.'
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
type: 'image_url',
|
|
166
|
+
image_url: {
|
|
167
|
+
url: imageData.imageUrl,
|
|
168
|
+
detail: imageData.detail || 'high' // 'auto', 'low', or 'high'
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
// Log the message structure for debugging
|
|
176
|
+
this._log(`Vision message structure: ${JSON.stringify(messages)}`, 'info');
|
|
177
|
+
} else {
|
|
178
|
+
// Standard chat message format
|
|
179
|
+
// Ensure we have a valid message content
|
|
180
|
+
let userContent = '';
|
|
181
|
+
|
|
182
|
+
if (typeof message === 'string') {
|
|
183
|
+
userContent = message;
|
|
184
|
+
} else if (message && typeof message.content === 'string') {
|
|
185
|
+
userContent = message.content;
|
|
186
|
+
} else if (message && message.content) {
|
|
187
|
+
// If content is an object, convert it to a string
|
|
188
|
+
try {
|
|
189
|
+
userContent = JSON.stringify(message.content);
|
|
190
|
+
} catch (e) {
|
|
191
|
+
userContent = 'Unable to process message content';
|
|
192
|
+
}
|
|
193
|
+
} else if (message) {
|
|
194
|
+
// If message is an object but doesn't have content property
|
|
195
|
+
try {
|
|
196
|
+
userContent = JSON.stringify(message);
|
|
197
|
+
} catch (e) {
|
|
198
|
+
userContent = 'Unable to process message';
|
|
199
|
+
}
|
|
200
|
+
} else {
|
|
201
|
+
userContent = 'No message provided';
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Log the processed content for debugging
|
|
205
|
+
this._log(`Processed user content: ${userContent.substring(0, 100)}${userContent.length > 100 ? '...' : ''}`, 'info');
|
|
206
|
+
|
|
207
|
+
messages = [
|
|
208
|
+
{
|
|
209
|
+
role: 'system',
|
|
210
|
+
content: agent.config.systemPrompt || 'You are a helpful assistant.'
|
|
211
|
+
},
|
|
212
|
+
...(agent.config.examples || []).flatMap(ex => [
|
|
213
|
+
{ role: 'user', content: ex.user },
|
|
214
|
+
{ role: 'assistant', content: ex.assistant }
|
|
215
|
+
]),
|
|
216
|
+
{
|
|
217
|
+
role: 'user',
|
|
218
|
+
content: userContent
|
|
219
|
+
}
|
|
220
|
+
];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const requestBody = {
|
|
224
|
+
model: modelName,
|
|
225
|
+
messages: messages,
|
|
226
|
+
temperature: agent.config.temperature || 0.7,
|
|
227
|
+
max_tokens: agent.config.maxTokens || 500,
|
|
228
|
+
top_p: agent.config.topP || 1,
|
|
229
|
+
frequency_penalty: agent.config.frequencyPenalty || 0,
|
|
230
|
+
presence_penalty: agent.config.presencePenalty || 0,
|
|
231
|
+
stream: false
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
// Use the appropriate API endpoint based on the model
|
|
235
|
+
const apiEndpoint = isVisionRequest ?
|
|
236
|
+
'https://api.openai.com/v1/chat/completions' :
|
|
237
|
+
'https://api.openai.com/v1/chat/completions';
|
|
238
|
+
|
|
239
|
+
const response = await fetch(apiEndpoint, {
|
|
240
|
+
method: 'POST',
|
|
241
|
+
headers: {
|
|
242
|
+
'Content-Type': 'application/json',
|
|
243
|
+
'Authorization': `Bearer ${this.options.apiKeys.openai}`,
|
|
244
|
+
'OpenAI-Organization': agent.config.organization || ''
|
|
245
|
+
},
|
|
246
|
+
body: JSON.stringify(requestBody)
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const data = await response.json();
|
|
250
|
+
|
|
251
|
+
if (!response.ok) {
|
|
252
|
+
this._log(`OpenAI API error: ${JSON.stringify(data)}`, 'error');
|
|
253
|
+
throw new Error(data.error?.message || `OpenAI API error: ${response.status}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Log successful vision processing
|
|
257
|
+
if (isVisionRequest) {
|
|
258
|
+
this._log(`Successfully processed vision request with model: ${modelName}`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return data.choices[0].message.content;
|
|
262
|
+
} catch (error) {
|
|
263
|
+
this._log(`OpenAI error details: ${error.message}`, 'error');
|
|
264
|
+
this._handleError(error, 'PROVIDER_ERROR', 'OpenAI request failed');
|
|
265
|
+
}
|
|
266
|
+
},
|
|
267
|
+
defaultModels: {
|
|
268
|
+
chat: 'gpt-4o',
|
|
269
|
+
vision: 'gpt-4o'
|
|
270
|
+
}
|
|
271
|
+
} : null,
|
|
272
|
+
|
|
273
|
+
anthropic: this.options.apiKeys.anthropic ? {
|
|
274
|
+
chat: async (agent, message) => {
|
|
275
|
+
try {
|
|
276
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
277
|
+
method: 'POST',
|
|
278
|
+
headers: {
|
|
279
|
+
'Content-Type': 'application/json',
|
|
280
|
+
'x-api-key': this.options.apiKeys.anthropic,
|
|
281
|
+
'anthropic-version': '2023-06-01'
|
|
282
|
+
},
|
|
283
|
+
body: JSON.stringify({
|
|
284
|
+
model: agent.config.model || 'claude-3-opus-20240229',
|
|
285
|
+
max_tokens: agent.config.maxTokens || 500,
|
|
286
|
+
temperature: agent.config.temperature || 0.7,
|
|
287
|
+
system: agent.config.systemPrompt,
|
|
288
|
+
messages: [
|
|
289
|
+
...(agent.config.examples || []).flatMap(ex => [
|
|
290
|
+
{ role: 'user', content: ex.user },
|
|
291
|
+
{ role: 'assistant', content: ex.assistant }
|
|
292
|
+
]),
|
|
293
|
+
{ role: 'user', content: message }
|
|
294
|
+
],
|
|
295
|
+
top_p: agent.config.topP || 1,
|
|
296
|
+
top_k: agent.config.topK || 50,
|
|
297
|
+
metadata: {
|
|
298
|
+
user_id: agent.id
|
|
299
|
+
}
|
|
300
|
+
})
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const data = await response.json();
|
|
304
|
+
if (!response.ok) throw new Error(data.error?.message || 'Anthropic API error');
|
|
305
|
+
return data.content[0].text;
|
|
306
|
+
} catch (error) {
|
|
307
|
+
this._handleError(error, 'PROVIDER_ERROR', 'Anthropic request failed');
|
|
308
|
+
}
|
|
309
|
+
},
|
|
310
|
+
defaultModels: {
|
|
311
|
+
chat: 'claude-3-opus-20240229',
|
|
312
|
+
completion: 'claude-3-sonnet-20240229'
|
|
313
|
+
}
|
|
314
|
+
} : null,
|
|
315
|
+
|
|
316
|
+
google: this.options.apiKeys.google ? {
|
|
317
|
+
chat: async (agent, message) => {
|
|
318
|
+
try {
|
|
319
|
+
const response = await fetch(`https://generativelanguage.googleapis.com/v1beta/models/${agent.config.model || 'gemini-pro'}:generateContent?key=${this.options.apiKeys.google}`, {
|
|
320
|
+
method: 'POST',
|
|
321
|
+
headers: {
|
|
322
|
+
'Content-Type': 'application/json'
|
|
323
|
+
},
|
|
324
|
+
body: JSON.stringify({
|
|
325
|
+
contents: [
|
|
326
|
+
{
|
|
327
|
+
role: 'user',
|
|
328
|
+
parts: [
|
|
329
|
+
{
|
|
330
|
+
text: agent.config.systemPrompt
|
|
331
|
+
}
|
|
332
|
+
]
|
|
333
|
+
},
|
|
334
|
+
...(agent.config.examples || []).flatMap(ex => [
|
|
335
|
+
{
|
|
336
|
+
role: 'user',
|
|
337
|
+
parts: [{ text: ex.user }]
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
role: 'model',
|
|
341
|
+
parts: [{ text: ex.assistant }]
|
|
342
|
+
}
|
|
343
|
+
]),
|
|
344
|
+
{
|
|
345
|
+
role: 'user',
|
|
346
|
+
parts: [{ text: message }]
|
|
347
|
+
}
|
|
348
|
+
],
|
|
349
|
+
generationConfig: {
|
|
350
|
+
temperature: agent.config.temperature || 0.7,
|
|
351
|
+
maxOutputTokens: agent.config.maxTokens || 500,
|
|
352
|
+
topP: agent.config.topP || 1,
|
|
353
|
+
topK: agent.config.topK || 40,
|
|
354
|
+
candidateCount: 1
|
|
355
|
+
},
|
|
356
|
+
safetySettings: [
|
|
357
|
+
{
|
|
358
|
+
category: 'HARM_CATEGORY_HARASSMENT',
|
|
359
|
+
threshold: 'BLOCK_MEDIUM_AND_ABOVE'
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
category: 'HARM_CATEGORY_HATE_SPEECH',
|
|
363
|
+
threshold: 'BLOCK_MEDIUM_AND_ABOVE'
|
|
364
|
+
}
|
|
365
|
+
]
|
|
366
|
+
})
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
const data = await response.json();
|
|
370
|
+
if (!response.ok) throw new Error(data.error?.message || 'Google API error');
|
|
371
|
+
return data.candidates[0].content.parts[0].text;
|
|
372
|
+
} catch (error) {
|
|
373
|
+
this._handleError(error, 'PROVIDER_ERROR', 'Google request failed');
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
defaultModels: {
|
|
377
|
+
chat: 'gemini-pro',
|
|
378
|
+
vision: 'gemini-pro-vision'
|
|
379
|
+
}
|
|
380
|
+
} : null
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Helper function to format messages based on provider
|
|
385
|
+
_formatMessages(provider, agent, message) {
|
|
386
|
+
switch (provider) {
|
|
387
|
+
case 'openai':
|
|
388
|
+
return [
|
|
389
|
+
{ role: 'system', content: agent.config.systemPrompt },
|
|
390
|
+
...(agent.config.examples || []).flatMap(ex => [
|
|
391
|
+
{ role: 'user', content: ex.user },
|
|
392
|
+
{ role: 'assistant', content: ex.assistant }
|
|
393
|
+
]),
|
|
394
|
+
{ role: 'user', content: message }
|
|
395
|
+
];
|
|
396
|
+
|
|
397
|
+
case 'anthropic':
|
|
398
|
+
return [
|
|
399
|
+
...(agent.config.examples || []).flatMap(ex => [
|
|
400
|
+
{ role: 'user', content: ex.user },
|
|
401
|
+
{ role: 'assistant', content: ex.assistant }
|
|
402
|
+
]),
|
|
403
|
+
{ role: 'user', content: message }
|
|
404
|
+
];
|
|
405
|
+
|
|
406
|
+
case 'google':
|
|
407
|
+
return [
|
|
408
|
+
{
|
|
409
|
+
role: 'user',
|
|
410
|
+
parts: [{ text: agent.config.systemPrompt }]
|
|
411
|
+
},
|
|
412
|
+
...(agent.config.examples || []).flatMap(ex => [
|
|
413
|
+
{
|
|
414
|
+
role: 'user',
|
|
415
|
+
parts: [{ text: ex.user }]
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
role: 'model',
|
|
419
|
+
parts: [{ text: ex.assistant }]
|
|
420
|
+
}
|
|
421
|
+
]),
|
|
422
|
+
{
|
|
423
|
+
role: 'user',
|
|
424
|
+
parts: [{ text: message }]
|
|
425
|
+
}
|
|
426
|
+
];
|
|
427
|
+
|
|
428
|
+
default:
|
|
429
|
+
throw new Error(`Unsupported provider: ${provider}`);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
// Load an agent from configuration
|
|
434
|
+
async loadAgent(config) {
|
|
435
|
+
if (!config.id || !config.name) {
|
|
436
|
+
this._handleError(new Error('Invalid agent configuration'), 'CONFIG_ERROR');
|
|
437
|
+
return null;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
try {
|
|
441
|
+
// Create new agent instance - Agent constructor now handles nested config
|
|
442
|
+
const agent = new Agent(config);
|
|
443
|
+
|
|
444
|
+
// Store agent
|
|
445
|
+
this.agents.set(agent.id, agent);
|
|
446
|
+
|
|
447
|
+
// Emit event
|
|
448
|
+
this._emit('agent-loaded', {
|
|
449
|
+
agentId: agent.id,
|
|
450
|
+
name: agent.name,
|
|
451
|
+
description: agent.description,
|
|
452
|
+
provider: agent.state.currentProvider,
|
|
453
|
+
model: agent.state.currentModel
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
// Update the control panel to show the new agent
|
|
457
|
+
this.updateControlPanel();
|
|
458
|
+
|
|
459
|
+
this._log(`Agent loaded: ${agent.name} (${agent.id})`);
|
|
460
|
+
return agent;
|
|
461
|
+
} catch (error) {
|
|
462
|
+
this._handleError(error, 'AGENT_LOAD_ERROR', `Failed to load agent: ${config.id}`);
|
|
463
|
+
return null;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// Send message to agent and get response
|
|
468
|
+
async chat(agentId, message, options = {}) {
|
|
469
|
+
const agent = this.agents.get(agentId);
|
|
470
|
+
if (!agent) {
|
|
471
|
+
this._handleError(new Error(`Agent not found: ${agentId}`), 'AGENT_NOT_FOUND');
|
|
472
|
+
return null;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Always use the agent's configured provider first, then fallback to options or default
|
|
476
|
+
const provider = agent.config.provider || options.provider || this.options.defaultProvider;
|
|
477
|
+
|
|
478
|
+
if (!this.providers[provider]) {
|
|
479
|
+
this._handleError(new Error(`Provider not available: ${provider}`), 'PROVIDER_ERROR');
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Update agent state with current provider and model
|
|
484
|
+
agent.state.currentProvider = provider;
|
|
485
|
+
agent.state.currentModel = agent.config.model || this.options.defaultModel;
|
|
486
|
+
|
|
487
|
+
// Create transaction record
|
|
488
|
+
const transaction = {
|
|
489
|
+
timestamp: new Date(),
|
|
490
|
+
query: message,
|
|
491
|
+
response: null,
|
|
492
|
+
duration: 0,
|
|
493
|
+
error: null,
|
|
494
|
+
provider,
|
|
495
|
+
model: agent.state.currentModel
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
this._emit('chat-start', { agentId, message });
|
|
499
|
+
this._log(`Chat request to ${agentId}: ${message}`);
|
|
500
|
+
|
|
501
|
+
const startTime = Date.now();
|
|
502
|
+
try {
|
|
503
|
+
const response = await this.providers[provider].chat(agent, message);
|
|
504
|
+
transaction.duration = Date.now() - startTime;
|
|
505
|
+
transaction.response = response;
|
|
506
|
+
|
|
507
|
+
// Add transaction to agent history
|
|
508
|
+
agent.addTransaction(transaction);
|
|
509
|
+
|
|
510
|
+
// Update control panel to reflect the new transaction
|
|
511
|
+
this.updateControlPanel();
|
|
512
|
+
|
|
513
|
+
this._emit('chat-complete', {
|
|
514
|
+
agentId,
|
|
515
|
+
message,
|
|
516
|
+
response,
|
|
517
|
+
duration: transaction.duration
|
|
518
|
+
});
|
|
519
|
+
|
|
520
|
+
this._log(`Chat response from ${agentId}: ${response}`);
|
|
521
|
+
return response;
|
|
522
|
+
} catch (error) {
|
|
523
|
+
transaction.error = error;
|
|
524
|
+
transaction.duration = Date.now() - startTime;
|
|
525
|
+
|
|
526
|
+
// Add failed transaction to history
|
|
527
|
+
agent.addTransaction(transaction);
|
|
528
|
+
|
|
529
|
+
// Update control panel to reflect the failed transaction
|
|
530
|
+
this.updateControlPanel();
|
|
531
|
+
|
|
532
|
+
this._handleError(error, 'CHAT_ERROR', `Chat failed with ${agentId}`);
|
|
533
|
+
return null;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Get agent history
|
|
538
|
+
getAgentHistory(agentId) {
|
|
539
|
+
const agent = this.agents.get(agentId);
|
|
540
|
+
return agent ? agent.getHistory() : [];
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// Get agent metrics
|
|
544
|
+
getAgentMetrics(agentId) {
|
|
545
|
+
const agent = this.agents.get(agentId);
|
|
546
|
+
return agent ? agent.getMetrics() : null;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Get agent state
|
|
550
|
+
getAgentState(agentId) {
|
|
551
|
+
const agent = this.agents.get(agentId);
|
|
552
|
+
return agent ? agent.getState() : null;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// Get agent by ID
|
|
556
|
+
getAgent(agentId) {
|
|
557
|
+
return this.agents.get(agentId);
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Get all loaded agents
|
|
561
|
+
getAllAgents() {
|
|
562
|
+
return Array.from(this.agents.values());
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// Remove agent by ID
|
|
566
|
+
removeAgent(agentId) {
|
|
567
|
+
const removed = this.agents.delete(agentId);
|
|
568
|
+
if (removed) {
|
|
569
|
+
this._emit('agent-removed', agentId);
|
|
570
|
+
this._log(`Agent removed: ${agentId}`);
|
|
571
|
+
|
|
572
|
+
// Update the control panel to reflect the removed agent
|
|
573
|
+
this.updateControlPanel();
|
|
574
|
+
}
|
|
575
|
+
return removed;
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Event handling
|
|
579
|
+
on(event, handler) {
|
|
580
|
+
if (!this.eventHandlers[event]) {
|
|
581
|
+
this.eventHandlers[event] = [];
|
|
582
|
+
}
|
|
583
|
+
this.eventHandlers[event].push(handler);
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
off(event, handler) {
|
|
587
|
+
if (this.eventHandlers[event]) {
|
|
588
|
+
this.eventHandlers[event] = this.eventHandlers[event].filter(h => h !== handler);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
_emit(event, data) {
|
|
593
|
+
if (this.eventHandlers[event]) {
|
|
594
|
+
this.eventHandlers[event].forEach(handler => handler(data));
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Error handling
|
|
599
|
+
_handleError(error, code, message) {
|
|
600
|
+
let errorMessage = message || error.message;
|
|
601
|
+
|
|
602
|
+
switch (code) {
|
|
603
|
+
case 'IMAGE_PROCESSING_ERROR':
|
|
604
|
+
if (error.message.includes('image_too_large')) {
|
|
605
|
+
errorMessage = 'Image size exceeds maximum allowed size. Please reduce the image size.';
|
|
606
|
+
} else if (error.message.includes('invalid_image_format')) {
|
|
607
|
+
errorMessage = 'Invalid image format. Supported formats are: JPEG, PNG, WEBP, GIF.';
|
|
608
|
+
} else if (error.message.includes('vision_model_unavailable')) {
|
|
609
|
+
errorMessage = 'Vision model is currently unavailable. Please try again later.';
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// OpenAI-specific errors
|
|
613
|
+
if (error.message.includes('invalid_api_key')) {
|
|
614
|
+
errorMessage = 'Invalid OpenAI API key. Please check your credentials.';
|
|
615
|
+
} else if (error.message.includes('insufficient_quota')) {
|
|
616
|
+
errorMessage = 'OpenAI API quota exceeded. Please check your usage limits.';
|
|
617
|
+
} else if (error.message.includes('rate_limit_exceeded')) {
|
|
618
|
+
errorMessage = 'OpenAI API rate limit exceeded. Please try again later.';
|
|
619
|
+
}
|
|
620
|
+
break;
|
|
621
|
+
case 'PROVIDER_ERROR':
|
|
622
|
+
if (error.message && error.message.includes('401')) {
|
|
623
|
+
errorMessage = 'API key is invalid or missing. Please check your API key in the control panel.';
|
|
624
|
+
} else if (error.message && error.message.includes('invalid_api_key')) {
|
|
625
|
+
errorMessage = 'Invalid API key format. Please check your API key in the control panel.';
|
|
626
|
+
} else if (error.message && error.message.includes('model')) {
|
|
627
|
+
errorMessage = `Model error: ${error.message}. Please try a different model in the control panel.`;
|
|
628
|
+
}
|
|
629
|
+
break;
|
|
630
|
+
case 'CONFIG_ERROR':
|
|
631
|
+
errorMessage = 'Invalid agent configuration. Please check your agent configuration and try again.';
|
|
632
|
+
break;
|
|
633
|
+
case 'AGENT_LOAD_ERROR':
|
|
634
|
+
errorMessage = `Failed to load agent: ${error.message}. Please check the agent configuration and try again.`;
|
|
635
|
+
break;
|
|
636
|
+
case 'AGENT_NOT_FOUND':
|
|
637
|
+
errorMessage = `Agent not found: ${error.message}. Please check the agent ID and try again.`;
|
|
638
|
+
break;
|
|
639
|
+
case 'CHAT_ERROR':
|
|
640
|
+
errorMessage = `Chat failed with ${error.message}. Please check the chat request and try again.`;
|
|
641
|
+
break;
|
|
642
|
+
default:
|
|
643
|
+
errorMessage = error.message || 'An unknown error occurred. Please try again later.';
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const worksonaError = {
|
|
647
|
+
message: errorMessage,
|
|
648
|
+
code,
|
|
649
|
+
originalError: error
|
|
650
|
+
};
|
|
651
|
+
this._emit('error', worksonaError);
|
|
652
|
+
this._log(`Error [${code}]: ${worksonaError.message}`, 'error');
|
|
653
|
+
throw worksonaError;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Logging
|
|
657
|
+
_log(message, level = 'info') {
|
|
658
|
+
if (this.options.debug) {
|
|
659
|
+
console[level](`[Worksona] ${message}`);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Create control panel for development
|
|
664
|
+
createControlPanel(containerId) {
|
|
665
|
+
this.controlPanelId = containerId;
|
|
666
|
+
const container = document.getElementById(containerId);
|
|
667
|
+
if (!container) {
|
|
668
|
+
this._log('Control panel container not found', 'error');
|
|
669
|
+
return false;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Store the container reference for other methods to use
|
|
673
|
+
this.controlPanelContainer = container;
|
|
674
|
+
|
|
675
|
+
// Create basic structure
|
|
676
|
+
container.innerHTML = `
|
|
677
|
+
<div class="worksona-control-panel">
|
|
678
|
+
<div class="worksona-panel-header">
|
|
679
|
+
<h2>Worksona Agents Control Panel</h2>
|
|
680
|
+
<div class="worksona-header-buttons">
|
|
681
|
+
<button class="worksona-expand-button" title="Toggle full screen">⛶</button>
|
|
682
|
+
<button class="worksona-close-button">×</button>
|
|
683
|
+
</div>
|
|
684
|
+
</div>
|
|
685
|
+
|
|
686
|
+
<div class="worksona-llm-status-bar">
|
|
687
|
+
<div class="worksona-status-label">LLM Status</div>
|
|
688
|
+
<div class="worksona-status-indicators">
|
|
689
|
+
<div class="worksona-status-item">
|
|
690
|
+
<div class="worksona-status-dot" id="worksona-openai-status"></div>
|
|
691
|
+
<span>OpenAI</span>
|
|
692
|
+
</div>
|
|
693
|
+
<div class="worksona-status-item">
|
|
694
|
+
<div class="worksona-status-dot" id="worksona-anthropic-status"></div>
|
|
695
|
+
<span>Anthropic</span>
|
|
696
|
+
</div>
|
|
697
|
+
<div class="worksona-status-item">
|
|
698
|
+
<div class="worksona-status-dot" id="worksona-google-status"></div>
|
|
699
|
+
<span>Google</span>
|
|
700
|
+
</div>
|
|
701
|
+
</div>
|
|
702
|
+
</div>
|
|
703
|
+
|
|
704
|
+
<div class="worksona-tabs">
|
|
705
|
+
<button class="worksona-tab" data-tab="api-keys">API Keys</button>
|
|
706
|
+
<button class="worksona-tab active" data-tab="agents">Agents</button>
|
|
707
|
+
</div>
|
|
708
|
+
|
|
709
|
+
<div class="worksona-content">
|
|
710
|
+
<div class="worksona-tab-content" id="worksona-api-keys-tab">
|
|
711
|
+
<h3 class="worksona-section-title">LLM Provider API Keys</h3>
|
|
712
|
+
|
|
713
|
+
<div class="worksona-key-input">
|
|
714
|
+
<label for="worksona-openai-key">OpenAI API Key</label>
|
|
715
|
+
<div class="worksona-input-group">
|
|
716
|
+
<input type="password" id="worksona-openai-key" placeholder="sk-...">
|
|
717
|
+
<button class="worksona-toggle-visibility">👁️</button>
|
|
718
|
+
</div>
|
|
719
|
+
</div>
|
|
720
|
+
|
|
721
|
+
<div class="worksona-key-input">
|
|
722
|
+
<label for="worksona-anthropic-key">Anthropic API Key</label>
|
|
723
|
+
<div class="worksona-input-group">
|
|
724
|
+
<input type="password" id="worksona-anthropic-key" placeholder="sk-ant-...">
|
|
725
|
+
<button class="worksona-toggle-visibility">👁️</button>
|
|
726
|
+
</div>
|
|
727
|
+
</div>
|
|
728
|
+
|
|
729
|
+
<div class="worksona-key-input">
|
|
730
|
+
<label for="worksona-google-key">Google API Key</label>
|
|
731
|
+
<div class="worksona-input-group">
|
|
732
|
+
<input type="password" id="worksona-google-key" placeholder="AIza...">
|
|
733
|
+
<button class="worksona-toggle-visibility">👁️</button>
|
|
734
|
+
</div>
|
|
735
|
+
</div>
|
|
736
|
+
|
|
737
|
+
<div class="worksona-button-group">
|
|
738
|
+
<button id="worksona-save-keys" class="worksona-primary-button">Save API Keys</button>
|
|
739
|
+
<button id="worksona-test-connections" class="worksona-secondary-button">Test Connections</button>
|
|
740
|
+
</div>
|
|
741
|
+
</div>
|
|
742
|
+
|
|
743
|
+
<div class="worksona-tab-content active" id="worksona-agents-tab">
|
|
744
|
+
<div id="worksona-agent-list"></div>
|
|
745
|
+
</div>
|
|
746
|
+
</div>
|
|
747
|
+
</div>
|
|
748
|
+
`;
|
|
749
|
+
|
|
750
|
+
// Add styles
|
|
751
|
+
const styles = `
|
|
752
|
+
.worksona-control-panel {
|
|
753
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
754
|
+
color: #333;
|
|
755
|
+
background: white;
|
|
756
|
+
border-radius: 8px;
|
|
757
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.15);
|
|
758
|
+
overflow: hidden;
|
|
759
|
+
width: 100%;
|
|
760
|
+
max-width: 800px;
|
|
761
|
+
margin: 0 auto;
|
|
762
|
+
position: relative;
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
.worksona-panel-header {
|
|
766
|
+
display: flex;
|
|
767
|
+
justify-content: space-between;
|
|
768
|
+
align-items: center;
|
|
769
|
+
padding: 15px 20px;
|
|
770
|
+
border-bottom: 1px solid #eee;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
.worksona-panel-header h2 {
|
|
774
|
+
margin: 0;
|
|
775
|
+
color: #1a56db;
|
|
776
|
+
font-size: 18px;
|
|
777
|
+
font-weight: 600;
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
.worksona-header-buttons {
|
|
781
|
+
display: flex;
|
|
782
|
+
gap: 10px;
|
|
783
|
+
align-items: center;
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
.worksona-expand-button {
|
|
787
|
+
background: none;
|
|
788
|
+
border: none;
|
|
789
|
+
font-size: 20px;
|
|
790
|
+
cursor: pointer;
|
|
791
|
+
color: #64748b;
|
|
792
|
+
padding: 4px;
|
|
793
|
+
display: flex;
|
|
794
|
+
align-items: center;
|
|
795
|
+
justify-content: center;
|
|
796
|
+
transition: color 0.2s;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
.worksona-expand-button:hover {
|
|
800
|
+
color: #334155;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
.worksona-llm-status-bar {
|
|
804
|
+
display: flex;
|
|
805
|
+
justify-content: space-between;
|
|
806
|
+
align-items: center;
|
|
807
|
+
padding: 10px 20px;
|
|
808
|
+
background: #111827;
|
|
809
|
+
color: white;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
.worksona-status-indicators {
|
|
813
|
+
display: flex;
|
|
814
|
+
gap: 20px;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
.worksona-status-item {
|
|
818
|
+
display: flex;
|
|
819
|
+
align-items: center;
|
|
820
|
+
gap: 5px;
|
|
821
|
+
font-size: 14px;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
.worksona-status-dot {
|
|
825
|
+
width: 8px;
|
|
826
|
+
height: 8px;
|
|
827
|
+
border-radius: 50%;
|
|
828
|
+
background: #6b7280;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
.worksona-status-dot.active {
|
|
832
|
+
background: #10b981;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
.worksona-tabs {
|
|
836
|
+
display: flex;
|
|
837
|
+
padding: 0 20px;
|
|
838
|
+
background: #f9fafb;
|
|
839
|
+
border-bottom: 1px solid #e5e7eb;
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
.worksona-tab {
|
|
843
|
+
padding: 15px 20px;
|
|
844
|
+
background: none;
|
|
845
|
+
border: none;
|
|
846
|
+
cursor: pointer;
|
|
847
|
+
color: #6b7280;
|
|
848
|
+
font-weight: 500;
|
|
849
|
+
position: relative;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
.worksona-tab:hover {
|
|
853
|
+
color: #4b5563;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
.worksona-tab.active {
|
|
857
|
+
color: #2563eb;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
.worksona-tab.active::after {
|
|
861
|
+
content: '';
|
|
862
|
+
position: absolute;
|
|
863
|
+
bottom: 0;
|
|
864
|
+
left: 0;
|
|
865
|
+
right: 0;
|
|
866
|
+
height: 2px;
|
|
867
|
+
background: #2563eb;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
.worksona-content {
|
|
871
|
+
padding: 20px;
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
.worksona-tab-content {
|
|
875
|
+
display: none;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
.worksona-tab-content.active {
|
|
879
|
+
display: block;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
.worksona-section-title {
|
|
883
|
+
font-size: 16px;
|
|
884
|
+
font-weight: 600;
|
|
885
|
+
margin-top: 0;
|
|
886
|
+
margin-bottom: 20px;
|
|
887
|
+
color: #111827;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
.worksona-key-input {
|
|
891
|
+
margin-bottom: 15px;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
.worksona-key-input label {
|
|
895
|
+
display: block;
|
|
896
|
+
margin-bottom: 5px;
|
|
897
|
+
font-weight: 500;
|
|
898
|
+
color: #374151;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
.worksona-input-group {
|
|
902
|
+
display: flex;
|
|
903
|
+
align-items: center;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
.worksona-input-group input {
|
|
907
|
+
flex: 1;
|
|
908
|
+
padding: 10px 12px;
|
|
909
|
+
border: 1px solid #d1d5db;
|
|
910
|
+
border-radius: 6px 0 0 6px;
|
|
911
|
+
font-size: 14px;
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
.worksona-toggle-visibility {
|
|
915
|
+
padding: 10px 12px;
|
|
916
|
+
background: #f9fafb;
|
|
917
|
+
border: 1px solid #d1d5db;
|
|
918
|
+
border-left: none;
|
|
919
|
+
border-radius: 0 6px 6px 0;
|
|
920
|
+
cursor: pointer;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
.worksona-button-group {
|
|
924
|
+
display: flex;
|
|
925
|
+
gap: 10px;
|
|
926
|
+
margin-top: 20px;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
.worksona-primary-button {
|
|
930
|
+
padding: 8px 16px;
|
|
931
|
+
background: #2563eb;
|
|
932
|
+
color: white;
|
|
933
|
+
border: none;
|
|
934
|
+
border-radius: 6px;
|
|
935
|
+
font-weight: 500;
|
|
936
|
+
cursor: pointer;
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
.worksona-primary-button:hover {
|
|
940
|
+
background: #1d4ed8;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
.worksona-secondary-button {
|
|
944
|
+
padding: 8px 16px;
|
|
945
|
+
background: #f3f4f6;
|
|
946
|
+
color: #374151;
|
|
947
|
+
border: 1px solid #d1d5db;
|
|
948
|
+
border-radius: 6px;
|
|
949
|
+
font-weight: 500;
|
|
950
|
+
cursor: pointer;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
.worksona-secondary-button:hover {
|
|
954
|
+
background: #e5e7eb;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
.worksona-agent-card {
|
|
958
|
+
background: white;
|
|
959
|
+
border: 1px solid #e5e7eb;
|
|
960
|
+
border-radius: 8px;
|
|
961
|
+
padding: 15px;
|
|
962
|
+
margin-bottom: 15px;
|
|
963
|
+
transition: box-shadow 0.2s;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
.worksona-agent-card:hover {
|
|
967
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
.worksona-agent-header {
|
|
971
|
+
display: flex;
|
|
972
|
+
justify-content: space-between;
|
|
973
|
+
align-items: center;
|
|
974
|
+
margin-bottom: 10px;
|
|
975
|
+
cursor: pointer;
|
|
976
|
+
position: relative;
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
.worksona-expand-icon {
|
|
980
|
+
font-size: 12px;
|
|
981
|
+
color: #6b7280;
|
|
982
|
+
transition: transform 0.2s;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
.worksona-expand-icon.rotated {
|
|
986
|
+
transform: rotate(180deg);
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
.worksona-agent-details.active + .worksona-expand-icon {
|
|
990
|
+
transform: rotate(180deg);
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
.worksona-agent-name {
|
|
994
|
+
font-size: 16px;
|
|
995
|
+
font-weight: 600;
|
|
996
|
+
color: #111827;
|
|
997
|
+
margin: 0;
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
.worksona-agent-id {
|
|
1001
|
+
color: #6b7280;
|
|
1002
|
+
font-size: 12px;
|
|
1003
|
+
font-family: monospace;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
.worksona-agent-status {
|
|
1007
|
+
display: flex;
|
|
1008
|
+
align-items: center;
|
|
1009
|
+
gap: 8px;
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
.worksona-status-label {
|
|
1013
|
+
font-size: 12px;
|
|
1014
|
+
padding: 2px 6px;
|
|
1015
|
+
border-radius: 10px;
|
|
1016
|
+
background: #f3f4f6;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
.worksona-agent-status.active .worksona-status-label {
|
|
1020
|
+
background: #dcfce7;
|
|
1021
|
+
color: #166534;
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
.worksona-agent-status.inactive .worksona-status-label {
|
|
1025
|
+
background: #fee2e2;
|
|
1026
|
+
color: #991b1b;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
.worksona-agent-description {
|
|
1030
|
+
color: #4b5563;
|
|
1031
|
+
margin-bottom: 15px;
|
|
1032
|
+
font-size: 14px;
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
.worksona-agent-details {
|
|
1036
|
+
display: none;
|
|
1037
|
+
overflow: hidden;
|
|
1038
|
+
border-top: 1px solid #e5e7eb;
|
|
1039
|
+
margin-top: 10px;
|
|
1040
|
+
padding-top: 15px;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
.worksona-agent-details.active {
|
|
1044
|
+
display: block;
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
.worksona-agent-tabs {
|
|
1048
|
+
display: flex;
|
|
1049
|
+
border-bottom: 1px solid #e5e7eb;
|
|
1050
|
+
margin-bottom: 15px;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
.worksona-agent-tab {
|
|
1054
|
+
padding: 8px 16px;
|
|
1055
|
+
border: none;
|
|
1056
|
+
background: none;
|
|
1057
|
+
cursor: pointer;
|
|
1058
|
+
color: #6b7280;
|
|
1059
|
+
font-weight: 500;
|
|
1060
|
+
position: relative;
|
|
1061
|
+
font-size: 14px;
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
.worksona-agent-tab:hover {
|
|
1065
|
+
color: #111827;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
.worksona-agent-tab.active {
|
|
1069
|
+
color: #2563eb;
|
|
1070
|
+
border-bottom: 2px solid #2563eb;
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
.worksona-agent-tab-content {
|
|
1074
|
+
display: none;
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
.worksona-agent-tab-content.active {
|
|
1078
|
+
display: block;
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
.worksona-agent-tab-content h4 {
|
|
1082
|
+
font-size: 14px;
|
|
1083
|
+
font-weight: 600;
|
|
1084
|
+
margin: 0 0 10px;
|
|
1085
|
+
color: #111827;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
.worksona-config-details,
|
|
1089
|
+
.worksona-traits,
|
|
1090
|
+
.worksona-metrics {
|
|
1091
|
+
background: #f9fafb;
|
|
1092
|
+
border-radius: 6px;
|
|
1093
|
+
padding: 12px;
|
|
1094
|
+
margin-bottom: 15px;
|
|
1095
|
+
font-size: 13px;
|
|
1096
|
+
line-height: 1.5;
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
.worksona-prompt-box {
|
|
1100
|
+
background: #f9fafb;
|
|
1101
|
+
border-radius: 6px;
|
|
1102
|
+
padding: 12px;
|
|
1103
|
+
margin-bottom: 15px;
|
|
1104
|
+
font-size: 13px;
|
|
1105
|
+
line-height: 1.5;
|
|
1106
|
+
white-space: pre-wrap;
|
|
1107
|
+
font-family: monospace;
|
|
1108
|
+
max-height: 200px;
|
|
1109
|
+
overflow-y: auto;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
.worksona-examples {
|
|
1113
|
+
display: flex;
|
|
1114
|
+
flex-direction: column;
|
|
1115
|
+
gap: 10px;
|
|
1116
|
+
margin-bottom: 20px;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
.worksona-example {
|
|
1120
|
+
background: #f9fafb;
|
|
1121
|
+
border-radius: 6px;
|
|
1122
|
+
padding: 12px;
|
|
1123
|
+
font-size: 13px;
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
.worksona-example-user {
|
|
1127
|
+
margin-bottom: 8px;
|
|
1128
|
+
color: #4b5563;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
.worksona-example-assistant {
|
|
1132
|
+
color: #1f2937;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
1135
|
+
.worksona-history {
|
|
1136
|
+
display: flex;
|
|
1137
|
+
flex-direction: column;
|
|
1138
|
+
gap: 10px;
|
|
1139
|
+
margin-bottom: 15px;
|
|
1140
|
+
max-height: 300px;
|
|
1141
|
+
overflow-y: auto;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
.worksona-history-item {
|
|
1145
|
+
background: #f9fafb;
|
|
1146
|
+
border-radius: 6px;
|
|
1147
|
+
padding: 12px;
|
|
1148
|
+
font-size: 13px;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
.worksona-history-time {
|
|
1152
|
+
font-size: 11px;
|
|
1153
|
+
color: #6b7280;
|
|
1154
|
+
margin-bottom: 5px;
|
|
1155
|
+
}
|
|
1156
|
+
|
|
1157
|
+
.worksona-history-query {
|
|
1158
|
+
margin-bottom: 8px;
|
|
1159
|
+
color: #4b5563;
|
|
1160
|
+
}
|
|
1161
|
+
|
|
1162
|
+
.worksona-history-response {
|
|
1163
|
+
color: #1f2937;
|
|
1164
|
+
margin-bottom: 8px;
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
.worksona-history-meta {
|
|
1168
|
+
font-size: 11px;
|
|
1169
|
+
color: #6b7280;
|
|
1170
|
+
}
|
|
1171
|
+
|
|
1172
|
+
.worksona-no-agents {
|
|
1173
|
+
text-align: center;
|
|
1174
|
+
padding: 40px 20px;
|
|
1175
|
+
color: #6b7280;
|
|
1176
|
+
background: #f9fafb;
|
|
1177
|
+
border-radius: 8px;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
.worksona-no-examples {
|
|
1181
|
+
color: #6b7280;
|
|
1182
|
+
font-style: italic;
|
|
1183
|
+
background: #f9fafb;
|
|
1184
|
+
padding: 12px;
|
|
1185
|
+
border-radius: 6px;
|
|
1186
|
+
margin: 10px 0;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1189
|
+
.worksona-control-panel {
|
|
1190
|
+
box-shadow: 0 5px 20px rgba(0,0,0,0.2);
|
|
1191
|
+
max-height: 80vh;
|
|
1192
|
+
overflow: auto;
|
|
1193
|
+
background: white;
|
|
1194
|
+
border-radius: 8px;
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
.worksona-json-display {
|
|
1198
|
+
background: #f8fafc;
|
|
1199
|
+
border-radius: 6px;
|
|
1200
|
+
padding: 15px;
|
|
1201
|
+
margin: 0;
|
|
1202
|
+
font-family: monospace;
|
|
1203
|
+
font-size: 13px;
|
|
1204
|
+
line-height: 1.5;
|
|
1205
|
+
overflow: auto;
|
|
1206
|
+
max-height: 500px;
|
|
1207
|
+
white-space: pre-wrap;
|
|
1208
|
+
color: #334155;
|
|
1209
|
+
border: 1px solid #e2e8f0;
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
/* Ensure the json tab content has proper height */
|
|
1213
|
+
#worksona-agent-tab-content[id$="-json"] {
|
|
1214
|
+
max-height: 500px;
|
|
1215
|
+
overflow: auto;
|
|
1216
|
+
}
|
|
1217
|
+
|
|
1218
|
+
/* JSON syntax highlighting */
|
|
1219
|
+
.worksona-json-key {
|
|
1220
|
+
color: #0f766e;
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
.worksona-json-string {
|
|
1224
|
+
color: #b91c1c;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
.worksona-json-number {
|
|
1228
|
+
color: #1d4ed8;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
.worksona-json-boolean {
|
|
1232
|
+
color: #7e22ce;
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
.worksona-json-null {
|
|
1236
|
+
color: #64748b;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
/* Ensure the modal is scrollable on smaller screens */
|
|
1240
|
+
@media (max-height: 768px) {
|
|
1241
|
+
#worksona-modal-container {
|
|
1242
|
+
max-height: 90vh;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
.worksona-control-panel {
|
|
1246
|
+
max-height: 90vh;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
`;
|
|
1250
|
+
|
|
1251
|
+
const styleSheet = document.createElement('style');
|
|
1252
|
+
styleSheet.textContent = styles;
|
|
1253
|
+
document.head.appendChild(styleSheet);
|
|
1254
|
+
|
|
1255
|
+
// Add event listeners
|
|
1256
|
+
this._setupEventListeners(container);
|
|
1257
|
+
|
|
1258
|
+
// Initial update
|
|
1259
|
+
this.updateControlPanel();
|
|
1260
|
+
|
|
1261
|
+
return true;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
_setupEventListeners(container) {
|
|
1265
|
+
// Use the provided container or fall back to the stored container reference
|
|
1266
|
+
container = container || this.controlPanelContainer;
|
|
1267
|
+
|
|
1268
|
+
if (!container) {
|
|
1269
|
+
this._log('No container available for event listeners', 'error');
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
|
|
1273
|
+
// Tab switching
|
|
1274
|
+
container.querySelectorAll('.worksona-tab').forEach(tab => {
|
|
1275
|
+
tab.addEventListener('click', () => {
|
|
1276
|
+
container.querySelectorAll('.worksona-tab').forEach(t => t.classList.remove('active'));
|
|
1277
|
+
container.querySelectorAll('.worksona-tab-content').forEach(c => c.classList.remove('active'));
|
|
1278
|
+
tab.classList.add('active');
|
|
1279
|
+
document.getElementById(`worksona-${tab.dataset.tab}-tab`).classList.add('active');
|
|
1280
|
+
});
|
|
1281
|
+
});
|
|
1282
|
+
|
|
1283
|
+
// Password visibility toggle
|
|
1284
|
+
container.querySelectorAll('.worksona-toggle-visibility').forEach(button => {
|
|
1285
|
+
button.addEventListener('click', () => {
|
|
1286
|
+
const input = button.previousElementSibling;
|
|
1287
|
+
if (input.type === 'password') {
|
|
1288
|
+
input.type = 'text';
|
|
1289
|
+
button.textContent = '🔒';
|
|
1290
|
+
} else {
|
|
1291
|
+
input.type = 'password';
|
|
1292
|
+
button.textContent = '👁️';
|
|
1293
|
+
}
|
|
1294
|
+
});
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
// Save API keys
|
|
1298
|
+
const saveButton = document.getElementById('worksona-save-keys');
|
|
1299
|
+
if (saveButton) {
|
|
1300
|
+
// Remove any existing event listeners
|
|
1301
|
+
const newSaveButton = saveButton.cloneNode(true);
|
|
1302
|
+
saveButton.parentNode.replaceChild(newSaveButton, saveButton);
|
|
1303
|
+
|
|
1304
|
+
newSaveButton.addEventListener('click', () => {
|
|
1305
|
+
const openaiKey = document.getElementById('worksona-openai-key').value;
|
|
1306
|
+
const anthropicKey = document.getElementById('worksona-anthropic-key').value;
|
|
1307
|
+
const googleKey = document.getElementById('worksona-google-key').value;
|
|
1308
|
+
|
|
1309
|
+
// Save to localStorage first
|
|
1310
|
+
if (openaiKey) localStorage.setItem('openai_api_key', openaiKey);
|
|
1311
|
+
if (anthropicKey) localStorage.setItem('anthropic_api_key', anthropicKey);
|
|
1312
|
+
if (googleKey) localStorage.setItem('google_api_key', googleKey);
|
|
1313
|
+
|
|
1314
|
+
// Update API keys in options
|
|
1315
|
+
this.options.apiKeys = {
|
|
1316
|
+
...this.options.apiKeys,
|
|
1317
|
+
...(openaiKey && { openai: openaiKey }),
|
|
1318
|
+
...(anthropicKey && { anthropic: anthropicKey }),
|
|
1319
|
+
...(googleKey && { google: googleKey })
|
|
1320
|
+
};
|
|
1321
|
+
|
|
1322
|
+
// Reinitialize providers with new keys
|
|
1323
|
+
this._initializeProviders();
|
|
1324
|
+
|
|
1325
|
+
// Update control panel
|
|
1326
|
+
this.updateControlPanel();
|
|
1327
|
+
|
|
1328
|
+
// Emit event
|
|
1329
|
+
this._emit('api-keys-updated', { providers: Object.keys(this.options.apiKeys) });
|
|
1330
|
+
|
|
1331
|
+
// Show success message only once
|
|
1332
|
+
alert('API keys saved successfully!');
|
|
1333
|
+
});
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
// Test connections
|
|
1337
|
+
const testButton = document.getElementById('worksona-test-connections');
|
|
1338
|
+
if (testButton) {
|
|
1339
|
+
// Remove any existing event listeners
|
|
1340
|
+
const newTestButton = testButton.cloneNode(true);
|
|
1341
|
+
testButton.parentNode.replaceChild(newTestButton, testButton);
|
|
1342
|
+
|
|
1343
|
+
newTestButton.addEventListener('click', async () => {
|
|
1344
|
+
newTestButton.disabled = true;
|
|
1345
|
+
newTestButton.textContent = 'Testing...';
|
|
1346
|
+
|
|
1347
|
+
try {
|
|
1348
|
+
await this._testProviderConnections();
|
|
1349
|
+
this.updateControlPanel();
|
|
1350
|
+
} finally {
|
|
1351
|
+
newTestButton.disabled = false;
|
|
1352
|
+
newTestButton.textContent = 'Test Connections';
|
|
1353
|
+
}
|
|
1354
|
+
});
|
|
1355
|
+
}
|
|
1356
|
+
|
|
1357
|
+
// Close button
|
|
1358
|
+
const closeButton = container.querySelector('.worksona-close-button');
|
|
1359
|
+
if (closeButton) {
|
|
1360
|
+
// Remove any existing event listeners
|
|
1361
|
+
const newCloseButton = closeButton.cloneNode(true);
|
|
1362
|
+
closeButton.parentNode.replaceChild(newCloseButton, closeButton);
|
|
1363
|
+
|
|
1364
|
+
newCloseButton.addEventListener('click', () => {
|
|
1365
|
+
// If the control panel is in a modal or overlay, hide it
|
|
1366
|
+
const panel = container.querySelector('.worksona-control-panel');
|
|
1367
|
+
if (panel) {
|
|
1368
|
+
panel.style.display = 'none';
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
// Temperature slider inputs
|
|
1374
|
+
container.querySelectorAll('input[type="range"]').forEach(slider => {
|
|
1375
|
+
const valueDisplay = slider.nextElementSibling;
|
|
1376
|
+
|
|
1377
|
+
slider.addEventListener('input', () => {
|
|
1378
|
+
if (valueDisplay) {
|
|
1379
|
+
valueDisplay.textContent = slider.value;
|
|
1380
|
+
}
|
|
1381
|
+
});
|
|
1382
|
+
|
|
1383
|
+
// Initialize with current value
|
|
1384
|
+
if (valueDisplay && slider.value) {
|
|
1385
|
+
valueDisplay.textContent = slider.value;
|
|
1386
|
+
}
|
|
1387
|
+
});
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
async _testProviderConnections() {
|
|
1391
|
+
const providers = ['openai', 'anthropic', 'google'];
|
|
1392
|
+
|
|
1393
|
+
for (const provider of providers) {
|
|
1394
|
+
const statusDot = document.getElementById(`worksona-${provider}-status`);
|
|
1395
|
+
if (!statusDot) continue;
|
|
1396
|
+
|
|
1397
|
+
if (!this.options.apiKeys[provider] || this.options.apiKeys[provider].trim() === '') {
|
|
1398
|
+
statusDot.className = 'worksona-status-dot';
|
|
1399
|
+
continue;
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
try {
|
|
1403
|
+
// Simple validation for each provider based on key format
|
|
1404
|
+
let isValid = false;
|
|
1405
|
+
|
|
1406
|
+
switch (provider) {
|
|
1407
|
+
case 'openai':
|
|
1408
|
+
isValid = this.options.apiKeys.openai && this.options.apiKeys.openai.startsWith('sk-');
|
|
1409
|
+
break;
|
|
1410
|
+
case 'anthropic':
|
|
1411
|
+
isValid = this.options.apiKeys.anthropic && this.options.apiKeys.anthropic.startsWith('sk-ant-');
|
|
1412
|
+
break;
|
|
1413
|
+
case 'google':
|
|
1414
|
+
isValid = this.options.apiKeys.google && this.options.apiKeys.google.length > 10;
|
|
1415
|
+
break;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
statusDot.className = isValid ? 'worksona-status-dot active' : 'worksona-status-dot';
|
|
1419
|
+
this._log(`Provider ${provider} validation result: ${isValid ? 'valid' : 'invalid'}`);
|
|
1420
|
+
|
|
1421
|
+
} catch (error) {
|
|
1422
|
+
statusDot.className = 'worksona-status-dot';
|
|
1423
|
+
console.error(`Error testing ${provider} connection:`, error);
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1428
|
+
updateControlPanel() {
|
|
1429
|
+
if (!this.controlPanelId) return;
|
|
1430
|
+
|
|
1431
|
+
// Update provider status dots
|
|
1432
|
+
this._updateProviderStatus();
|
|
1433
|
+
|
|
1434
|
+
// Fill existing API key inputs
|
|
1435
|
+
Object.entries(this.options.apiKeys).forEach(([provider, key]) => {
|
|
1436
|
+
const input = document.getElementById(`worksona-${provider}-key`);
|
|
1437
|
+
if (input && key) {
|
|
1438
|
+
input.value = key;
|
|
1439
|
+
}
|
|
1440
|
+
});
|
|
1441
|
+
|
|
1442
|
+
// Update agent list
|
|
1443
|
+
this._updateAgentList();
|
|
1444
|
+
|
|
1445
|
+
// Register for agent changes
|
|
1446
|
+
this._setupAgentEventListeners();
|
|
1447
|
+
|
|
1448
|
+
// Ensure all event listeners are properly set up
|
|
1449
|
+
if (this.controlPanelContainer) {
|
|
1450
|
+
this._setupEventListeners(this.controlPanelContainer);
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
_updateProviderStatus() {
|
|
1455
|
+
const providers = ['openai', 'anthropic', 'google'];
|
|
1456
|
+
|
|
1457
|
+
providers.forEach(provider => {
|
|
1458
|
+
const statusDot = document.getElementById(`worksona-${provider}-status`);
|
|
1459
|
+
if (!statusDot) return;
|
|
1460
|
+
|
|
1461
|
+
const isConfigured = !!this.options.apiKeys[provider];
|
|
1462
|
+
statusDot.className = isConfigured ? 'worksona-status-dot active' : 'worksona-status-dot';
|
|
1463
|
+
});
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
_updateAgentList() {
|
|
1467
|
+
const agentList = document.getElementById('worksona-agent-list');
|
|
1468
|
+
if (!agentList) return;
|
|
1469
|
+
|
|
1470
|
+
const agents = this.getAgents();
|
|
1471
|
+
|
|
1472
|
+
if (agents.length === 0) {
|
|
1473
|
+
agentList.innerHTML = `
|
|
1474
|
+
<div class="worksona-no-agents">
|
|
1475
|
+
<p>No agents have been loaded yet.</p>
|
|
1476
|
+
<p>Agents will appear here when they are loaded by your application.</p>
|
|
1477
|
+
</div>
|
|
1478
|
+
`;
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
agentList.innerHTML = agents.map(agent => {
|
|
1483
|
+
const metrics = agent.getMetrics();
|
|
1484
|
+
const state = agent.getState();
|
|
1485
|
+
const history = agent.getHistory();
|
|
1486
|
+
|
|
1487
|
+
// Format history items
|
|
1488
|
+
const historyHtml = history.length > 0
|
|
1489
|
+
? history.slice(-5).map(item => `
|
|
1490
|
+
<div class="worksona-history-item">
|
|
1491
|
+
<div class="worksona-history-time">${new Date(item.timestamp).toLocaleTimeString()}</div>
|
|
1492
|
+
<div class="worksona-history-query"><strong>Query:</strong> ${this._escapeHtml(item.query)}</div>
|
|
1493
|
+
<div class="worksona-history-response"><strong>Response:</strong> ${this._escapeHtml(item.response || 'Error: ' + (item.error?.message || 'Unknown error'))}</div>
|
|
1494
|
+
<div class="worksona-history-meta">
|
|
1495
|
+
<span>Provider: ${item.provider}</span> |
|
|
1496
|
+
<span>Model: ${item.model}</span> |
|
|
1497
|
+
<span>Duration: ${item.duration}ms</span>
|
|
1498
|
+
</div>
|
|
1499
|
+
</div>
|
|
1500
|
+
`).join('')
|
|
1501
|
+
: '<p>No interaction history available yet.</p>';
|
|
1502
|
+
|
|
1503
|
+
// Get system prompt and examples - ensure we're getting them from the right places with thorough checks
|
|
1504
|
+
const getSystemPrompt = (agent) => {
|
|
1505
|
+
// Check all possible locations for system prompt
|
|
1506
|
+
return agent.systemPrompt ||
|
|
1507
|
+
agent.config?.systemPrompt ||
|
|
1508
|
+
(agent.config?.config?.systemPrompt) ||
|
|
1509
|
+
'';
|
|
1510
|
+
};
|
|
1511
|
+
|
|
1512
|
+
const getExamples = (agent) => {
|
|
1513
|
+
// Check all possible locations for examples, including deeper nesting
|
|
1514
|
+
const examples = agent.examples ||
|
|
1515
|
+
agent.config?.examples ||
|
|
1516
|
+
agent.config?.config?.examples ||
|
|
1517
|
+
[];
|
|
1518
|
+
console.log('Agent:', agent.id, 'Examples:', JSON.stringify(examples));
|
|
1519
|
+
return examples;
|
|
1520
|
+
};
|
|
1521
|
+
|
|
1522
|
+
const systemPrompt = getSystemPrompt(agent);
|
|
1523
|
+
const promptDisplay = systemPrompt
|
|
1524
|
+
? `<div class="worksona-prompt-box">${this._escapeHtml(systemPrompt)}</div>`
|
|
1525
|
+
: `<p class="worksona-no-examples">No system prompt has been defined for this agent. A system prompt helps establish the agent's behavior and capabilities.</p>`;
|
|
1526
|
+
|
|
1527
|
+
const examples = getExamples(agent);
|
|
1528
|
+
|
|
1529
|
+
const examplesHtml = examples && examples.length > 0
|
|
1530
|
+
? examples.map(ex => `
|
|
1531
|
+
<div class="worksona-example">
|
|
1532
|
+
<div class="worksona-example-user"><strong>User:</strong> ${this._escapeHtml(ex.user || '')}</div>
|
|
1533
|
+
<div class="worksona-example-assistant"><strong>Assistant:</strong> ${this._escapeHtml(ex.assistant || '')}</div>
|
|
1534
|
+
</div>
|
|
1535
|
+
`).join('')
|
|
1536
|
+
: '<p class="worksona-no-examples">No examples have been defined for this agent. Examples help demonstrate the expected conversation flow.</p>';
|
|
1537
|
+
|
|
1538
|
+
// Format configuration details - ensure we're showing all available config settings with proper null checks
|
|
1539
|
+
const configDetails = `
|
|
1540
|
+
<div class="worksona-config-details">
|
|
1541
|
+
<h4>Model Settings</h4>
|
|
1542
|
+
<div><strong>Provider:</strong> ${agent.config?.provider || agent.state?.currentProvider || 'default'}</div>
|
|
1543
|
+
<div><strong>Model:</strong> ${agent.config?.model || agent.state?.currentModel || 'default'}</div>
|
|
1544
|
+
<div><strong>Temperature:</strong> ${agent.config?.temperature !== undefined ? agent.config.temperature : (agent.config?.config?.temperature !== undefined ? agent.config.config.temperature : 'default')}</div>
|
|
1545
|
+
<div><strong>Max Tokens:</strong> ${agent.config?.maxTokens !== undefined ? agent.config.maxTokens : (agent.config?.config?.maxTokens !== undefined ? agent.config.config.maxTokens : 'default')}</div>
|
|
1546
|
+
${agent.config?.topP !== undefined || agent.config?.config?.topP !== undefined ? `<div><strong>Top P:</strong> ${agent.config?.topP !== undefined ? agent.config.topP : agent.config?.config?.topP}</div>` : ''}
|
|
1547
|
+
${agent.config?.frequencyPenalty !== undefined || agent.config?.config?.frequencyPenalty !== undefined ? `<div><strong>Frequency Penalty:</strong> ${agent.config?.frequencyPenalty !== undefined ? agent.config.frequencyPenalty : agent.config?.config?.frequencyPenalty}</div>` : ''}
|
|
1548
|
+
${agent.config?.presencePenalty !== undefined || agent.config?.config?.presencePenalty !== undefined ? `<div><strong>Presence Penalty:</strong> ${agent.config?.presencePenalty !== undefined ? agent.config.presencePenalty : agent.config?.config?.presencePenalty}</div>` : ''}
|
|
1549
|
+
</div>
|
|
1550
|
+
`;
|
|
1551
|
+
|
|
1552
|
+
// Format JSON for this specific agent with complete and properly formatted information
|
|
1553
|
+
const agentJson = JSON.stringify({
|
|
1554
|
+
id: agent.id,
|
|
1555
|
+
name: agent.name,
|
|
1556
|
+
description: agent.description,
|
|
1557
|
+
config: {
|
|
1558
|
+
provider: agent.config.provider,
|
|
1559
|
+
model: agent.config.model,
|
|
1560
|
+
temperature: agent.config.temperature,
|
|
1561
|
+
maxTokens: agent.config.maxTokens,
|
|
1562
|
+
topP: agent.config.topP,
|
|
1563
|
+
frequencyPenalty: agent.config.frequencyPenalty,
|
|
1564
|
+
presencePenalty: agent.config.presencePenalty,
|
|
1565
|
+
},
|
|
1566
|
+
systemPrompt: getSystemPrompt(agent),
|
|
1567
|
+
examples: getExamples(agent),
|
|
1568
|
+
traits: agent.traits || {},
|
|
1569
|
+
metrics: agent.getMetrics(),
|
|
1570
|
+
state: agent.getState(),
|
|
1571
|
+
transactions: agent.getHistory().slice(-5)
|
|
1572
|
+
}, null, 2);
|
|
1573
|
+
|
|
1574
|
+
// Trait information
|
|
1575
|
+
const traits = agent.traits || {};
|
|
1576
|
+
const traitsHtml = `
|
|
1577
|
+
<div class="worksona-traits">
|
|
1578
|
+
${traits.personality ? `<div><strong>Personality:</strong> ${traits.personality.join(', ')}</div>` : ''}
|
|
1579
|
+
${traits.knowledge ? `<div><strong>Knowledge:</strong> ${traits.knowledge.join(', ')}</div>` : ''}
|
|
1580
|
+
${traits.tone ? `<div><strong>Tone:</strong> ${traits.tone}</div>` : ''}
|
|
1581
|
+
${traits.background ? `<div><strong>Background:</strong> ${traits.background}</div>` : ''}
|
|
1582
|
+
</div>
|
|
1583
|
+
`;
|
|
1584
|
+
|
|
1585
|
+
return `
|
|
1586
|
+
<div class="worksona-agent-card">
|
|
1587
|
+
<div class="worksona-agent-header" onclick="document.getElementById('worksona-agent-details-${agent.id}').classList.toggle('active'); this.querySelector('.worksona-expand-icon').classList.toggle('rotated')">
|
|
1588
|
+
<h3 class="worksona-agent-name">${agent.name}</h3>
|
|
1589
|
+
<div class="worksona-agent-status ${state.isActive ? 'active' : 'inactive'}">
|
|
1590
|
+
<span class="worksona-agent-id">${agent.id}</span>
|
|
1591
|
+
<span class="worksona-status-label">${state.isActive ? 'Active' : 'Inactive'}</span>
|
|
1592
|
+
</div>
|
|
1593
|
+
<div class="worksona-expand-icon">▼</div>
|
|
1594
|
+
</div>
|
|
1595
|
+
<p class="worksona-agent-description">${agent.description || 'No description provided'}</p>
|
|
1596
|
+
|
|
1597
|
+
<div id="worksona-agent-details-${agent.id}" class="worksona-agent-details">
|
|
1598
|
+
<div class="worksona-agent-tabs">
|
|
1599
|
+
<button class="worksona-agent-tab active" data-agent="${agent.id}" data-tab="agent">Agent Details</button>
|
|
1600
|
+
<button class="worksona-agent-tab" data-agent="${agent.id}" data-tab="model">Model Settings</button>
|
|
1601
|
+
<button class="worksona-agent-tab" data-agent="${agent.id}" data-tab="prompt">System Prompt</button>
|
|
1602
|
+
<button class="worksona-agent-tab" data-agent="${agent.id}" data-tab="history">History</button>
|
|
1603
|
+
<button class="worksona-agent-tab" data-agent="${agent.id}" data-tab="json">Raw JSON</button>
|
|
1604
|
+
</div>
|
|
1605
|
+
|
|
1606
|
+
<div class="worksona-agent-tab-content active" id="worksona-agent-${agent.id}-agent">
|
|
1607
|
+
<h4>Agent Configuration</h4>
|
|
1608
|
+
<div class="worksona-config-details">
|
|
1609
|
+
<div><strong>ID:</strong> ${agent.id}</div>
|
|
1610
|
+
<div><strong>Name:</strong> ${agent.name}</div>
|
|
1611
|
+
<div><strong>Description:</strong> ${agent.description || 'No description provided'}</div>
|
|
1612
|
+
</div>
|
|
1613
|
+
<h4>Traits</h4>
|
|
1614
|
+
${traitsHtml}
|
|
1615
|
+
</div>
|
|
1616
|
+
|
|
1617
|
+
<div class="worksona-agent-tab-content" id="worksona-agent-${agent.id}-model">
|
|
1618
|
+
<h4>Model Settings</h4>
|
|
1619
|
+
${configDetails}
|
|
1620
|
+
</div>
|
|
1621
|
+
|
|
1622
|
+
<div class="worksona-agent-tab-content" id="worksona-agent-${agent.id}-prompt">
|
|
1623
|
+
<h4>System Prompt</h4>
|
|
1624
|
+
${promptDisplay}
|
|
1625
|
+
|
|
1626
|
+
<h4>Examples</h4>
|
|
1627
|
+
<div class="worksona-examples">
|
|
1628
|
+
${examplesHtml}
|
|
1629
|
+
</div>
|
|
1630
|
+
</div>
|
|
1631
|
+
|
|
1632
|
+
<div class="worksona-agent-tab-content" id="worksona-agent-${agent.id}-history">
|
|
1633
|
+
<h4>Recent Interactions</h4>
|
|
1634
|
+
<div class="worksona-history">
|
|
1635
|
+
${historyHtml}
|
|
1636
|
+
</div>
|
|
1637
|
+
<div class="worksona-metrics">
|
|
1638
|
+
<div><strong>Total Queries:</strong> ${metrics.totalQueries}</div>
|
|
1639
|
+
<div><strong>Avg Response Time:</strong> ${Math.round(metrics.avgResponseTime)}ms</div>
|
|
1640
|
+
<div><strong>Success Rate:</strong> ${(metrics.successRate * 100).toFixed(1)}%</div>
|
|
1641
|
+
<div><strong>Last Active:</strong> ${metrics.lastActive ? new Date(metrics.lastActive).toLocaleTimeString() : 'Never'}</div>
|
|
1642
|
+
</div>
|
|
1643
|
+
</div>
|
|
1644
|
+
|
|
1645
|
+
<div class="worksona-agent-tab-content" id="worksona-agent-${agent.id}-json">
|
|
1646
|
+
<h4>Agent Configuration JSON</h4>
|
|
1647
|
+
<div class="worksona-json-display">${this._escapeHtml(agentJson)}</div>
|
|
1648
|
+
</div>
|
|
1649
|
+
</div>
|
|
1650
|
+
</div>
|
|
1651
|
+
`;
|
|
1652
|
+
}).join('');
|
|
1653
|
+
|
|
1654
|
+
// Remove any existing agent tab event listeners before adding new ones
|
|
1655
|
+
if (this.agentTabEventListeners) {
|
|
1656
|
+
this.agentTabEventListeners.forEach(({ element, type, listener }) => {
|
|
1657
|
+
element.removeEventListener(type, listener);
|
|
1658
|
+
});
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
// Initialize array to store new event listeners
|
|
1662
|
+
this.agentTabEventListeners = [];
|
|
1663
|
+
|
|
1664
|
+
// Add click handlers for agent tabs
|
|
1665
|
+
agentList.querySelectorAll('.worksona-agent-tab').forEach(tab => {
|
|
1666
|
+
const clickHandler = (e) => {
|
|
1667
|
+
const agentId = tab.dataset.agent;
|
|
1668
|
+
const tabName = tab.dataset.tab;
|
|
1669
|
+
|
|
1670
|
+
// Deactivate all tabs for this agent
|
|
1671
|
+
document.querySelectorAll(`.worksona-agent-tab[data-agent="${agentId}"]`).forEach(t => {
|
|
1672
|
+
t.classList.remove('active');
|
|
1673
|
+
});
|
|
1674
|
+
|
|
1675
|
+
// Deactivate all tab contents for this agent
|
|
1676
|
+
document.querySelectorAll(`[id^="worksona-agent-${agentId}-"]`).forEach(c => {
|
|
1677
|
+
c.classList.remove('active');
|
|
1678
|
+
});
|
|
1679
|
+
|
|
1680
|
+
// Activate clicked tab and content
|
|
1681
|
+
tab.classList.add('active');
|
|
1682
|
+
document.getElementById(`worksona-agent-${agentId}-${tabName}`).classList.add('active');
|
|
1683
|
+
};
|
|
1684
|
+
|
|
1685
|
+
// Store reference to event listener
|
|
1686
|
+
this.agentTabEventListeners.push({
|
|
1687
|
+
element: tab,
|
|
1688
|
+
type: 'click',
|
|
1689
|
+
listener: clickHandler
|
|
1690
|
+
});
|
|
1691
|
+
|
|
1692
|
+
// Add the event listener
|
|
1693
|
+
tab.addEventListener('click', clickHandler);
|
|
1694
|
+
});
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
// Helper to escape HTML for safe display
|
|
1698
|
+
_escapeHtml(unsafe) {
|
|
1699
|
+
if (!unsafe) return '';
|
|
1700
|
+
return unsafe
|
|
1701
|
+
.toString()
|
|
1702
|
+
.replace(/&/g, "&")
|
|
1703
|
+
.replace(/</g, "<")
|
|
1704
|
+
.replace(/>/g, ">")
|
|
1705
|
+
.replace(/"/g, """)
|
|
1706
|
+
.replace(/'/g, "'");
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
_setupAgentEventListeners() {
|
|
1710
|
+
// Remove existing listeners to avoid duplicates
|
|
1711
|
+
this.off('agent-loaded', this._handleAgentLoaded);
|
|
1712
|
+
this.off('agent-removed', this._handleAgentRemoved);
|
|
1713
|
+
|
|
1714
|
+
// Add listeners for agent changes
|
|
1715
|
+
this.on('agent-loaded', this._handleAgentLoaded.bind(this));
|
|
1716
|
+
this.on('agent-removed', this._handleAgentRemoved.bind(this));
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
_handleAgentLoaded() {
|
|
1720
|
+
this._updateAgentList();
|
|
1721
|
+
}
|
|
1722
|
+
|
|
1723
|
+
_handleAgentRemoved() {
|
|
1724
|
+
this._updateAgentList();
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
// Get all loaded agents
|
|
1728
|
+
getAgents() {
|
|
1729
|
+
return Array.from(this.agents.values());
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
// Create a floating control panel with button in the bottom right
|
|
1733
|
+
createFloatingControlPanel() {
|
|
1734
|
+
// Ensure DOM is ready
|
|
1735
|
+
if (!document.body) {
|
|
1736
|
+
// Wait for DOM to be ready
|
|
1737
|
+
if (document.readyState === 'loading') {
|
|
1738
|
+
document.addEventListener('DOMContentLoaded', () => this.createFloatingControlPanel());
|
|
1739
|
+
return;
|
|
1740
|
+
} else {
|
|
1741
|
+
// Fallback: wait a bit and try again
|
|
1742
|
+
setTimeout(() => this.createFloatingControlPanel(), 100);
|
|
1743
|
+
return;
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
|
|
1747
|
+
try {
|
|
1748
|
+
// Create a wrapper for the button and panel
|
|
1749
|
+
const wrapper = document.createElement('div');
|
|
1750
|
+
wrapper.id = 'worksona-floating-control';
|
|
1751
|
+
|
|
1752
|
+
// Create floating button
|
|
1753
|
+
const button = document.createElement('button');
|
|
1754
|
+
button.id = 'worksona-floating-button';
|
|
1755
|
+
button.innerHTML = '⚙️';
|
|
1756
|
+
button.setAttribute('aria-label', 'Open Worksona Control Panel');
|
|
1757
|
+
wrapper.appendChild(button);
|
|
1758
|
+
|
|
1759
|
+
// Create control panel container
|
|
1760
|
+
const panelContainer = document.createElement('div');
|
|
1761
|
+
panelContainer.id = 'worksona-modal-container';
|
|
1762
|
+
|
|
1763
|
+
// Create overlay
|
|
1764
|
+
const overlay = document.createElement('div');
|
|
1765
|
+
overlay.id = 'worksona-overlay';
|
|
1766
|
+
|
|
1767
|
+
// Add panel and overlay to wrapper
|
|
1768
|
+
wrapper.appendChild(overlay);
|
|
1769
|
+
wrapper.appendChild(panelContainer);
|
|
1770
|
+
|
|
1771
|
+
// Append wrapper to body first to ensure all elements are in DOM
|
|
1772
|
+
document.body.appendChild(wrapper);
|
|
1773
|
+
|
|
1774
|
+
// Small delay to ensure DOM is updated, then set up the control panel
|
|
1775
|
+
setTimeout(() => {
|
|
1776
|
+
this.createControlPanel('worksona-modal-container');
|
|
1777
|
+
|
|
1778
|
+
// Verify the control panel was created successfully
|
|
1779
|
+
const controlPanel = panelContainer.querySelector('.worksona-control-panel');
|
|
1780
|
+
if (!controlPanel) {
|
|
1781
|
+
this._log('Failed to create control panel content, retrying...', 'warn');
|
|
1782
|
+
// Retry after a short delay
|
|
1783
|
+
setTimeout(() => {
|
|
1784
|
+
this.createControlPanel('worksona-modal-container');
|
|
1785
|
+
}, 100);
|
|
1786
|
+
}
|
|
1787
|
+
}, 10);
|
|
1788
|
+
|
|
1789
|
+
// Helper function to close the panel
|
|
1790
|
+
const closePanel = () => {
|
|
1791
|
+
overlay.classList.remove('active');
|
|
1792
|
+
panelContainer.classList.remove('active');
|
|
1793
|
+
const panel = panelContainer.querySelector('.worksona-control-panel');
|
|
1794
|
+
if (panel) {
|
|
1795
|
+
panel.style.display = 'none';
|
|
1796
|
+
}
|
|
1797
|
+
};
|
|
1798
|
+
|
|
1799
|
+
// Add styles for floating button and modal
|
|
1800
|
+
const floatingStyles = document.createElement('style');
|
|
1801
|
+
floatingStyles.textContent = `
|
|
1802
|
+
#worksona-floating-button {
|
|
1803
|
+
position: fixed;
|
|
1804
|
+
bottom: 20px;
|
|
1805
|
+
right: 20px;
|
|
1806
|
+
width: 50px;
|
|
1807
|
+
height: 50px;
|
|
1808
|
+
border-radius: 50%;
|
|
1809
|
+
background: #2563eb;
|
|
1810
|
+
color: white;
|
|
1811
|
+
border: none;
|
|
1812
|
+
font-size: 24px;
|
|
1813
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.2);
|
|
1814
|
+
cursor: pointer;
|
|
1815
|
+
z-index: 9998;
|
|
1816
|
+
display: flex;
|
|
1817
|
+
align-items: center;
|
|
1818
|
+
justify-content: center;
|
|
1819
|
+
transition: all 0.2s ease-in-out;
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
#worksona-floating-button:hover {
|
|
1823
|
+
transform: scale(1.1);
|
|
1824
|
+
background: #1d4ed8;
|
|
1825
|
+
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
#worksona-floating-button:active {
|
|
1829
|
+
transform: scale(0.95);
|
|
1830
|
+
}
|
|
1831
|
+
|
|
1832
|
+
@media (max-width: 768px) {
|
|
1833
|
+
#worksona-floating-button {
|
|
1834
|
+
width: 45px;
|
|
1835
|
+
height: 45px;
|
|
1836
|
+
font-size: 20px;
|
|
1837
|
+
bottom: 15px;
|
|
1838
|
+
right: 15px;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
|
|
1842
|
+
#worksona-modal-container {
|
|
1843
|
+
position: fixed;
|
|
1844
|
+
top: 50%;
|
|
1845
|
+
left: 50%;
|
|
1846
|
+
transform: translate(-50%, -50%);
|
|
1847
|
+
z-index: 10000;
|
|
1848
|
+
width: 90%;
|
|
1849
|
+
max-width: 800px;
|
|
1850
|
+
max-height: 80vh;
|
|
1851
|
+
overflow: auto;
|
|
1852
|
+
display: none;
|
|
1853
|
+
background: white;
|
|
1854
|
+
border-radius: 8px;
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
#worksona-modal-container.active {
|
|
1858
|
+
display: block;
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
#worksona-overlay {
|
|
1862
|
+
position: fixed;
|
|
1863
|
+
top: 0;
|
|
1864
|
+
left: 0;
|
|
1865
|
+
width: 100%;
|
|
1866
|
+
height: 100%;
|
|
1867
|
+
background: rgba(0,0,0,0.5);
|
|
1868
|
+
z-index: 9999;
|
|
1869
|
+
display: none;
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
#worksona-overlay.active {
|
|
1873
|
+
display: block;
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1876
|
+
.worksona-control-panel {
|
|
1877
|
+
box-shadow: 0 5px 20px rgba(0,0,0,0.2);
|
|
1878
|
+
max-height: 80vh;
|
|
1879
|
+
overflow: auto;
|
|
1880
|
+
background: white;
|
|
1881
|
+
border-radius: 8px;
|
|
1882
|
+
transition: all 0.3s ease;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
.worksona-panel-header {
|
|
1886
|
+
display: flex;
|
|
1887
|
+
justify-content: space-between;
|
|
1888
|
+
align-items: center;
|
|
1889
|
+
padding: 15px 20px;
|
|
1890
|
+
border-bottom: 1px solid #eee;
|
|
1891
|
+
background: #fff;
|
|
1892
|
+
position: sticky;
|
|
1893
|
+
top: 0;
|
|
1894
|
+
z-index: 1;
|
|
1895
|
+
}
|
|
1896
|
+
|
|
1897
|
+
.worksona-header-buttons {
|
|
1898
|
+
display: flex;
|
|
1899
|
+
gap: 10px;
|
|
1900
|
+
align-items: center;
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
.worksona-expand-button {
|
|
1904
|
+
background: none;
|
|
1905
|
+
border: none;
|
|
1906
|
+
font-size: 20px;
|
|
1907
|
+
cursor: pointer;
|
|
1908
|
+
color: #64748b;
|
|
1909
|
+
padding: 4px;
|
|
1910
|
+
display: flex;
|
|
1911
|
+
align-items: center;
|
|
1912
|
+
justify-content: center;
|
|
1913
|
+
transition: color 0.2s;
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
.worksona-expand-button:hover {
|
|
1917
|
+
color: #334155;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
.worksona-control-panel.expanded {
|
|
1921
|
+
position: fixed;
|
|
1922
|
+
top: 0;
|
|
1923
|
+
left: 0;
|
|
1924
|
+
width: 100% !important;
|
|
1925
|
+
height: 100vh !important;
|
|
1926
|
+
max-height: 100vh !important;
|
|
1927
|
+
max-width: 100% !important;
|
|
1928
|
+
border-radius: 0;
|
|
1929
|
+
z-index: 10001;
|
|
1930
|
+
}
|
|
1931
|
+
|
|
1932
|
+
#worksona-modal-container.expanded {
|
|
1933
|
+
width: 100%;
|
|
1934
|
+
max-width: 100%;
|
|
1935
|
+
height: 100vh;
|
|
1936
|
+
max-height: 100vh;
|
|
1937
|
+
top: 0;
|
|
1938
|
+
left: 0;
|
|
1939
|
+
transform: none;
|
|
1940
|
+
border-radius: 0;
|
|
1941
|
+
}
|
|
1942
|
+
`;
|
|
1943
|
+
document.head.appendChild(floatingStyles);
|
|
1944
|
+
|
|
1945
|
+
// Set up additional event listeners specific to floating panel
|
|
1946
|
+
button.addEventListener('click', () => {
|
|
1947
|
+
overlay.classList.add('active');
|
|
1948
|
+
panelContainer.classList.add('active');
|
|
1949
|
+
// Ensure the control panel is visible
|
|
1950
|
+
const panel = panelContainer.querySelector('.worksona-control-panel');
|
|
1951
|
+
if (panel) {
|
|
1952
|
+
panel.style.display = 'block';
|
|
1953
|
+
} else {
|
|
1954
|
+
// If panel doesn't exist, try to recreate it
|
|
1955
|
+
this._log('Control panel not found, attempting to recreate', 'warn');
|
|
1956
|
+
const success = this.createControlPanel('worksona-modal-container');
|
|
1957
|
+
if (success !== false) {
|
|
1958
|
+
// Wait a moment for DOM to update, then try again
|
|
1959
|
+
setTimeout(() => {
|
|
1960
|
+
const newPanel = panelContainer.querySelector('.worksona-control-panel');
|
|
1961
|
+
if (newPanel) {
|
|
1962
|
+
newPanel.style.display = 'block';
|
|
1963
|
+
this._log('Control panel recreated successfully', 'info');
|
|
1964
|
+
} else {
|
|
1965
|
+
this._log('Failed to recreate control panel - panel element not found after creation', 'error');
|
|
1966
|
+
// Try to update the control panel content
|
|
1967
|
+
this.updateControlPanel();
|
|
1968
|
+
}
|
|
1969
|
+
}, 50);
|
|
1970
|
+
} else {
|
|
1971
|
+
this._log('Failed to recreate control panel - container not found', 'error');
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
});
|
|
1975
|
+
|
|
1976
|
+
// Add expand/contract functionality
|
|
1977
|
+
const expandButton = panelContainer.querySelector('.worksona-expand-button');
|
|
1978
|
+
if (expandButton) {
|
|
1979
|
+
expandButton.addEventListener('click', () => {
|
|
1980
|
+
const panel = panelContainer.querySelector('.worksona-control-panel');
|
|
1981
|
+
const isExpanded = panel.classList.contains('expanded');
|
|
1982
|
+
|
|
1983
|
+
if (isExpanded) {
|
|
1984
|
+
panel.classList.remove('expanded');
|
|
1985
|
+
panelContainer.classList.remove('expanded');
|
|
1986
|
+
expandButton.innerHTML = '⛶'; // Expand icon
|
|
1987
|
+
expandButton.title = 'Expand to full screen';
|
|
1988
|
+
} else {
|
|
1989
|
+
panel.classList.add('expanded');
|
|
1990
|
+
panelContainer.classList.add('expanded');
|
|
1991
|
+
expandButton.innerHTML = '⛾'; // Contract icon
|
|
1992
|
+
expandButton.title = 'Return to normal size';
|
|
1993
|
+
}
|
|
1994
|
+
});
|
|
1995
|
+
}
|
|
1996
|
+
|
|
1997
|
+
// Close panel when clicking overlay
|
|
1998
|
+
overlay.addEventListener('click', closePanel);
|
|
1999
|
+
|
|
2000
|
+
// Close panel when clicking close button
|
|
2001
|
+
const closeButton = panelContainer.querySelector('.worksona-close-button');
|
|
2002
|
+
if (closeButton) {
|
|
2003
|
+
closeButton.addEventListener('click', (e) => {
|
|
2004
|
+
e.preventDefault();
|
|
2005
|
+
e.stopPropagation();
|
|
2006
|
+
closePanel();
|
|
2007
|
+
});
|
|
2008
|
+
}
|
|
2009
|
+
|
|
2010
|
+
// Add keyboard event listener for Escape key
|
|
2011
|
+
document.addEventListener('keydown', (e) => {
|
|
2012
|
+
if (e.key === 'Escape' && overlay.classList.contains('active')) {
|
|
2013
|
+
closePanel();
|
|
2014
|
+
}
|
|
2015
|
+
});
|
|
2016
|
+
|
|
2017
|
+
} catch (error) {
|
|
2018
|
+
this._log(`Error creating floating control panel: ${error.message}`, 'error');
|
|
2019
|
+
// Retry after a delay
|
|
2020
|
+
setTimeout(() => this.createFloatingControlPanel(), 1000);
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
|
|
2024
|
+
// Add method to check if control panel is ready
|
|
2025
|
+
isControlPanelReady() {
|
|
2026
|
+
const container = document.getElementById(this.controlPanelId);
|
|
2027
|
+
const panel = container?.querySelector('.worksona-control-panel');
|
|
2028
|
+
return !!(container && panel);
|
|
2029
|
+
}
|
|
2030
|
+
|
|
2031
|
+
/**
|
|
2032
|
+
* Process (analyze) an image using the agent's provider (gpt-4o for OpenAI)
|
|
2033
|
+
*/
|
|
2034
|
+
async processImage(agentId, imageData, options = {}) {
|
|
2035
|
+
const agent = this.agents.get(agentId);
|
|
2036
|
+
if (!agent) {
|
|
2037
|
+
this._handleError(new Error(`Agent not found: ${agentId}`), 'AGENT_NOT_FOUND');
|
|
2038
|
+
return null;
|
|
2039
|
+
}
|
|
2040
|
+
const provider = agent.config.provider || this.options.defaultProvider;
|
|
2041
|
+
this._emit('image-analysis-start', { agentId, provider, imageData, options });
|
|
2042
|
+
try {
|
|
2043
|
+
if (provider === 'openai') {
|
|
2044
|
+
// Use OpenAI GPT-4o for vision analysis
|
|
2045
|
+
const modelName = (agent.config.model || 'gpt-4o').trim();
|
|
2046
|
+
const messages = [
|
|
2047
|
+
{ role: 'system', content: agent.config.systemPrompt || 'You are a helpful vision analysis assistant.' },
|
|
2048
|
+
{ role: 'user', content: [
|
|
2049
|
+
{ type: 'text', text: options.prompt || 'Please analyze this image.' },
|
|
2050
|
+
{ type: 'image_url', image_url: { url: imageData, detail: options.detail || 'high' } }
|
|
2051
|
+
] }
|
|
2052
|
+
];
|
|
2053
|
+
const requestBody = {
|
|
2054
|
+
model: modelName,
|
|
2055
|
+
messages,
|
|
2056
|
+
temperature: agent.config.temperature || 0.7,
|
|
2057
|
+
max_tokens: agent.config.maxTokens || 500,
|
|
2058
|
+
top_p: agent.config.topP || 1,
|
|
2059
|
+
frequency_penalty: agent.config.frequencyPenalty || 0,
|
|
2060
|
+
presence_penalty: agent.config.presencePenalty || 0,
|
|
2061
|
+
stream: false
|
|
2062
|
+
};
|
|
2063
|
+
const response = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
2064
|
+
method: 'POST',
|
|
2065
|
+
headers: {
|
|
2066
|
+
'Content-Type': 'application/json',
|
|
2067
|
+
'Authorization': `Bearer ${this.options.apiKeys.openai}`
|
|
2068
|
+
},
|
|
2069
|
+
body: JSON.stringify(requestBody)
|
|
2070
|
+
});
|
|
2071
|
+
const data = await response.json();
|
|
2072
|
+
if (!response.ok) throw new Error(data.error?.message || 'OpenAI image analysis error');
|
|
2073
|
+
this._emit('image-analysis-complete', { agentId, provider, imageData, result: data });
|
|
2074
|
+
return data.choices[0].message.content;
|
|
2075
|
+
} else {
|
|
2076
|
+
throw new Error(`Provider ${provider} does not support image analysis`);
|
|
2077
|
+
}
|
|
2078
|
+
} catch (error) {
|
|
2079
|
+
this._emit('image-processing-error', { agentId, error });
|
|
2080
|
+
this._handleError(error, 'IMAGE_PROCESSING_ERROR', 'Failed to analyze image');
|
|
2081
|
+
return null;
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
// Alias method for analyzeImage that calls processImage
|
|
2086
|
+
async analyzeImage(agentId, imageData, options = {}) {
|
|
2087
|
+
return this.processImage(agentId, imageData, options);
|
|
2088
|
+
}
|
|
2089
|
+
|
|
2090
|
+
/**
|
|
2091
|
+
* Generate an image from a text prompt using the agent's provider (OpenAI DALL-E/gpt-4o)
|
|
2092
|
+
* Supports models: 'dalle-3', 'gpt-4o', 'gpt-4o-vision-preview'.
|
|
2093
|
+
* Model selection order: options.model > agent.config.model > 'dalle-3'
|
|
2094
|
+
*/
|
|
2095
|
+
async generateImage(agentId, prompt, options = {}) {
|
|
2096
|
+
const agent = this.agents.get(agentId);
|
|
2097
|
+
if (!agent) {
|
|
2098
|
+
this._handleError(new Error(`Agent not found: ${agentId}`), 'AGENT_NOT_FOUND');
|
|
2099
|
+
return null;
|
|
2100
|
+
}
|
|
2101
|
+
const provider = agent.config.provider || this.options.defaultProvider;
|
|
2102
|
+
this._emit('image-generation-start', { agentId, provider, prompt, options });
|
|
2103
|
+
try {
|
|
2104
|
+
if (provider === 'openai') {
|
|
2105
|
+
// Model selection logic
|
|
2106
|
+
const modelName = (options.model || agent.config.imageGenerationModel || 'dall-e-3').trim();
|
|
2107
|
+
// DALL-E endpoint supports model param for dalle-3
|
|
2108
|
+
const requestBody = {
|
|
2109
|
+
prompt,
|
|
2110
|
+
n: options.n || 1,
|
|
2111
|
+
size: options.size || '1024x1024',
|
|
2112
|
+
response_format: options.response_format || 'url',
|
|
2113
|
+
user: agentId,
|
|
2114
|
+
model: modelName
|
|
2115
|
+
};
|
|
2116
|
+
const response = await fetch('https://api.openai.com/v1/images/generations', {
|
|
2117
|
+
method: 'POST',
|
|
2118
|
+
headers: {
|
|
2119
|
+
'Content-Type': 'application/json',
|
|
2120
|
+
'Authorization': `Bearer ${this.options.apiKeys.openai}`
|
|
2121
|
+
},
|
|
2122
|
+
body: JSON.stringify(requestBody)
|
|
2123
|
+
});
|
|
2124
|
+
const data = await response.json();
|
|
2125
|
+
if (!response.ok) throw new Error(data.error?.message || 'OpenAI image generation error');
|
|
2126
|
+
this._emit('image-generation-complete', { agentId, provider, prompt, result: data });
|
|
2127
|
+
return data.data[0].url;
|
|
2128
|
+
} else {
|
|
2129
|
+
throw new Error(`Provider ${provider} does not support image generation`);
|
|
2130
|
+
}
|
|
2131
|
+
} catch (error) {
|
|
2132
|
+
this._emit('image-generation-error', { agentId, error });
|
|
2133
|
+
this._handleError(error, 'IMAGE_GENERATION_ERROR', 'Failed to generate image');
|
|
2134
|
+
return null;
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
|
|
2138
|
+
/**
|
|
2139
|
+
* Edit an image based on a prompt using the agent's provider (OpenAI DALL-E)
|
|
2140
|
+
* Supports models: 'dalle-3', 'gpt-4o', 'gpt-4o-vision-preview'.
|
|
2141
|
+
* Model selection order: options.model > agent.config.model > 'dalle-3'
|
|
2142
|
+
*/
|
|
2143
|
+
async editImage(agentId, imageData, prompt, options = {}) {
|
|
2144
|
+
const agent = this.agents.get(agentId);
|
|
2145
|
+
if (!agent) {
|
|
2146
|
+
this._handleError(new Error(`Agent not found: ${agentId}`), 'AGENT_NOT_FOUND');
|
|
2147
|
+
return null;
|
|
2148
|
+
}
|
|
2149
|
+
const provider = agent.config.provider || this.options.defaultProvider;
|
|
2150
|
+
this._emit('image-edit-start', { agentId, provider, prompt, options });
|
|
2151
|
+
try {
|
|
2152
|
+
if (provider === 'openai') {
|
|
2153
|
+
// Model selection logic
|
|
2154
|
+
const modelName = (options.model || agent.config.model || 'dalle-3').trim();
|
|
2155
|
+
const requestBody = {
|
|
2156
|
+
image: imageData,
|
|
2157
|
+
prompt,
|
|
2158
|
+
n: options.n || 1,
|
|
2159
|
+
size: options.size || '1024x1024',
|
|
2160
|
+
response_format: options.response_format || 'url',
|
|
2161
|
+
user: agentId,
|
|
2162
|
+
model: modelName
|
|
2163
|
+
};
|
|
2164
|
+
const response = await fetch('https://api.openai.com/v1/images/edits', {
|
|
2165
|
+
method: 'POST',
|
|
2166
|
+
headers: {
|
|
2167
|
+
'Content-Type': 'application/json',
|
|
2168
|
+
'Authorization': `Bearer ${this.options.apiKeys.openai}`
|
|
2169
|
+
},
|
|
2170
|
+
body: JSON.stringify(requestBody)
|
|
2171
|
+
});
|
|
2172
|
+
const data = await response.json();
|
|
2173
|
+
if (!response.ok) throw new Error(data.error?.message || 'OpenAI image edit error');
|
|
2174
|
+
this._emit('image-edit-complete', { agentId, provider, prompt, result: data });
|
|
2175
|
+
return data.data[0].url;
|
|
2176
|
+
} else {
|
|
2177
|
+
throw new Error(`Provider ${provider} does not support image editing`);
|
|
2178
|
+
}
|
|
2179
|
+
} catch (error) {
|
|
2180
|
+
this._emit('image-edit-error', { agentId, error });
|
|
2181
|
+
this._handleError(error, 'IMAGE_EDIT_ERROR', 'Failed to edit image');
|
|
2182
|
+
return null;
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
|
|
2186
|
+
/**
|
|
2187
|
+
* Create a variation of an image using the agent's provider (OpenAI DALL-E)
|
|
2188
|
+
* Supports models: 'dalle-3', 'gpt-4o', 'gpt-4o-vision-preview'.
|
|
2189
|
+
* Model selection order: options.model > agent.config.model > 'dalle-3'
|
|
2190
|
+
*/
|
|
2191
|
+
async variationImage(agentId, imageData, options = {}) {
|
|
2192
|
+
const agent = this.agents.get(agentId);
|
|
2193
|
+
if (!agent) {
|
|
2194
|
+
this._handleError(new Error(`Agent not found: ${agentId}`), 'AGENT_NOT_FOUND');
|
|
2195
|
+
return null;
|
|
2196
|
+
}
|
|
2197
|
+
const provider = agent.config.provider || this.options.defaultProvider;
|
|
2198
|
+
this._emit('image-variation-start', { agentId, provider, options });
|
|
2199
|
+
try {
|
|
2200
|
+
if (provider === 'openai') {
|
|
2201
|
+
// Model selection logic
|
|
2202
|
+
const modelName = (options.model || agent.config.model || 'dalle-3').trim();
|
|
2203
|
+
const requestBody = {
|
|
2204
|
+
image: imageData,
|
|
2205
|
+
n: options.n || 1,
|
|
2206
|
+
size: options.size || '1024x1024',
|
|
2207
|
+
response_format: options.response_format || 'url',
|
|
2208
|
+
user: agentId,
|
|
2209
|
+
model: modelName
|
|
2210
|
+
};
|
|
2211
|
+
const response = await fetch('https://api.openai.com/v1/images/variations', {
|
|
2212
|
+
method: 'POST',
|
|
2213
|
+
headers: {
|
|
2214
|
+
'Content-Type': 'application/json',
|
|
2215
|
+
'Authorization': `Bearer ${this.options.apiKeys.openai}`
|
|
2216
|
+
},
|
|
2217
|
+
body: JSON.stringify(requestBody)
|
|
2218
|
+
});
|
|
2219
|
+
const data = await response.json();
|
|
2220
|
+
if (!response.ok) throw new Error(data.error?.message || 'OpenAI image variation error');
|
|
2221
|
+
this._emit('image-variation-complete', { agentId, provider, result: data });
|
|
2222
|
+
return data.data[0].url;
|
|
2223
|
+
} else {
|
|
2224
|
+
throw new Error(`Provider ${provider} does not support image variation`);
|
|
2225
|
+
}
|
|
2226
|
+
} catch (error) {
|
|
2227
|
+
this._emit('image-variation-error', { agentId, error });
|
|
2228
|
+
this._handleError(error, 'IMAGE_VARIATION_ERROR', 'Failed to create image variation');
|
|
2229
|
+
return null;
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
// Export to global scope
|
|
2235
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
2236
|
+
module.exports = Worksona;
|
|
2237
|
+
} else {
|
|
2238
|
+
global.Worksona = Worksona;
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
})(typeof window !== 'undefined' ? window : global);
|