@loxia-labs/loxia-autopilot-one 1.0.1 → 1.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (120) hide show
  1. package/README.md +44 -54
  2. package/bin/cli.js +1 -115
  3. package/bin/loxia-terminal-v2.js +3 -0
  4. package/bin/loxia-terminal.js +3 -0
  5. package/bin/start-with-terminal.js +3 -0
  6. package/package.json +15 -15
  7. package/scripts/install-scanners.js +1 -235
  8. package/src/analyzers/CSSAnalyzer.js +1 -297
  9. package/src/analyzers/ConfigValidator.js +1 -690
  10. package/src/analyzers/ESLintAnalyzer.js +1 -320
  11. package/src/analyzers/JavaScriptAnalyzer.js +1 -261
  12. package/src/analyzers/PrettierFormatter.js +1 -247
  13. package/src/analyzers/PythonAnalyzer.js +1 -266
  14. package/src/analyzers/SecurityAnalyzer.js +1 -729
  15. package/src/analyzers/TypeScriptAnalyzer.js +1 -247
  16. package/src/analyzers/codeCloneDetector/analyzer.js +1 -344
  17. package/src/analyzers/codeCloneDetector/detector.js +1 -203
  18. package/src/analyzers/codeCloneDetector/index.js +1 -160
  19. package/src/analyzers/codeCloneDetector/parser.js +1 -199
  20. package/src/analyzers/codeCloneDetector/reporter.js +1 -148
  21. package/src/analyzers/codeCloneDetector/scanner.js +1 -59
  22. package/src/core/agentPool.js +1 -1474
  23. package/src/core/agentScheduler.js +1 -2147
  24. package/src/core/contextManager.js +1 -709
  25. package/src/core/messageProcessor.js +1 -732
  26. package/src/core/orchestrator.js +1 -548
  27. package/src/core/stateManager.js +1 -877
  28. package/src/index.js +1 -631
  29. package/src/interfaces/cli.js +1 -549
  30. package/src/interfaces/terminal/__tests__/smoke/advancedFeatures.test.js +1 -0
  31. package/src/interfaces/terminal/__tests__/smoke/agentControl.test.js +1 -0
  32. package/src/interfaces/terminal/__tests__/smoke/agents.test.js +1 -0
  33. package/src/interfaces/terminal/__tests__/smoke/components.test.js +1 -0
  34. package/src/interfaces/terminal/__tests__/smoke/connection.test.js +1 -0
  35. package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +1 -0
  36. package/src/interfaces/terminal/__tests__/smoke/imports.test.js +1 -0
  37. package/src/interfaces/terminal/__tests__/smoke/messages.test.js +1 -0
  38. package/src/interfaces/terminal/__tests__/smoke/tools.test.js +1 -0
  39. package/src/interfaces/terminal/api/apiClient.js +1 -0
  40. package/src/interfaces/terminal/api/messageRouter.js +1 -0
  41. package/src/interfaces/terminal/api/session.js +1 -0
  42. package/src/interfaces/terminal/api/websocket.js +1 -0
  43. package/src/interfaces/terminal/components/AgentCreator.js +1 -0
  44. package/src/interfaces/terminal/components/AgentEditor.js +1 -0
  45. package/src/interfaces/terminal/components/AgentSwitcher.js +1 -0
  46. package/src/interfaces/terminal/components/ErrorBoundary.js +1 -0
  47. package/src/interfaces/terminal/components/ErrorPanel.js +1 -0
  48. package/src/interfaces/terminal/components/Header.js +1 -0
  49. package/src/interfaces/terminal/components/HelpPanel.js +1 -0
  50. package/src/interfaces/terminal/components/InputBox.js +1 -0
  51. package/src/interfaces/terminal/components/Layout.js +1 -0
  52. package/src/interfaces/terminal/components/LoadingSpinner.js +1 -0
  53. package/src/interfaces/terminal/components/MessageList.js +1 -0
  54. package/src/interfaces/terminal/components/MultilineTextInput.js +1 -0
  55. package/src/interfaces/terminal/components/SearchPanel.js +1 -0
  56. package/src/interfaces/terminal/components/SettingsPanel.js +1 -0
  57. package/src/interfaces/terminal/components/StatusBar.js +1 -0
  58. package/src/interfaces/terminal/components/TextInput.js +1 -0
  59. package/src/interfaces/terminal/config/agentEditorConstants.js +1 -0
  60. package/src/interfaces/terminal/config/constants.js +1 -0
  61. package/src/interfaces/terminal/index.js +1 -0
  62. package/src/interfaces/terminal/state/useAgentControl.js +1 -0
  63. package/src/interfaces/terminal/state/useAgents.js +1 -0
  64. package/src/interfaces/terminal/state/useConnection.js +1 -0
  65. package/src/interfaces/terminal/state/useMessages.js +1 -0
  66. package/src/interfaces/terminal/state/useTools.js +1 -0
  67. package/src/interfaces/terminal/utils/debugLogger.js +1 -0
  68. package/src/interfaces/terminal/utils/settingsStorage.js +1 -0
  69. package/src/interfaces/terminal/utils/theme.js +1 -0
  70. package/src/interfaces/webServer.js +1 -2162
  71. package/src/modules/fileExplorer/controller.js +1 -280
  72. package/src/modules/fileExplorer/index.js +1 -37
  73. package/src/modules/fileExplorer/middleware.js +1 -92
  74. package/src/modules/fileExplorer/routes.js +1 -125
  75. package/src/modules/fileExplorer/types.js +1 -44
  76. package/src/services/aiService.js +1 -1232
  77. package/src/services/apiKeyManager.js +1 -164
  78. package/src/services/benchmarkService.js +1 -366
  79. package/src/services/budgetService.js +1 -539
  80. package/src/services/contextInjectionService.js +1 -247
  81. package/src/services/conversationCompactionService.js +1 -637
  82. package/src/services/errorHandler.js +1 -810
  83. package/src/services/fileAttachmentService.js +1 -544
  84. package/src/services/modelRouterService.js +1 -366
  85. package/src/services/modelsService.js +1 -322
  86. package/src/services/qualityInspector.js +1 -796
  87. package/src/services/tokenCountingService.js +1 -536
  88. package/src/tools/agentCommunicationTool.js +1 -1344
  89. package/src/tools/agentDelayTool.js +1 -485
  90. package/src/tools/asyncToolManager.js +1 -604
  91. package/src/tools/baseTool.js +1 -800
  92. package/src/tools/browserTool.js +1 -920
  93. package/src/tools/cloneDetectionTool.js +1 -621
  94. package/src/tools/dependencyResolverTool.js +1 -1215
  95. package/src/tools/fileContentReplaceTool.js +1 -875
  96. package/src/tools/fileSystemTool.js +1 -1107
  97. package/src/tools/fileTreeTool.js +1 -853
  98. package/src/tools/imageTool.js +1 -901
  99. package/src/tools/importAnalyzerTool.js +1 -1060
  100. package/src/tools/jobDoneTool.js +1 -248
  101. package/src/tools/seekTool.js +1 -956
  102. package/src/tools/staticAnalysisTool.js +1 -1778
  103. package/src/tools/taskManagerTool.js +1 -2873
  104. package/src/tools/terminalTool.js +1 -2304
  105. package/src/tools/webTool.js +1 -1430
  106. package/src/types/agent.js +1 -519
  107. package/src/types/contextReference.js +1 -972
  108. package/src/types/conversation.js +1 -730
  109. package/src/types/toolCommand.js +1 -747
  110. package/src/utilities/attachmentValidator.js +1 -292
  111. package/src/utilities/configManager.js +1 -582
  112. package/src/utilities/constants.js +1 -722
  113. package/src/utilities/directoryAccessManager.js +1 -535
  114. package/src/utilities/fileProcessor.js +1 -307
  115. package/src/utilities/logger.js +1 -436
  116. package/src/utilities/tagParser.js +1 -1246
  117. package/src/utilities/toolConstants.js +1 -317
  118. package/web-ui/build/index.html +2 -2
  119. package/web-ui/build/static/{index-Dy2bYbOa.css → index-CClD1090.css} +1 -1
  120. package/web-ui/build/static/{index-CjkkcnFA.js → index-lCBai6dX.js} +66 -67
@@ -1,901 +1 @@
1
- /**
2
- * @file tools/imageTool.js
3
- * @description Tool for generating images using AI models (Flux, DALL-E, etc.)
4
- */
5
-
6
- import path from 'path';
7
- import os from 'os';
8
- import { promises as fs } from 'fs';
9
- import { BaseTool } from './baseTool.js';
10
-
11
- /**
12
- * Configuration constants for image generation
13
- */
14
- const IMAGE_CONFIG = {
15
- DEFAULT_MODEL: 'azure-openai-dalle3',
16
- DEFAULT_SIZE: '1024x1024',
17
- DEFAULT_QUALITY: 'standard',
18
- VALID_SIZES: ['256x256', '512x512', '1024x1024', '1024x1792', '1792x1024'],
19
- VALID_FORMATS: ['png', 'jpg', 'jpeg', 'webp'],
20
- MAX_CONCURRENT: 3,
21
- QUEUE_LIMIT: 10,
22
- TEMP_CLEANUP_MS: 3600000, // 1 hour
23
- MAX_PROMPT_LENGTH: 4000,
24
- DOWNLOAD_TIMEOUT: 60000 // 60 seconds
25
- };
26
-
27
- /**
28
- * ImageTool - Generate images using AI models
29
- * Supports queueing, async processing, and both temp/project directory storage
30
- */
31
- export class ImageTool extends BaseTool {
32
- constructor(config = {}, logger = null) {
33
- super(config, logger);
34
-
35
- // Override tool ID
36
- this.id = 'image-gen';
37
-
38
- // Job queue and tracking
39
- this.queue = [];
40
- this.currentJob = null;
41
- this.completedJobs = new Map();
42
- this.isProcessing = false;
43
-
44
- // AIService will be injected later
45
- this.aiService = null;
46
-
47
- // AgentPool will be injected later (for saving to conversation history)
48
- this.agentPool = null;
49
-
50
- // Temp directory for images
51
- this.tempDir = path.join(os.tmpdir(), 'loxia-images');
52
-
53
- // Cleanup timers
54
- this.cleanupTimers = new Map();
55
- }
56
-
57
- /**
58
- * Set AI service for image generation
59
- * @param {AIService} aiService - AI service instance
60
- */
61
- setAIService(aiService) {
62
- this.aiService = aiService;
63
- this.logger?.info('AI Service set for ImageTool');
64
- }
65
-
66
- /**
67
- * Set Agent Pool for saving results to conversation history
68
- * @param {AgentPool} agentPool - AgentPool instance
69
- */
70
- setAgentPool(agentPool) {
71
- this.agentPool = agentPool;
72
- this.logger?.info('AgentPool set for ImageTool');
73
- }
74
-
75
- /**
76
- * Get tool description for agent system prompt
77
- * @returns {string} Formatted tool description
78
- */
79
- getDescription() {
80
- return `Tool: Image Generator - Generate images using AI models
81
-
82
- **Purpose:** Generate images from text descriptions using DALL-E 3 via Azure OpenAI. Images are saved to files and displayed in the chat.
83
-
84
- **CRITICAL: Automatic Execution**
85
- - ANY \`\`\`json block with "toolId": "image-gen" will be EXECUTED IMMEDIATELY
86
- - ANY <image-gen> XML tag will be EXECUTED IMMEDIATELY
87
- - DO NOT show examples or ask permission - just output the command when you want to generate an image
88
- - If generation fails, output a NEW command with corrections - do NOT explain or show examples
89
-
90
- **Invocation Syntax:**
91
-
92
- XML Format (PREFERRED):
93
- <image-gen>
94
- <prompt>Detailed description of the image</prompt>
95
- <output-path>images/filename.png</output-path>
96
- <model>azure-openai-dalle3</model>
97
- <size>1024x1024</size>
98
- </image-gen>
99
-
100
- JSON Format (also works):
101
- \`\`\`json
102
- {
103
- "toolId": "image-gen",
104
- "parameters": {
105
- "prompt": "Detailed description",
106
- "outputPath": "images/filename.png",
107
- "model": "azure-openai-dalle3",
108
- "size": "1024x1024"
109
- }
110
- }
111
- \`\`\`
112
-
113
- **Parameters:**
114
- - **prompt** (string, required): Detailed description of the image to generate
115
- - **outputPath** (string, optional): Relative path for saving the image
116
- - If specified: Saves to project directory at this path (permanent)
117
- - If omitted: Saves to temp directory (auto-deleted after 1 hour)
118
- - **model** (string, optional): AI model to use. Default: "azure-openai-dalle3"
119
- - **size** (string, optional): Image size. Options: 256x256, 512x512, 1024x1024, 1024x1792, 1792x1024. Default: "1024x1024"
120
- - **quality** (string, optional): Image quality. Options: standard, hd. Default: "standard"
121
-
122
- **Storage Behavior:**
123
- - With outputPath: Saved to project directory (permanent)
124
- - Without outputPath: Saved to temp directory, auto-deleted after 1 hour
125
-
126
- **Usage Instructions:**
127
- 1. When user requests an image, output the <image-gen> tag IMMEDIATELY
128
- 2. DO NOT ask for permission or show examples first
129
- 3. DO NOT explain the format - just use it
130
- 4. If it fails, retry with a corrected command (no explanations)
131
- 5. Only generate ONE image at a time (no batch mode needed)
132
-
133
- **Correct Usage Pattern:**
134
-
135
- User: "create a sunset image"
136
- You: <image-gen>
137
- <prompt>A beautiful sunset over the ocean with vibrant orange and pink colors reflecting on calm waters</prompt>
138
- <output-path>images/sunset.png</output-path>
139
- </image-gen>
140
-
141
- **WRONG Usage Pattern (DO NOT DO THIS):**
142
-
143
- User: "create a sunset image"
144
- You: "I'll generate that image for you. Here's the command:
145
- \`\`\`xml
146
- <image-gen>...</image-gen>
147
- \`\`\`
148
- Do you want me to proceed?"
149
- ← This is WRONG because the command already executed when you showed it!
150
-
151
- **Required Parameters:**
152
- - prompt: Detailed, descriptive text (be creative and specific)
153
- - model: Always use "azure-openai-dalle3"
154
- - size: Use "1024x1024" for square, "1024x1792" for portrait, "1792x1024" for landscape
155
-
156
- **Optional Parameters:**
157
- - output-path: Relative path like "images/filename.png" (omit for temp file)
158
- - quality: "standard" or "hd" (default: standard)
159
-
160
- **Important Notes:**
161
- - Images take 15-30 seconds to generate
162
- - You'll get immediate confirmation with a job ID
163
- - The image appears automatically when complete
164
- - Be descriptive in prompts for better results
165
- - Maximum ${IMAGE_CONFIG.QUEUE_LIMIT} images in queue at once`;
166
- }
167
-
168
- /**
169
- * Parse image generation parameters
170
- * @param {string|Object} content - Raw content or parsed object
171
- * @returns {Object} Parsed parameters
172
- */
173
- parseParameters(content) {
174
- // Handle JSON format
175
- if (typeof content === 'object' && content !== null) {
176
- return this._parseJSONParams(content);
177
- }
178
-
179
- // Handle string format
180
- if (typeof content === 'string') {
181
- const trimmed = content.trim();
182
-
183
- // Try to parse as JSON first
184
- if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
185
- try {
186
- const parsed = JSON.parse(trimmed);
187
- return this._parseJSONParams(parsed);
188
- } catch (err) {
189
- // Not valid JSON, fall through to XML parsing
190
- }
191
- }
192
-
193
- // Parse as XML
194
- return this._parseXMLParams(content);
195
- }
196
-
197
- throw new Error('Invalid parameter format');
198
- }
199
-
200
- /**
201
- * Parse JSON parameters
202
- * @private
203
- */
204
- _parseJSONParams(obj) {
205
- // Handle parameters wrapper (when called via toolId/parameters structure)
206
- if (obj.parameters) {
207
- obj = obj.parameters;
208
- }
209
-
210
- // Check for batch mode
211
- if (obj.batch && Array.isArray(obj.batch)) {
212
- return {
213
- batch: true,
214
- images: obj.batch.map(img => this._parseImageParams(img))
215
- };
216
- }
217
-
218
- return {
219
- batch: false,
220
- images: [this._parseImageParams(obj)]
221
- };
222
- }
223
-
224
- /**
225
- * Parse XML parameters
226
- * @private
227
- */
228
- _parseXMLParams(content) {
229
- const params = { batch: false, images: [] };
230
-
231
- // Check for batch mode
232
- const batchMatch = /<batch>([\s\S]*?)<\/batch>/i.exec(content);
233
-
234
- if (batchMatch) {
235
- params.batch = true;
236
- const batchContent = batchMatch[1];
237
-
238
- // Extract individual <image> blocks
239
- const imageRegex = /<image>([\s\S]*?)<\/image>/gi;
240
- let match;
241
-
242
- while ((match = imageRegex.exec(batchContent)) !== null) {
243
- params.images.push(this._parseXMLImage(match[1]));
244
- }
245
- } else {
246
- // Single image mode
247
- params.images.push(this._parseXMLImage(content));
248
- }
249
-
250
- if (params.images.length === 0) {
251
- throw new Error('No valid image parameters found');
252
- }
253
-
254
- return params;
255
- }
256
-
257
- /**
258
- * Parse single image parameters from object
259
- * @private
260
- */
261
- _parseImageParams(obj) {
262
- const outputPath = obj.outputPath || obj['output-path'] || null;
263
-
264
- return {
265
- prompt: obj.prompt || '',
266
- outputPath: outputPath,
267
- saveToProject: outputPath !== null, // If path specified, save to project
268
- model: obj.model || IMAGE_CONFIG.DEFAULT_MODEL,
269
- size: obj.size || IMAGE_CONFIG.DEFAULT_SIZE,
270
- quality: obj.quality || IMAGE_CONFIG.DEFAULT_QUALITY
271
- };
272
- }
273
-
274
- /**
275
- * Parse single image parameters from XML string
276
- * @private
277
- */
278
- _parseXMLImage(xmlContent) {
279
- const extractTag = (tag) => {
280
- const regex = new RegExp(`<${tag}>([\\s\\S]*?)<\\/${tag}>`, 'i');
281
- const match = regex.exec(xmlContent);
282
- return match ? match[1].trim() : null;
283
- };
284
-
285
- const outputPath = extractTag('output-path') || null;
286
-
287
- return {
288
- prompt: extractTag('prompt') || '',
289
- outputPath: outputPath,
290
- saveToProject: outputPath !== null, // If path specified, save to project
291
- model: extractTag('model') || IMAGE_CONFIG.DEFAULT_MODEL,
292
- size: extractTag('size') || IMAGE_CONFIG.DEFAULT_SIZE,
293
- quality: extractTag('quality') || IMAGE_CONFIG.DEFAULT_QUALITY
294
- };
295
- }
296
-
297
- /**
298
- * Execute image generation
299
- * @param {Object|string} params - Parsed parameters object OR raw XML/JSON string
300
- * @param {Object} context - Execution context
301
- * @returns {Promise<Object>} Execution result
302
- */
303
- async execute(params, context = {}) {
304
- try {
305
- const { agentId, projectDir, directoryAccess, sessionId } = context;
306
-
307
- // Auto-detect and parse string inputs (from TagParser)
308
- if (typeof params === 'string') {
309
- this.logger?.info('ImageTool: Auto-parsing string parameters');
310
- params = this.parseParameters(params);
311
- }
312
-
313
- // Validate parameters
314
- this._validateParameters(params);
315
-
316
- // Queue images
317
- const jobIds = [];
318
-
319
- for (const imageParams of params.images) {
320
- // Create job
321
- const jobId = this._generateJobId();
322
-
323
- const job = {
324
- jobId,
325
- agentId,
326
- sessionId,
327
- prompt: imageParams.prompt,
328
- outputPath: imageParams.outputPath,
329
- saveToProject: imageParams.saveToProject,
330
- model: imageParams.model,
331
- size: imageParams.size,
332
- quality: imageParams.quality,
333
- projectDir: projectDir || process.cwd(),
334
- directoryAccess,
335
- status: 'queued',
336
- createdAt: new Date().toISOString()
337
- };
338
-
339
- // Check queue limit
340
- if (this.queue.length >= IMAGE_CONFIG.QUEUE_LIMIT) {
341
- return {
342
- success: false,
343
- error: `Queue limit reached (${IMAGE_CONFIG.QUEUE_LIMIT} images). Please wait for current jobs to complete.`,
344
- queueLength: this.queue.length
345
- };
346
- }
347
-
348
- this.queue.push(job);
349
- jobIds.push(jobId);
350
-
351
- this.logger?.info(`Image generation job queued: ${jobId}`, {
352
- prompt: imageParams.prompt.substring(0, 50) + '...',
353
- queuePosition: this.queue.length
354
- });
355
- }
356
-
357
- // Start processing if not already running
358
- if (!this.isProcessing) {
359
- this._processQueue().catch(err => {
360
- this.logger?.error('Queue processing error:', err);
361
- });
362
- }
363
-
364
- // Return immediate response
365
- return {
366
- success: true,
367
- jobIds,
368
- queueLength: this.queue.length,
369
- message: params.batch
370
- ? `${jobIds.length} images queued for generation`
371
- : 'Image queued for generation',
372
- estimatedWaitTime: this._estimateWaitTime()
373
- };
374
-
375
- } catch (error) {
376
- this.logger?.error('Image generation error:', error);
377
- return {
378
- success: false,
379
- error: error.message
380
- };
381
- }
382
- }
383
-
384
- /**
385
- * Validate parameters
386
- * @private
387
- */
388
- _validateParameters(params) {
389
- if (!params.images || params.images.length === 0) {
390
- throw new Error('No images specified');
391
- }
392
-
393
- for (const img of params.images) {
394
- if (!img.prompt || img.prompt.trim().length === 0) {
395
- throw new Error('Image prompt is required');
396
- }
397
-
398
- if (img.prompt.length > IMAGE_CONFIG.MAX_PROMPT_LENGTH) {
399
- throw new Error(`Prompt too long (max ${IMAGE_CONFIG.MAX_PROMPT_LENGTH} characters)`);
400
- }
401
-
402
- if (img.size && !IMAGE_CONFIG.VALID_SIZES.includes(img.size)) {
403
- throw new Error(`Invalid size: ${img.size}. Valid sizes: ${IMAGE_CONFIG.VALID_SIZES.join(', ')}`);
404
- }
405
-
406
- if (img.outputPath) {
407
- const ext = path.extname(img.outputPath).toLowerCase().replace('.', '');
408
- if (ext && !IMAGE_CONFIG.VALID_FORMATS.includes(ext)) {
409
- throw new Error(`Invalid format: ${ext}. Valid formats: ${IMAGE_CONFIG.VALID_FORMATS.join(', ')}`);
410
- }
411
- }
412
- }
413
- }
414
-
415
- /**
416
- * Process the image generation queue
417
- * @private
418
- */
419
- async _processQueue() {
420
- if (this.isProcessing) {
421
- return;
422
- }
423
-
424
- this.isProcessing = true;
425
-
426
- while (this.queue.length > 0) {
427
- const job = this.queue.shift();
428
- this.currentJob = job;
429
-
430
- this.logger?.info(`Processing image generation job: ${job.jobId}`);
431
-
432
- try {
433
- job.status = 'processing';
434
-
435
- // Generate the image
436
- const result = await this._generateImage(job);
437
-
438
- job.status = 'completed';
439
- job.result = result;
440
- job.completedAt = new Date().toISOString();
441
-
442
- // Store completed job
443
- this.completedJobs.set(job.jobId, job);
444
-
445
- // Broadcast result via WebSocket
446
- if (global.loxiaWebServer && job.sessionId) {
447
- // Determine which URL to use: local saved file or temporary AI URL
448
- let imageUrl;
449
- let isTemporary = false;
450
-
451
- if (result.savedToDisk && result.resolvedOutputPath) {
452
- // Image was saved successfully - use our server endpoint
453
- imageUrl = this._convertToWebUrl(result.resolvedOutputPath, job.sessionId);
454
- } else if (result.temporaryUrl) {
455
- // Download failed - use temporary AI-generated URL (expires in ~1 hour)
456
- imageUrl = result.temporaryUrl;
457
- isTemporary = true;
458
- }
459
-
460
- global.loxiaWebServer.broadcastToSession(job.sessionId, {
461
- type: 'imageGenerated',
462
- agentId: job.agentId,
463
- jobId: job.jobId,
464
- imageUrl,
465
- localPath: result.resolvedOutputPath,
466
- prompt: job.prompt,
467
- success: true,
468
- isTemporary, // Indicates if URL will expire
469
- savedToDisk: result.savedToDisk,
470
- downloadError: result.downloadError, // Include error if save failed
471
- timestamp: job.completedAt
472
- });
473
-
474
- this.logger?.info('Image generation broadcast sent', {
475
- jobId: job.jobId,
476
- imageUrl,
477
- localPath: result.resolvedOutputPath,
478
- savedToDisk: result.savedToDisk,
479
- isTemporary
480
- });
481
-
482
- // Save image result to conversation history for persistence
483
- if (this.agentPool && job.agentId) {
484
- try {
485
- const agent = await this.agentPool.getAgent(job.agentId);
486
- if (agent) {
487
- // Build message content with warnings if applicable
488
- let content = `Image generated: ${job.prompt}`;
489
-
490
- if (isTemporary) {
491
- content += '\n\n⚠️ **Warning:** Image is using a temporary URL (expires in ~1 hour). Failed to save to disk.';
492
- if (result.downloadError) {
493
- content += `\n**Error:** ${result.downloadError}`;
494
- }
495
- }
496
-
497
- // Create image message for conversation history
498
- const imageMessage = {
499
- id: `img-result-${job.jobId}`,
500
- role: 'assistant',
501
- content,
502
- timestamp: job.completedAt,
503
- imageUrl, // CRITICAL: Include imageUrl so it persists across page refreshes
504
- type: 'image-result',
505
- toolId: 'image-gen',
506
- status: 'completed',
507
- isTemporary: isTemporary || false,
508
- savedToDisk: result.savedToDisk !== false
509
- };
510
-
511
- // Add to full conversation
512
- agent.conversations.full.messages.push(imageMessage);
513
- agent.conversations.full.lastUpdated = job.completedAt;
514
-
515
- // Add to current model conversation if exists
516
- if (agent.currentModel && agent.conversations[agent.currentModel]) {
517
- agent.conversations[agent.currentModel].messages.push(imageMessage);
518
- agent.conversations[agent.currentModel].lastUpdated = job.completedAt;
519
- }
520
-
521
- // Update agent activity
522
- agent.lastActivity = job.completedAt;
523
-
524
- // Persist agent state to save conversation history
525
- await this.agentPool.persistAgentState(job.agentId);
526
-
527
- this.logger?.info('Image result saved to conversation history', {
528
- agentId: job.agentId,
529
- jobId: job.jobId,
530
- messageId: imageMessage.id
531
- });
532
- }
533
- } catch (error) {
534
- this.logger?.error('Failed to save image result to conversation history', {
535
- error: error.message,
536
- agentId: job.agentId,
537
- jobId: job.jobId
538
- });
539
- }
540
- }
541
- }
542
-
543
- this.logger?.info(`Image generation completed: ${job.jobId}`, {
544
- outputPath: result.resolvedOutputPath
545
- });
546
-
547
- } catch (error) {
548
- this.logger?.error(`Image generation failed: ${job.jobId}`, error);
549
-
550
- job.status = 'failed';
551
- job.error = error.message;
552
- job.completedAt = new Date().toISOString();
553
-
554
- this.completedJobs.set(job.jobId, job);
555
-
556
- // Broadcast error to specific session
557
- if (global.loxiaWebServer && job.sessionId) {
558
- global.loxiaWebServer.broadcastToSession(job.sessionId, {
559
- type: 'imageGenerated',
560
- jobId: job.jobId,
561
- agentId: job.agentId,
562
- prompt: job.prompt,
563
- success: false,
564
- error: error.message,
565
- timestamp: job.completedAt
566
- });
567
-
568
- this.logger?.info('Image generation error broadcast sent', {
569
- jobId: job.jobId,
570
- sessionId: job.sessionId,
571
- error: error.message
572
- });
573
- }
574
-
575
- // Save error message to conversation history
576
- if (this.agentPool && job.agentId) {
577
- try {
578
- const agent = await this.agentPool.getAgent(job.agentId);
579
- if (agent) {
580
- // Create error message for conversation history
581
- const errorMessage = {
582
- id: `img-error-${job.jobId}`,
583
- role: 'system',
584
- content: `❌ Image generation failed: ${error.message}\n\n**Prompt:** ${job.prompt}`,
585
- timestamp: job.completedAt,
586
- type: 'error',
587
- toolId: 'image-gen',
588
- status: 'failed',
589
- jobId: job.jobId
590
- };
591
-
592
- // Add to full conversation
593
- agent.conversations.full.messages.push(errorMessage);
594
- agent.conversations.full.lastUpdated = job.completedAt;
595
-
596
- // Add to current model conversation if exists
597
- if (agent.currentModel && agent.conversations[agent.currentModel]) {
598
- agent.conversations[agent.currentModel].messages.push(errorMessage);
599
- agent.conversations[agent.currentModel].lastUpdated = job.completedAt;
600
- }
601
-
602
- // Update agent activity
603
- agent.lastActivity = job.completedAt;
604
-
605
- // Persist agent state to save conversation history
606
- await this.agentPool.persistAgentState(job.agentId);
607
-
608
- this.logger?.info('Image error saved to conversation history', {
609
- agentId: job.agentId,
610
- jobId: job.jobId,
611
- error: error.message
612
- });
613
- }
614
- } catch (historyError) {
615
- this.logger?.error('Failed to save image error to conversation history', {
616
- error: historyError.message,
617
- agentId: job.agentId,
618
- jobId: job.jobId
619
- });
620
- }
621
- }
622
- }
623
- }
624
-
625
- this.isProcessing = false;
626
- this.currentJob = null;
627
- }
628
-
629
- /**
630
- * Generate a single image
631
- * @private
632
- */
633
- async _generateImage(job) {
634
- // Check if AI service is available
635
- if (!this.aiService) {
636
- throw new Error('AI service not available. Image generation requires AI service.');
637
- }
638
-
639
- // Resolve output path
640
- const resolvedOutputPath = await this._resolveOutputPath(job);
641
-
642
- // Ensure directory exists
643
- const outputDir = path.dirname(resolvedOutputPath);
644
- await fs.mkdir(outputDir, { recursive: true });
645
-
646
- this.logger?.info(`Generating image with ${job.model}`, {
647
- size: job.size,
648
- quality: job.quality
649
- });
650
-
651
- // Call AI service to generate image
652
- const options = {
653
- model: job.model,
654
- size: job.size,
655
- quality: job.quality,
656
- responseFormat: 'url', // Get URL to download
657
- sessionId: job.sessionId // CRITICAL: Pass sessionId for API key retrieval
658
- };
659
-
660
- const aiResult = await this.aiService.generateImage(job.prompt, options);
661
-
662
- // AIService returns: { url, b64_json, model, requestId, revisedPrompt }
663
- // Handle both 'url' and 'imageUrl' response formats
664
- const imageUrl = aiResult?.url || aiResult?.imageUrl;
665
-
666
- if (!imageUrl) {
667
- throw new Error('No image URL received from AI service');
668
- }
669
-
670
- this.logger?.info(`Image URL received: ${imageUrl.substring(0, 50)}...`);
671
-
672
- // Try to download and save image
673
- let savedToDisk = false;
674
- let downloadError = null;
675
-
676
- try {
677
- await this._downloadImage(imageUrl, resolvedOutputPath);
678
- savedToDisk = true;
679
-
680
- // Schedule cleanup if temp file
681
- if (!job.saveToProject) {
682
- this._scheduleCleanup(resolvedOutputPath, job.jobId);
683
- }
684
- } catch (error) {
685
- // Download failed, but we still have the temporary URL
686
- downloadError = error.message;
687
- this.logger?.warn(`Failed to save image to disk, will use temporary URL: ${error.message}`);
688
- }
689
-
690
- return {
691
- jobId: job.jobId,
692
- prompt: job.prompt,
693
- outputPath: job.outputPath,
694
- resolvedOutputPath: savedToDisk ? resolvedOutputPath : null,
695
- temporaryUrl: imageUrl, // AI-generated URL (valid for ~1 hour)
696
- savedToDisk,
697
- downloadError,
698
- success: true, // Image was generated successfully
699
- model: aiResult.model || job.model,
700
- size: job.size,
701
- usage: aiResult.usage
702
- };
703
- }
704
-
705
- /**
706
- * Resolve output path (temp or project directory)
707
- * @private
708
- */
709
- async _resolveOutputPath(job) {
710
- if (job.saveToProject) {
711
- // Save to project directory
712
- const projectDir = job.projectDir || process.cwd();
713
-
714
- let outputPath = job.outputPath;
715
- if (!outputPath) {
716
- // Auto-generate filename
717
- const timestamp = Date.now();
718
- outputPath = `images/generated-${timestamp}.png`;
719
- }
720
-
721
- const resolvedPath = path.isAbsolute(outputPath)
722
- ? path.normalize(outputPath)
723
- : path.normalize(path.join(projectDir, outputPath));
724
-
725
- // Security: Check for path traversal
726
- if (!resolvedPath.startsWith(path.normalize(projectDir))) {
727
- throw new Error('Path traversal detected');
728
- }
729
-
730
- // Check directory access if provided
731
- if (job.directoryAccess) {
732
- // Simple check - file must be within allowed directories
733
- // Full implementation would use DirectoryAccessManager
734
- const relativePath = path.relative(projectDir, resolvedPath);
735
- if (relativePath.startsWith('..')) {
736
- throw new Error('Access denied: path outside project directory');
737
- }
738
- }
739
-
740
- return resolvedPath;
741
- } else {
742
- // Save to temp directory
743
- await fs.mkdir(this.tempDir, { recursive: true });
744
-
745
- let filename = job.outputPath
746
- ? path.basename(job.outputPath)
747
- : `generated-${job.jobId}.png`;
748
-
749
- return path.join(this.tempDir, filename);
750
- }
751
- }
752
-
753
- /**
754
- * Download image from URL
755
- * @private
756
- */
757
- async _downloadImage(imageUrl, outputPath) {
758
- try {
759
- const response = await fetch(imageUrl, {
760
- signal: AbortSignal.timeout(IMAGE_CONFIG.DOWNLOAD_TIMEOUT)
761
- });
762
-
763
- if (!response.ok) {
764
- throw new Error(`Failed to download image: HTTP ${response.status}`);
765
- }
766
-
767
- const buffer = Buffer.from(await response.arrayBuffer());
768
- await fs.writeFile(outputPath, buffer);
769
-
770
- this.logger?.info(`Image saved to: ${outputPath}`);
771
-
772
- } catch (error) {
773
- if (error.name === 'TimeoutError') {
774
- throw new Error('Image download timeout');
775
- } else if (error.name === 'TypeError') {
776
- throw new Error(`Network error: ${error.message}`);
777
- } else {
778
- throw new Error(`Download failed: ${error.message}`);
779
- }
780
- }
781
- }
782
-
783
- /**
784
- * Schedule cleanup of temp file
785
- * @private
786
- */
787
- _scheduleCleanup(filePath, jobId) {
788
- const timer = setTimeout(async () => {
789
- try {
790
- await fs.unlink(filePath);
791
- this.logger?.debug(`Cleaned up temp image: ${filePath}`);
792
- this.cleanupTimers.delete(jobId);
793
- } catch (error) {
794
- // File might already be deleted, ignore
795
- }
796
- }, IMAGE_CONFIG.TEMP_CLEANUP_MS);
797
-
798
- this.cleanupTimers.set(jobId, timer);
799
- }
800
-
801
- /**
802
- * Convert local file path to web-accessible URL
803
- * @private
804
- */
805
- _convertToWebUrl(localPath, sessionId) {
806
- // Extract just the filename from the path
807
- const filename = path.basename(localPath);
808
-
809
- // Construct web URL using the image serving endpoint
810
- // Assumes web server runs on port 8080 (can be made configurable)
811
- const port = global.loxiaWebServer?.port || 8080;
812
- let host = global.loxiaWebServer?.host || 'localhost';
813
-
814
- // Convert 0.0.0.0 (server binding address) to localhost (browser-accessible)
815
- // Browsers cannot connect to 0.0.0.0, even though servers can bind to it
816
- if (host === '0.0.0.0') {
817
- host = 'localhost';
818
- }
819
-
820
- return `http://${host}:${port}/api/images/${sessionId}/${filename}`;
821
- }
822
-
823
- /**
824
- * Estimate wait time based on queue
825
- * @private
826
- */
827
- _estimateWaitTime() {
828
- const avgGenerationTime = 30; // seconds
829
- const queuePosition = this.queue.length;
830
-
831
- if (queuePosition === 0) {
832
- return '~30 seconds';
833
- }
834
-
835
- const estimatedSeconds = queuePosition * avgGenerationTime;
836
- const minutes = Math.floor(estimatedSeconds / 60);
837
- const seconds = estimatedSeconds % 60;
838
-
839
- if (minutes > 0) {
840
- return `~${minutes}m ${seconds}s`;
841
- }
842
- return `~${seconds}s`;
843
- }
844
-
845
- /**
846
- * Generate unique job ID
847
- * @private
848
- */
849
- _generateJobId() {
850
- return `img-${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
851
- }
852
-
853
- /**
854
- * Get job status
855
- * @param {string} jobId - Job ID
856
- * @returns {Object} Job status
857
- */
858
- getJobStatus(jobId) {
859
- // Check completed jobs
860
- if (this.completedJobs.has(jobId)) {
861
- return this.completedJobs.get(jobId);
862
- }
863
-
864
- // Check current job
865
- if (this.currentJob && this.currentJob.jobId === jobId) {
866
- return this.currentJob;
867
- }
868
-
869
- // Check queue
870
- const queuedJob = this.queue.find(job => job.jobId === jobId);
871
- if (queuedJob) {
872
- return queuedJob;
873
- }
874
-
875
- return {
876
- jobId,
877
- status: 'not_found'
878
- };
879
- }
880
-
881
- /**
882
- * Cleanup on shutdown
883
- */
884
- async cleanup() {
885
- this.logger?.info('Shutting down ImageTool');
886
-
887
- // Clear all cleanup timers
888
- for (const timer of this.cleanupTimers.values()) {
889
- clearTimeout(timer);
890
- }
891
- this.cleanupTimers.clear();
892
-
893
- // Mark queued jobs as cancelled
894
- for (const job of this.queue) {
895
- job.status = 'cancelled';
896
- }
897
- this.queue = [];
898
- }
899
- }
900
-
901
- export default ImageTool;
1
+ const a0_0x52463a=a0_0x7f4b;(function(_0x2fcc2e,_0x555ab1){const _0x27ada7=a0_0x7f4b,_0x5acb28=_0x2fcc2e();while(!![]){try{const _0x59ca7b=parseInt(_0x27ada7(0x128))/0x1*(parseInt(_0x27ada7(0x106))/0x2)+-parseInt(_0x27ada7(0x168))/0x3+-parseInt(_0x27ada7(0x16a))/0x4*(-parseInt(_0x27ada7(0xf7))/0x5)+-parseInt(_0x27ada7(0x134))/0x6*(parseInt(_0x27ada7(0x13f))/0x7)+parseInt(_0x27ada7(0x13c))/0x8+parseInt(_0x27ada7(0x121))/0x9*(-parseInt(_0x27ada7(0x175))/0xa)+parseInt(_0x27ada7(0x12a))/0xb*(-parseInt(_0x27ada7(0x13a))/0xc);if(_0x59ca7b===_0x555ab1)break;else _0x5acb28['push'](_0x5acb28['shift']());}catch(_0x163340){_0x5acb28['push'](_0x5acb28['shift']());}}}(a0_0x5cbb,0x802d1));import a0_0x37cfd3 from'path';import a0_0x1096a7 from'os';import{promises as a0_0xc44517}from'fs';function a0_0x5cbb(){const _0x132d50=['DgvTCerPCG','CgfYC2vqyxjHBwv0zxjZ','yMf0y2G','C2v0','BgfZDefJDgL2Axr5','z2v0qwDLBNq','Bg94Awfxzwjtzxj2zxi','C2f2zwruB0rPC2S','CMvZDwX0','oti2odK4BLr1Cuvj','BwTKAxi','mJmWodbSDeTAqLa','Dg9tDhjPBMC','4P2mieLTywDLigDLBMvYyxrPB24GzMfPBgvKoIa','vKfmsurFrK9stufuuW','Bg9Nz2vY','ywDLBNrjza','B2jQzwn0','x3zHBgLKyxrLugfYyw1LDgvYCW','tufyx1bst01qvf9mru5hveG','y29TCgXLDgvKsM9ICW','CMvZB2X2zwrpDxrWDxrqyxrO','mtbwqKPxs3i','tM8GDMfSAwqGAw1Hz2uGCgfYyw1LDgvYCYbMB3vUza','y29UDMvYC2f0Aw9UCW','C2v0quLtzxj2AwnL','zMLUza','ChjVBxb0','FJmWihnLy29Uzhm','zMfPBgvK','x3bHCNnLsw1Hz2vqyxjHBxm','Bg9JywXOB3n0','zxjYB3i','vevnuf9dtevbtLvqx01t','BgfZDfvWzgf0zwq','ntyWv3nowuvV','Cg9YDa','C3rHDhvZ','Aw5MBW','vhLWzuvYCM9Y','DxjS','y29TCgXLDgvK','y29TCgXLDgvKqxq','Bg94AweTAw1Hz2vZ','BwfW','x2vZDgLTyxrLv2fPDfrPBwu','revgqvvmvf9nt0rfta','D2vICa','BM93','CxvHBgL0Eq','ntC4nJm4qKnQDerq','sw1Hz2uGz2vUzxjHDgvKoIa','x2DLBMvYyxrLsw1Hz2u','AM9PBG','zg93BMXVywrfCNjVCG','igLTywDLCYbXDwv1zwqGzM9YigDLBMvYyxrPB24','yNjVywrJyxn0vg9tzxnZAw9U','sw1Hz2vuB29SoIbbDxrVlxbHCNnPBMCGC3rYAw5NihbHCMfTzxrLCNm','uxvLDwuGBgLTAxqGCMvHy2HLzcaO','sw1Hz2uGChjVBxb0igLZihjLCxvPCMvK','CxvLDwu','sw52ywXPzcbMB3jTyxq6ia','y2XLyxi','C2L6zq','CgfYyw1LDgvYCW','x3bHCNnLsLnptLbHCMfTCW','zgvSzxrL','sw1Hz2uGC2f2zwqGDg86ia','uvvfvuvFteLnsvq','zxHLy3v0zq','Aw1NlwvYCM9Ylq','x3bYB2nLC3nrDwv1zq','z2v0rgvZy3jPChrPB24','revgqvvmvf9tsvPf','x3jLC29SDMvpDxrWDxrqyxrO','B3v0Chv0ugf0Aa','x2DLBMvYyxrLsM9Iswq','ntq5ntC5nKfyDffJva','sw52ywXPzcbWyxjHBwv0zxiGzM9YBwf0','BMfTzq','q2XLyw5Lzcb1Ccb0zw1WigLTywDLoIa','y3vYCMvUDe1VzgvS','sw1Hz2uGz2vUzxjHDgLVBIbLCNjVCIbICM9HzgnHC3qGC2vUDa','ChjVAMvJDerPCG','mLzMsMXJBW','zNvSBa','mtm1mdaZq1r1r2Tc','C2vZC2LVBKLK','z2vUzxjHDgvjBwfNzq','C3vIC3rYAw5N','sw1Hz2uGzg93BMXVywqGDgLTzw91Da','zxH0BMfTzq','vKfmsurFu0LArvm','y2XLyw51CfrPBwvYCW','CxvLDwvK','rMfPBgvKihrVihnHDMuGAw1Hz2uGzxjYB3iGDg8Gy29UDMvYC2f0Aw9UigHPC3rVCNK','mJqWnK93tvfksW','ywLtzxj2AwnL','AM9Iswq','uxvLDwuGChjVy2vZC2LUzYbLCNjVCJO','quKGu2vYDMLJzsbZzxqGzM9YieLTywDLvg9VBa','BM9YBwfSAxPL','mtjkwLPeCKe','ChvZAa','mJqYnZeZnKrgvLDdyq','zxHLyW','sw1Hz2uGvvjmihjLy2vPDMvKoIa','mti0nM1ICNbZsq','Dw5SAw5R','C3rHCNrZv2L0Aa','vgLTzw91DevYCM9Y','Aw1Hz2vZ','BwvZC2fNzq','Aw1Hz2uTz2vU','D2fYBG','Dg9ju09tDhjPBMC','nteYEduXmG','Ahr0CdOVlW','sw1Hz2uGzxjYB3iGC2f2zwqGDg8Gy29UDMvYC2f0Aw9UigHPC3rVCNK','DxnHz2u','yxnZAxn0yw50','BgvUz3rO','Ag9ZDa','lI4U','u2H1DhrPBMCGzg93BIbjBwfNzvrVB2W','y3vYCMvUDePVyG','ywDLBNrqB29S','y3DK','sw1Hz2uGCMvZDwX0ihnHDMvKihrVignVBNzLCNnHDgLVBIbOAxn0B3j5','AxnqCM9JzxnZAw5N','Aw1Hz2vhzw5LCMf0zwq','BwvZC2fNzxm','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','Bw9KzwW','z2vUzxjHDgvKlq','zgLYzwn0B3j5qwnJzxnZ','yMfZzw5HBwu','sw1Hz2uGz2vUzxjHDgLVBIbQB2iGCxvLDwvKoIa','CgvYC2LZDefNzw50u3rHDgu'];a0_0x5cbb=function(){return _0x132d50;};return a0_0x5cbb();}import{BaseTool}from'./baseTool.js';const IMAGE_CONFIG={'DEFAULT_MODEL':'azure-openai-dalle3','DEFAULT_SIZE':'1024x1024','DEFAULT_QUALITY':'standard','VALID_SIZES':['256x256',a0_0x52463a(0x148),'1024x1024','1024x1792','1792x1024'],'VALID_FORMATS':['png','jpg','jpeg',a0_0x52463a(0x103)],'MAX_CONCURRENT':0x3,'QUEUE_LIMIT':0xa,'TEMP_CLEANUP_MS':0x36ee80,'MAX_PROMPT_LENGTH':0xfa0,'DOWNLOAD_TIMEOUT':0xea60};function a0_0x7f4b(_0x1c44f0,_0x3a1ff3){_0x1c44f0=_0x1c44f0-0xeb;const _0x5cbb5a=a0_0x5cbb();let _0x7f4b1f=_0x5cbb5a[_0x1c44f0];if(a0_0x7f4b['FukXIE']===undefined){var _0x55dd97=function(_0x4851c6){const _0x1a4b12='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x37cfd3='',_0x1096a7='';for(let _0xc44517=0x0,_0x45a911,_0x2251a7,_0x4c7900=0x0;_0x2251a7=_0x4851c6['charAt'](_0x4c7900++);~_0x2251a7&&(_0x45a911=_0xc44517%0x4?_0x45a911*0x40+_0x2251a7:_0x2251a7,_0xc44517++%0x4)?_0x37cfd3+=String['fromCharCode'](0xff&_0x45a911>>(-0x2*_0xc44517&0x6)):0x0){_0x2251a7=_0x1a4b12['indexOf'](_0x2251a7);}for(let _0x1b0685=0x0,_0x2257bd=_0x37cfd3['length'];_0x1b0685<_0x2257bd;_0x1b0685++){_0x1096a7+='%'+('00'+_0x37cfd3['charCodeAt'](_0x1b0685)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x1096a7);};a0_0x7f4b['qxDIIs']=_0x55dd97,a0_0x7f4b['sbgefD']={},a0_0x7f4b['FukXIE']=!![];}const _0x43e098=_0x5cbb5a[0x0],_0x23a54c=_0x1c44f0+_0x43e098,_0x50bae4=a0_0x7f4b['sbgefD'][_0x23a54c];return!_0x50bae4?(_0x7f4b1f=a0_0x7f4b['qxDIIs'](_0x7f4b1f),a0_0x7f4b['sbgefD'][_0x23a54c]=_0x7f4b1f):_0x7f4b1f=_0x50bae4,_0x7f4b1f;}export class ImageTool extends BaseTool{constructor(_0x45a911={},_0x2251a7=null){const _0x5b80ae=a0_0x52463a;super(_0x45a911,_0x2251a7),this['id']=_0x5b80ae(0x145),this[_0x5b80ae(0x110)]=[],this['currentJob']=null,this[_0x5b80ae(0x173)]=new Map(),this[_0x5b80ae(0x155)]=![],this[_0x5b80ae(0x135)]=null,this[_0x5b80ae(0x152)]=null,this['tempDir']=a0_0x37cfd3[_0x5b80ae(0x109)](a0_0x1096a7['tmpdir'](),_0x5b80ae(0xff)),this['cleanupTimers']=new Map();}[a0_0x52463a(0xed)](_0x4c7900){const _0x1f5564=a0_0x52463a;this['aiService']=_0x4c7900,this['logger']?.['info'](_0x1f5564(0x138));}['setAgentPool'](_0x1b0685){this['agentPool']=_0x1b0685,this['logger']?.['info']('AgentPool\x20set\x20for\x20ImageTool');}[a0_0x52463a(0x11c)](){const _0x5f182a=a0_0x52463a;return _0x5f182a(0x158)+IMAGE_CONFIG['QUEUE_LIMIT']+'\x20images\x20in\x20queue\x20at\x20once';}[a0_0x52463a(0x160)](_0x2257bd){const _0x5c9a73=a0_0x52463a;if(typeof _0x2257bd===_0x5c9a73(0x170)&&_0x2257bd!==null)return this[_0x5c9a73(0x115)](_0x2257bd);if(typeof _0x2257bd==='string'){const _0x44b6a4=_0x2257bd['trim']();if(_0x44b6a4[_0x5c9a73(0x141)]('{')||_0x44b6a4[_0x5c9a73(0x141)]('['))try{const _0x1e3b81=JSON['parse'](_0x44b6a4);return this[_0x5c9a73(0x115)](_0x1e3b81);}catch(_0x558df0){}return this['_parseXMLParams'](_0x2257bd);}throw new Error(_0x5c9a73(0x122));}['_parseJSONParams'](_0x464f0f){const _0x4a7807=a0_0x52463a;_0x464f0f[_0x4a7807(0x114)]&&(_0x464f0f=_0x464f0f[_0x4a7807(0x114)]);if(_0x464f0f['batch']&&Array['isArray'](_0x464f0f['batch']))return{'batch':!![],'images':_0x464f0f[_0x4a7807(0x161)][_0x4a7807(0x100)](_0x12cbd0=>this['_parseImageParams'](_0x12cbd0))};return{'batch':![],'images':[this[_0x4a7807(0xf2)](_0x464f0f)]};}['_parseXMLParams'](_0x4d7107){const _0x2c0899=a0_0x52463a,_0x5899a6={'batch':![],'images':[]},_0x5aff81=/<batch>([\s\S]*?)<\/batch>/i['exec'](_0x4d7107);if(_0x5aff81){_0x5899a6[_0x2c0899(0x161)]=!![];const _0x85829d=_0x5aff81[0x1],_0x2966e5=/<image>([\s\S]*?)<\/image>/gi;let _0x9d26ee;while((_0x9d26ee=_0x2966e5[_0x2c0899(0x13d)](_0x85829d))!==null){_0x5899a6['images']['push'](this['_parseXMLImage'](_0x9d26ee[0x1]));}}else _0x5899a6['images'][_0x2c0899(0x13b)](this['_parseXMLImage'](_0x4d7107));if(_0x5899a6[_0x2c0899(0x143)]['length']===0x0)throw new Error(_0x2c0899(0xeb));return _0x5899a6;}['_parseImageParams'](_0x1aebfe){const _0x24c707=a0_0x52463a,_0x353085=_0x1aebfe['outputPath']||_0x1aebfe['output-path']||null;return{'prompt':_0x1aebfe['prompt']||'','outputPath':_0x353085,'saveToProject':_0x353085!==null,'model':_0x1aebfe['model']||IMAGE_CONFIG[_0x24c707(0x102)],'size':_0x1aebfe['size']||IMAGE_CONFIG['DEFAULT_SIZE'],'quality':_0x1aebfe['quality']||IMAGE_CONFIG['DEFAULT_QUALITY']};}['_parseXMLImage'](_0x226403){const _0x190d84=a0_0x52463a,_0x1fea40=_0x568d54=>{const _0x4061c3=a0_0x7f4b,_0x3482ca=new RegExp('<'+_0x568d54+'>([\x5cs\x5cS]*?)<\x5c/'+_0x568d54+'>','i'),_0x151b04=_0x3482ca[_0x4061c3(0x13d)](_0x226403);return _0x151b04?_0x151b04[0x1]['trim']():null;},_0x4e0843=_0x1fea40('output-path')||null;return{'prompt':_0x1fea40(_0x190d84(0xef))||'','outputPath':_0x4e0843,'saveToProject':_0x4e0843!==null,'model':_0x1fea40(_0x190d84(0x159))||IMAGE_CONFIG[_0x190d84(0x102)],'size':_0x1fea40('size')||IMAGE_CONFIG[_0x190d84(0x11d)],'quality':_0x1fea40('quality')||IMAGE_CONFIG['DEFAULT_QUALITY']};}async[a0_0x52463a(0x119)](_0x5b1ef4,_0x36ad97={}){const _0x6b33a9=a0_0x52463a;try{const {agentId:_0x1cd4d6,projectDir:_0x1e53d9,directoryAccess:_0x2eef7a,sessionId:_0x21c134}=_0x36ad97;typeof _0x5b1ef4==='string'&&(this[_0x6b33a9(0x16e)]?.[_0x6b33a9(0xfa)](_0x6b33a9(0x10d)),_0x5b1ef4=this['parseParameters'](_0x5b1ef4));this[_0x6b33a9(0x171)](_0x5b1ef4);const _0x4bbdaa=[];for(const _0x369439 of _0x5b1ef4[_0x6b33a9(0x143)]){const _0x407a3d=this['_generateJobId'](),_0xc1881={'jobId':_0x407a3d,'agentId':_0x1cd4d6,'sessionId':_0x21c134,'prompt':_0x369439[_0x6b33a9(0xef)],'outputPath':_0x369439['outputPath'],'saveToProject':_0x369439['saveToProject'],'model':_0x369439[_0x6b33a9(0x159)],'size':_0x369439[_0x6b33a9(0x113)],'quality':_0x369439[_0x6b33a9(0x105)],'projectDir':_0x1e53d9||process[_0x6b33a9(0x153)](),'directoryAccess':_0x2eef7a,'status':_0x6b33a9(0x132),'createdAt':new Date()['toISOString']()};if(this['queue']['length']>=IMAGE_CONFIG[_0x6b33a9(0x118)])return{'success':![],'error':_0x6b33a9(0x10e)+IMAGE_CONFIG['QUEUE_LIMIT']+'\x20images).\x20Please\x20wait\x20for\x20current\x20jobs\x20to\x20complete.','queueLength':this[_0x6b33a9(0x110)][_0x6b33a9(0x14d)]};this['queue']['push'](_0xc1881),_0x4bbdaa['push'](_0x407a3d),this[_0x6b33a9(0x16e)]?.[_0x6b33a9(0xfa)](_0x6b33a9(0x15d)+_0x407a3d,{'prompt':_0x369439[_0x6b33a9(0xef)]['substring'](0x0,0x32)+'...','queuePosition':this[_0x6b33a9(0x110)][_0x6b33a9(0x14d)]});}return!this[_0x6b33a9(0x155)]&&this[_0x6b33a9(0x11b)]()['catch'](_0x1c8d19=>{const _0x3e1291=_0x6b33a9;this[_0x3e1291(0x16e)]?.[_0x3e1291(0xf4)](_0x3e1291(0x137),_0x1c8d19);}),{'success':!![],'jobIds':_0x4bbdaa,'queueLength':this[_0x6b33a9(0x110)]['length'],'message':_0x5b1ef4['batch']?_0x4bbdaa['length']+_0x6b33a9(0x10b):'Image\x20queued\x20for\x20generation','estimatedWaitTime':this[_0x6b33a9(0x101)]()};}catch(_0x400e74){return this[_0x6b33a9(0x16e)]?.['error']('Image\x20generation\x20error:',_0x400e74),{'success':![],'error':_0x400e74[_0x6b33a9(0x144)]};}}[a0_0x52463a(0x171)](_0x435061){const _0x28a715=a0_0x52463a;if(!_0x435061['images']||_0x435061[_0x28a715(0x143)][_0x28a715(0x14d)]===0x0)throw new Error('No\x20images\x20specified');for(const _0x59a80f of _0x435061[_0x28a715(0x143)]){if(!_0x59a80f[_0x28a715(0xef)]||_0x59a80f['prompt']['trim']()['length']===0x0)throw new Error(_0x28a715(0x10f));if(_0x59a80f[_0x28a715(0xef)][_0x28a715(0x14d)]>IMAGE_CONFIG['MAX_PROMPT_LENGTH'])throw new Error('Prompt\x20too\x20long\x20(max\x20'+IMAGE_CONFIG[_0x28a715(0x172)]+'\x20characters)');if(_0x59a80f['size']&&!IMAGE_CONFIG['VALID_SIZES']['includes'](_0x59a80f['size']))throw new Error('Invalid\x20size:\x20'+_0x59a80f[_0x28a715(0x113)]+'.\x20Valid\x20sizes:\x20'+IMAGE_CONFIG[_0x28a715(0x130)]['join'](',\x20'));if(_0x59a80f['outputPath']){const _0x2a9a2e=a0_0x37cfd3[_0x28a715(0x12f)](_0x59a80f['outputPath'])['toLowerCase']()['replace']('.','');if(_0x2a9a2e&&!IMAGE_CONFIG[_0x28a715(0x16d)]['includes'](_0x2a9a2e))throw new Error(_0x28a715(0x111)+_0x2a9a2e+'.\x20Valid\x20formats:\x20'+IMAGE_CONFIG['VALID_FORMATS']['join'](',\x20'));}}}async['_processQueue'](){const _0x178bfc=a0_0x52463a;if(this['isProcessing'])return;this[_0x178bfc(0x155)]=!![];while(this[_0x178bfc(0x110)][_0x178bfc(0x14d)]>0x0){const _0x4a5b1a=this[_0x178bfc(0x110)]['shift']();this[_0x178bfc(0x151)]=_0x4a5b1a,this['logger']?.[_0x178bfc(0xfa)]('Processing\x20image\x20generation\x20job:\x20'+_0x4a5b1a[_0x178bfc(0x136)]);try{_0x4a5b1a[_0x178bfc(0xf9)]='processing';const _0x2d6479=await this[_0x178bfc(0x108)](_0x4a5b1a);_0x4a5b1a[_0x178bfc(0xf9)]=_0x178bfc(0xfd),_0x4a5b1a[_0x178bfc(0x167)]=_0x2d6479,_0x4a5b1a['completedAt']=new Date()['toISOString'](),this[_0x178bfc(0x173)]['set'](_0x4a5b1a['jobId'],_0x4a5b1a);if(global[_0x178bfc(0x165)]&&_0x4a5b1a['sessionId']){let _0x545beb,_0x4e32c5=![];if(_0x2d6479['savedToDisk']&&_0x2d6479[_0x178bfc(0x174)])_0x545beb=this['_convertToWebUrl'](_0x2d6479['resolvedOutputPath'],_0x4a5b1a['sessionId']);else _0x2d6479['temporaryUrl']&&(_0x545beb=_0x2d6479['temporaryUrl'],_0x4e32c5=!![]);global[_0x178bfc(0x165)][_0x178bfc(0x10c)](_0x4a5b1a['sessionId'],{'type':_0x178bfc(0x156),'agentId':_0x4a5b1a['agentId'],'jobId':_0x4a5b1a[_0x178bfc(0x136)],'imageUrl':_0x545beb,'localPath':_0x2d6479['resolvedOutputPath'],'prompt':_0x4a5b1a['prompt'],'success':!![],'isTemporary':_0x4e32c5,'savedToDisk':_0x2d6479['savedToDisk'],'downloadError':_0x2d6479[_0x178bfc(0x10a)],'timestamp':_0x4a5b1a['completedAt']}),this[_0x178bfc(0x16e)]?.[_0x178bfc(0xfa)]('Image\x20generation\x20broadcast\x20sent',{'jobId':_0x4a5b1a[_0x178bfc(0x136)],'imageUrl':_0x545beb,'localPath':_0x2d6479['resolvedOutputPath'],'savedToDisk':_0x2d6479[_0x178bfc(0x166)],'isTemporary':_0x4e32c5});if(this[_0x178bfc(0x152)]&&_0x4a5b1a[_0x178bfc(0x16f)])try{const _0x57f048=await this[_0x178bfc(0x152)][_0x178bfc(0x164)](_0x4a5b1a['agentId']);if(_0x57f048){let _0x3bfded=_0x178bfc(0x107)+_0x4a5b1a['prompt'];_0x4e32c5&&(_0x3bfded+='\x0a\x0a⚠️\x20**Warning:**\x20Image\x20is\x20using\x20a\x20temporary\x20URL\x20(expires\x20in\x20~1\x20hour).\x20Failed\x20to\x20save\x20to\x20disk.',_0x2d6479['downloadError']&&(_0x3bfded+='\x0a**Error:**\x20'+_0x2d6479['downloadError']));const _0xa16c61={'id':'img-result-'+_0x4a5b1a[_0x178bfc(0x136)],'role':_0x178bfc(0x14c),'content':_0x3bfded,'timestamp':_0x4a5b1a['completedAt'],'imageUrl':_0x545beb,'type':'image-result','toolId':'image-gen','status':'completed','isTemporary':_0x4e32c5||![],'savedToDisk':_0x2d6479[_0x178bfc(0x166)]!==![]};_0x57f048[_0x178bfc(0xec)]['full']['messages']['push'](_0xa16c61),_0x57f048[_0x178bfc(0xec)]['full'][_0x178bfc(0xf6)]=_0x4a5b1a[_0x178bfc(0xfe)],_0x57f048['currentModel']&&_0x57f048[_0x178bfc(0xec)][_0x57f048[_0x178bfc(0x125)]]&&(_0x57f048['conversations'][_0x57f048['currentModel']]['messages']['push'](_0xa16c61),_0x57f048[_0x178bfc(0xec)][_0x57f048['currentModel']][_0x178bfc(0xf6)]=_0x4a5b1a['completedAt']),_0x57f048[_0x178bfc(0x163)]=_0x4a5b1a[_0x178bfc(0xfe)],await this['agentPool'][_0x178bfc(0x15e)](_0x4a5b1a[_0x178bfc(0x16f)]),this['logger']?.[_0x178bfc(0xfa)](_0x178bfc(0x154),{'agentId':_0x4a5b1a[_0x178bfc(0x16f)],'jobId':_0x4a5b1a['jobId'],'messageId':_0xa16c61['id']});}}catch(_0x4f5753){this['logger']?.['error']('Failed\x20to\x20save\x20image\x20result\x20to\x20conversation\x20history',{'error':_0x4f5753['message'],'agentId':_0x4a5b1a[_0x178bfc(0x16f)],'jobId':_0x4a5b1a[_0x178bfc(0x136)]});}}this[_0x178bfc(0x16e)]?.['info']('Image\x20generation\x20completed:\x20'+_0x4a5b1a[_0x178bfc(0x136)],{'outputPath':_0x2d6479[_0x178bfc(0x174)]});}catch(_0x2ff904){this['logger']?.[_0x178bfc(0xf4)]('Image\x20generation\x20failed:\x20'+_0x4a5b1a[_0x178bfc(0x136)],_0x2ff904),_0x4a5b1a[_0x178bfc(0xf9)]='failed',_0x4a5b1a[_0x178bfc(0xf4)]=_0x2ff904['message'],_0x4a5b1a['completedAt']=new Date()[_0x178bfc(0x147)](),this[_0x178bfc(0x173)][_0x178bfc(0x162)](_0x4a5b1a[_0x178bfc(0x136)],_0x4a5b1a);global[_0x178bfc(0x165)]&&_0x4a5b1a[_0x178bfc(0x12b)]&&(global['loxiaWebServer'][_0x178bfc(0x10c)](_0x4a5b1a[_0x178bfc(0x12b)],{'type':_0x178bfc(0x156),'jobId':_0x4a5b1a[_0x178bfc(0x136)],'agentId':_0x4a5b1a[_0x178bfc(0x16f)],'prompt':_0x4a5b1a['prompt'],'success':![],'error':_0x2ff904[_0x178bfc(0x144)],'timestamp':_0x4a5b1a['completedAt']}),this['logger']?.[_0x178bfc(0xfa)](_0x178bfc(0x126),{'jobId':_0x4a5b1a[_0x178bfc(0x136)],'sessionId':_0x4a5b1a[_0x178bfc(0x12b)],'error':_0x2ff904['message']}));if(this[_0x178bfc(0x152)]&&_0x4a5b1a['agentId'])try{const _0x1ac9f4=await this['agentPool'][_0x178bfc(0x164)](_0x4a5b1a[_0x178bfc(0x16f)]);if(_0x1ac9f4){const _0x4b65b4={'id':_0x178bfc(0x11a)+_0x4a5b1a['jobId'],'role':'system','content':_0x178bfc(0x16c)+_0x2ff904['message']+'\x0a\x0a**Prompt:**\x20'+_0x4a5b1a[_0x178bfc(0xef)],'timestamp':_0x4a5b1a['completedAt'],'type':_0x178bfc(0xf4),'toolId':_0x178bfc(0x145),'status':_0x178bfc(0xf1),'jobId':_0x4a5b1a[_0x178bfc(0x136)]};_0x1ac9f4['conversations']['full']['messages'][_0x178bfc(0x13b)](_0x4b65b4),_0x1ac9f4['conversations'][_0x178bfc(0x129)][_0x178bfc(0xf6)]=_0x4a5b1a[_0x178bfc(0xfe)],_0x1ac9f4['currentModel']&&_0x1ac9f4[_0x178bfc(0xec)][_0x1ac9f4[_0x178bfc(0x125)]]&&(_0x1ac9f4[_0x178bfc(0xec)][_0x1ac9f4[_0x178bfc(0x125)]][_0x178bfc(0x157)]['push'](_0x4b65b4),_0x1ac9f4[_0x178bfc(0xec)][_0x1ac9f4[_0x178bfc(0x125)]][_0x178bfc(0xf6)]=_0x4a5b1a['completedAt']),_0x1ac9f4[_0x178bfc(0x163)]=_0x4a5b1a['completedAt'],await this[_0x178bfc(0x152)]['persistAgentState'](_0x4a5b1a['agentId']),this['logger']?.['info'](_0x178bfc(0x14a),{'agentId':_0x4a5b1a[_0x178bfc(0x16f)],'jobId':_0x4a5b1a['jobId'],'error':_0x2ff904[_0x178bfc(0x144)]});}}catch(_0x225974){this['logger']?.[_0x178bfc(0xf4)](_0x178bfc(0x133),{'error':_0x225974[_0x178bfc(0x144)],'agentId':_0x4a5b1a[_0x178bfc(0x16f)],'jobId':_0x4a5b1a['jobId']});}}}this['isProcessing']=![],this[_0x178bfc(0x151)]=null;}async[a0_0x52463a(0x108)](_0x123a6d){const _0x22b623=a0_0x52463a;if(!this[_0x22b623(0x135)])throw new Error('AI\x20service\x20not\x20available.\x20Image\x20generation\x20requires\x20AI\x20service.');const _0x118b4a=await this[_0x22b623(0x11e)](_0x123a6d),_0x1c07e0=a0_0x37cfd3['dirname'](_0x118b4a);await a0_0xc44517['mkdir'](_0x1c07e0,{'recursive':!![]}),this['logger']?.['info']('Generating\x20image\x20with\x20'+_0x123a6d[_0x22b623(0x159)],{'size':_0x123a6d['size'],'quality':_0x123a6d['quality']});const _0x478d77={'model':_0x123a6d['model'],'size':_0x123a6d[_0x22b623(0x113)],'quality':_0x123a6d[_0x22b623(0x105)],'responseFormat':'url','sessionId':_0x123a6d['sessionId']},_0x7d6c6e=await this[_0x22b623(0x135)][_0x22b623(0x12c)](_0x123a6d[_0x22b623(0xef)],_0x478d77),_0x1c7059=_0x7d6c6e?.[_0x22b623(0xfc)]||_0x7d6c6e?.['imageUrl'];if(!_0x1c7059)throw new Error('No image URL received from AI service');this[_0x22b623(0x16e)]?.['info'](_0x22b623(0x13e)+_0x1c7059['substring'](0x0,0x32)+_0x22b623(0x14f));let _0x2f3c5a=![],_0x3c555c=null;try{await this['_downloadImage'](_0x1c7059,_0x118b4a),_0x2f3c5a=!![],!_0x123a6d['saveToProject']&&this['_scheduleCleanup'](_0x118b4a,_0x123a6d['jobId']);}catch(_0x5a1d26){_0x3c555c=_0x5a1d26['message'],this[_0x22b623(0x16e)]?.[_0x22b623(0x146)]('Failed\x20to\x20save\x20image\x20to\x20disk,\x20will\x20use\x20temporary\x20URL:\x20'+_0x5a1d26[_0x22b623(0x144)]);}return{'jobId':_0x123a6d[_0x22b623(0x136)],'prompt':_0x123a6d[_0x22b623(0xef)],'outputPath':_0x123a6d['outputPath'],'resolvedOutputPath':_0x2f3c5a?_0x118b4a:null,'temporaryUrl':_0x1c7059,'savedToDisk':_0x2f3c5a,'downloadError':_0x3c555c,'success':!![],'model':_0x7d6c6e[_0x22b623(0x159)]||_0x123a6d['model'],'size':_0x123a6d[_0x22b623(0x113)],'usage':_0x7d6c6e[_0x22b623(0x14b)]};}async[a0_0x52463a(0x11e)](_0x38f2f0){const _0x1e9d3e=a0_0x52463a;if(_0x38f2f0['saveToProject']){const _0x169b91=_0x38f2f0[_0x1e9d3e(0x127)]||process['cwd']();let _0x14e56f=_0x38f2f0[_0x1e9d3e(0x11f)];if(!_0x14e56f){const _0x189dc9=Date[_0x1e9d3e(0x104)]();_0x14e56f='images/generated-'+_0x189dc9+'.png';}const _0x52b16a=a0_0x37cfd3['isAbsolute'](_0x14e56f)?a0_0x37cfd3[_0x1e9d3e(0x139)](_0x14e56f):a0_0x37cfd3['normalize'](a0_0x37cfd3['join'](_0x169b91,_0x14e56f));if(!_0x52b16a[_0x1e9d3e(0x141)](a0_0x37cfd3['normalize'](_0x169b91)))throw new Error('Path\x20traversal\x20detected');if(_0x38f2f0[_0x1e9d3e(0x15b)]){const _0x5aed67=a0_0x37cfd3['relative'](_0x169b91,_0x52b16a);if(_0x5aed67['startsWith']('..'))throw new Error('Access\x20denied:\x20path\x20outside\x20project\x20directory');}return _0x52b16a;}else{await a0_0xc44517[_0x1e9d3e(0x169)](this['tempDir'],{'recursive':!![]});let _0x371f2a=_0x38f2f0[_0x1e9d3e(0x11f)]?a0_0x37cfd3[_0x1e9d3e(0x15c)](_0x38f2f0[_0x1e9d3e(0x11f)]):_0x1e9d3e(0x15a)+_0x38f2f0['jobId']+'.png';return a0_0x37cfd3['join'](this[_0x1e9d3e(0x15f)],_0x371f2a);}}async['_downloadImage'](_0x4cfcdf,_0x440016){const _0x4f5a75=a0_0x52463a;try{const _0x424814=await fetch(_0x4cfcdf,{'signal':AbortSignal['timeout'](IMAGE_CONFIG['DOWNLOAD_TIMEOUT'])});if(!_0x424814['ok'])throw new Error('Failed\x20to\x20download\x20image:\x20HTTP\x20'+_0x424814['status']);const _0x58add6=Buffer['from'](await _0x424814['arrayBuffer']());await a0_0xc44517['writeFile'](_0x440016,_0x58add6),this[_0x4f5a75(0x16e)]?.[_0x4f5a75(0xfa)](_0x4f5a75(0x117)+_0x440016);}catch(_0x399a5f){if(_0x399a5f[_0x4f5a75(0x123)]===_0x4f5a75(0x142))throw new Error(_0x4f5a75(0x12e));else{if(_0x399a5f[_0x4f5a75(0x123)]===_0x4f5a75(0xfb))throw new Error('Network\x20error:\x20'+_0x399a5f[_0x4f5a75(0x144)]);else throw new Error('Download\x20failed:\x20'+_0x399a5f['message']);}}}['_scheduleCleanup'](_0x1b4ff1,_0xe5a2e3){const _0x57ff07=a0_0x52463a,_0x16d9f5=setTimeout(async()=>{const _0x43cf35=a0_0x7f4b;try{await a0_0xc44517[_0x43cf35(0x140)](_0x1b4ff1),this['logger']?.['debug'](_0x43cf35(0x124)+_0x1b4ff1),this['cleanupTimers'][_0x43cf35(0x116)](_0xe5a2e3);}catch(_0x270fcb){}},IMAGE_CONFIG[_0x57ff07(0xf5)]);this[_0x57ff07(0x131)][_0x57ff07(0x162)](_0xe5a2e3,_0x16d9f5);}['_convertToWebUrl'](_0xb7c1f0,_0x441bdf){const _0x38c902=a0_0x52463a,_0x5e1be4=a0_0x37cfd3['basename'](_0xb7c1f0),_0xb1a03d=global[_0x38c902(0x165)]?.[_0x38c902(0xf8)]||0x1f90;let _0x3c5e9a=global['loxiaWebServer']?.[_0x38c902(0x14e)]||_0x38c902(0xf3);return _0x3c5e9a==='0.0.0.0'&&(_0x3c5e9a='localhost'),_0x38c902(0x149)+_0x3c5e9a+':'+_0xb1a03d+'/api/images/'+_0x441bdf+'/'+_0x5e1be4;}[a0_0x52463a(0x101)](){const _0x706e40=a0_0x52463a,_0x208100=0x1e,_0x2819ac=this[_0x706e40(0x110)][_0x706e40(0x14d)];if(_0x2819ac===0x0)return _0x706e40(0xf0);const _0x3cdbdf=_0x2819ac*_0x208100,_0x325d5f=Math['floor'](_0x3cdbdf/0x3c),_0x2011f6=_0x3cdbdf%0x3c;if(_0x325d5f>0x0)return'~'+_0x325d5f+'m\x20'+_0x2011f6+'s';return'~'+_0x2011f6+'s';}[a0_0x52463a(0x120)](){const _0x9327cc=a0_0x52463a;return'img-'+Date['now']()+'-'+Math['random']()[_0x9327cc(0x16b)](0x24)[_0x9327cc(0x12d)](0x2,0x9);}['getJobStatus'](_0x5e92dd){const _0xb2cc4a=a0_0x52463a;if(this['completedJobs']['has'](_0x5e92dd))return this[_0xb2cc4a(0x173)]['get'](_0x5e92dd);if(this['currentJob']&&this['currentJob'][_0xb2cc4a(0x136)]===_0x5e92dd)return this['currentJob'];const _0x36897f=this[_0xb2cc4a(0x110)][_0xb2cc4a(0xee)](_0x5e28b6=>_0x5e28b6[_0xb2cc4a(0x136)]===_0x5e92dd);if(_0x36897f)return _0x36897f;return{'jobId':_0x5e92dd,'status':'not_found'};}async['cleanup'](){const _0x183970=a0_0x52463a;this[_0x183970(0x16e)]?.[_0x183970(0xfa)](_0x183970(0x150));for(const _0x13d07b of this['cleanupTimers']['values']()){clearTimeout(_0x13d07b);}this[_0x183970(0x131)][_0x183970(0x112)]();for(const _0x1f1f6c of this['queue']){_0x1f1f6c[_0x183970(0xf9)]='cancelled';}this['queue']=[];}}export default ImageTool;