@loxia-labs/loxia-autopilot-one 1.0.1 → 1.0.3
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/README.md +44 -54
- package/bin/cli.js +1 -115
- package/bin/loxia-terminal-v2.js +3 -0
- package/bin/loxia-terminal.js +3 -0
- package/bin/start-with-terminal.js +3 -0
- package/package.json +14 -15
- package/scripts/install-scanners.js +1 -235
- package/src/analyzers/CSSAnalyzer.js +1 -297
- package/src/analyzers/ConfigValidator.js +1 -690
- package/src/analyzers/ESLintAnalyzer.js +1 -320
- package/src/analyzers/JavaScriptAnalyzer.js +1 -261
- package/src/analyzers/PrettierFormatter.js +1 -247
- package/src/analyzers/PythonAnalyzer.js +1 -266
- package/src/analyzers/SecurityAnalyzer.js +1 -729
- package/src/analyzers/TypeScriptAnalyzer.js +1 -247
- package/src/analyzers/codeCloneDetector/analyzer.js +1 -344
- package/src/analyzers/codeCloneDetector/detector.js +1 -203
- package/src/analyzers/codeCloneDetector/index.js +1 -160
- package/src/analyzers/codeCloneDetector/parser.js +1 -199
- package/src/analyzers/codeCloneDetector/reporter.js +1 -148
- package/src/analyzers/codeCloneDetector/scanner.js +1 -59
- package/src/core/agentPool.js +1 -1474
- package/src/core/agentScheduler.js +1 -2147
- package/src/core/contextManager.js +1 -709
- package/src/core/messageProcessor.js +1 -732
- package/src/core/orchestrator.js +1 -548
- package/src/core/stateManager.js +1 -877
- package/src/index.js +1 -631
- package/src/interfaces/cli.js +1 -549
- package/src/interfaces/terminal/__tests__/smoke/advancedFeatures.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/agentControl.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/agents.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/components.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/connection.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/enhancements.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/imports.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/messages.test.js +1 -0
- package/src/interfaces/terminal/__tests__/smoke/tools.test.js +1 -0
- package/src/interfaces/terminal/api/apiClient.js +1 -0
- package/src/interfaces/terminal/api/messageRouter.js +1 -0
- package/src/interfaces/terminal/api/session.js +1 -0
- package/src/interfaces/terminal/api/websocket.js +1 -0
- package/src/interfaces/terminal/components/AgentCreator.js +1 -0
- package/src/interfaces/terminal/components/AgentEditor.js +1 -0
- package/src/interfaces/terminal/components/AgentSwitcher.js +1 -0
- package/src/interfaces/terminal/components/ErrorBoundary.js +1 -0
- package/src/interfaces/terminal/components/ErrorPanel.js +1 -0
- package/src/interfaces/terminal/components/Header.js +1 -0
- package/src/interfaces/terminal/components/HelpPanel.js +1 -0
- package/src/interfaces/terminal/components/InputBox.js +1 -0
- package/src/interfaces/terminal/components/Layout.js +1 -0
- package/src/interfaces/terminal/components/LoadingSpinner.js +1 -0
- package/src/interfaces/terminal/components/MessageList.js +1 -0
- package/src/interfaces/terminal/components/MultilineTextInput.js +1 -0
- package/src/interfaces/terminal/components/SearchPanel.js +1 -0
- package/src/interfaces/terminal/components/SettingsPanel.js +1 -0
- package/src/interfaces/terminal/components/StatusBar.js +1 -0
- package/src/interfaces/terminal/components/TextInput.js +1 -0
- package/src/interfaces/terminal/config/agentEditorConstants.js +1 -0
- package/src/interfaces/terminal/config/constants.js +1 -0
- package/src/interfaces/terminal/index.js +1 -0
- package/src/interfaces/terminal/state/useAgentControl.js +1 -0
- package/src/interfaces/terminal/state/useAgents.js +1 -0
- package/src/interfaces/terminal/state/useConnection.js +1 -0
- package/src/interfaces/terminal/state/useMessages.js +1 -0
- package/src/interfaces/terminal/state/useTools.js +1 -0
- package/src/interfaces/terminal/utils/debugLogger.js +1 -0
- package/src/interfaces/terminal/utils/settingsStorage.js +1 -0
- package/src/interfaces/terminal/utils/theme.js +1 -0
- package/src/interfaces/webServer.js +1 -2162
- package/src/modules/fileExplorer/controller.js +1 -280
- package/src/modules/fileExplorer/index.js +1 -37
- package/src/modules/fileExplorer/middleware.js +1 -92
- package/src/modules/fileExplorer/routes.js +1 -125
- package/src/modules/fileExplorer/types.js +1 -44
- package/src/services/aiService.js +1 -1232
- package/src/services/apiKeyManager.js +1 -164
- package/src/services/benchmarkService.js +1 -366
- package/src/services/budgetService.js +1 -539
- package/src/services/contextInjectionService.js +1 -247
- package/src/services/conversationCompactionService.js +1 -637
- package/src/services/errorHandler.js +1 -810
- package/src/services/fileAttachmentService.js +1 -544
- package/src/services/modelRouterService.js +1 -366
- package/src/services/modelsService.js +1 -322
- package/src/services/qualityInspector.js +1 -796
- package/src/services/tokenCountingService.js +1 -536
- package/src/tools/agentCommunicationTool.js +1 -1344
- package/src/tools/agentDelayTool.js +1 -485
- package/src/tools/asyncToolManager.js +1 -604
- package/src/tools/baseTool.js +1 -800
- package/src/tools/browserTool.js +1 -920
- package/src/tools/cloneDetectionTool.js +1 -621
- package/src/tools/dependencyResolverTool.js +1 -1215
- package/src/tools/fileContentReplaceTool.js +1 -875
- package/src/tools/fileSystemTool.js +1 -1107
- package/src/tools/fileTreeTool.js +1 -853
- package/src/tools/imageTool.js +1 -901
- package/src/tools/importAnalyzerTool.js +1 -1060
- package/src/tools/jobDoneTool.js +1 -248
- package/src/tools/seekTool.js +1 -956
- package/src/tools/staticAnalysisTool.js +1 -1778
- package/src/tools/taskManagerTool.js +1 -2873
- package/src/tools/terminalTool.js +1 -2304
- package/src/tools/webTool.js +1 -1430
- package/src/types/agent.js +1 -519
- package/src/types/contextReference.js +1 -972
- package/src/types/conversation.js +1 -730
- package/src/types/toolCommand.js +1 -747
- package/src/utilities/attachmentValidator.js +1 -292
- package/src/utilities/configManager.js +1 -582
- package/src/utilities/constants.js +1 -722
- package/src/utilities/directoryAccessManager.js +1 -535
- package/src/utilities/fileProcessor.js +1 -307
- package/src/utilities/logger.js +1 -436
- package/src/utilities/tagParser.js +1 -1246
- package/src/utilities/toolConstants.js +1 -317
- package/web-ui/build/index.html +2 -2
- package/web-ui/build/static/{index-Dy2bYbOa.css → index-CClD1090.css} +1 -1
- package/web-ui/build/static/{index-CjkkcnFA.js → index-lCBai6dX.js} +66 -67
package/src/tools/imageTool.js
CHANGED
|
@@ -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
|
+
function a0_0x158f(){const _0x150768=['mvnmEunbsG','mtqZodi1odHmBxH0BeS','x2DLBMvYyxrLsw1Hz2u','uhjVy2vZC2LUzYbPBwfNzsbNzw5LCMf0Aw9UigPVyJOG','mc4WlJaUma','cIOQrxjYB3i6kIOG','zgvIDwC','ywDLBNrjza','zNvSBa','y3vYCMvUDe1VzgvS','zxjYB3i','C2v0','quKGu2vYDMLJzsbZzxqGzM9YieLTywDLvg9VBa','y2XLyxi','igLTywDLCYbPBIbXDwv1zsbHDcbVBMnL','lI4U','BM9YBwfSAxPL','yxnZAxn0yw50','B2jQzwn0','Bg9JywXOB3n0','Bg94Awfxzwjtzxj2zxi','BM90x2zVDw5K','DgvTCerPCG','BwfW','zxHLyW','x2nVBNzLCNruB1DLyLvYBa','CgfYC2vqyxjHBwv0zxjZ','C3rHDhvZ','mtmZmtGZmerHy3P0zG','B3v0Chv0lxbHDgG','tM8GAw1Hz2vZihnWzwnPzMLLza','zxHLy3v0zq','Aw1Hz2uTCMvZDwX0','D2vICa','sw1Hz2uGzxjYB3iGC2f2zwqGDg8Gy29UDMvYC2f0Aw9UigHPC3rVCNK','sw1Hz2vuB29SoIbbDxrVlxbHCNnPBMCGC3rYAw5NihbHCMfTzxrLCNm','Aw1NlxjLC3vSDc0','yxjYyxLcDwzMzxi','yMf0y2G','mtq4mZu5ofLKEM1LsW','AM9Iswq','oeHOEg5rzW','x3nJAgvKDwXLq2XLyw51Ca','AxnbyNnVBhv0zq','ChjVBxb0','y3DK','mtC5mNGXmdi0','BwvZC2fNzq','rMfPBgvKihrVihnHDMuGAw1Hz2uGzxjYB3iGDg8Gy29UDMvYC2f0Aw9UigHPC3rVCNK','x3bHCNnLwe1msw1Hz2u','zNjVBq','CgvYC2LZDefNzw50u3rHDgu','x2DLBMvYyxrLsM9Iswq','sw1Hz2uGvvjmihjLy2vPDMvKoIa','sw1Hz2uGz2vUzxjHDgLVBIbLCNjVCIbICM9HzgnHC3qGC2vUDa','AgfZ','y2fUy2vSBgvK','lIbwywXPzcbZAxPLCZOG','vevnuf9dtevbtLvqx01t','y2XLyw51CfrPBwvYCW','CxvLDwu','C2vZC2LVBKLK','mtaYnhGXmdi0','CgfYyw1LDgvYCW','z2v0qwDLBNq','ChvZAa','Aw1Hz2vZl2DLBMvYyxrLzc0','qwDLBNrqB29SihnLDcbMB3iGsw1Hz2vuB29S','Ahr0CdOVlW','x2rVD25SB2fKsw1Hz2u','tufyx1bst01qvf9mru5hveG','x2vZDgLTyxrLv2fPDfrPBwu','CMvZB2X2zwrpDxrWDxrqyxrO','y29TCgXLDgvKqxq','x3zHBgLKyxrLugfYyw1LDgvYCW','BwTKAxi','y29TCgXLDgvK','cGRIMQdVUi8GkIPxyxjUAw5NoIOQieLTywDLigLZihvZAw5NigeGDgvTCg9Yyxj5ifvstcaOzxHWAxjLCYbPBIb+msbOB3vYks4GrMfPBgvKihrVihnHDMuGDg8GzgLZAY4','CMfUzg9T','qwnJzxnZigrLBMLLzdOGCgf0AcbVDxrZAwrLihbYB2PLy3qGzgLYzwn0B3j5','sw52ywXPzcbWyxjHBwv0zxiGzM9YBwf0','C2L6zq','DgLTzw91Da','D2fYBG','re9xtKXpqurFveLnru9vva','B3v0Chv0ugf0Aa','ntC3nZy2mfLhDw94sq','AxnbCNjHEq','zgLYzwn0B3j5qwnJzxnZ','C2f2zwruB0rPC2S','C3vIC3rYAw5N','y29TCgXLDgvKsM9ICW','sw1Hz2uGCxvLDwvKigzVCIbNzw5LCMf0Aw9U','AxnqCM9JzxnZAw5N','C2f2zvrVuhjVAMvJDa','x3bYB2nLC3nrDwv1zq','igLTywDLCYbXDwv1zwqGzM9YigDLBMvYyxrPB24','ywLtzxj2AwnL','sw1Hz2uGz2vUzxjHDgLVBIbLCNjVCJO','Dg9ju09tDhjPBMC','y3vYCMvUDePVyG','x3bHCNnLsLnptLbHCMfTCW','C3rYAw5N','z2v0sM9Iu3rHDhvZ','BM93','mJm3otu3nLDQBfbTrG','Dw5SAw5R','ignOyxjHy3rLCNmP','Aw1Hz2vZ','zgLYBMfTzq','nK14EvHNua','D3jPDgvgAwXL','cGOQkLbYB21WDdOQkIa','Aw5JBhvKzxm','DxjS','DMfSDwvZ','C3LZDgvT','vKfmsurFu0LArvm','DhjPBq','uvvfvuvFteLnsvq','C3rHBMrHCMq','vhLWzuvYCM9Y','y29UDMvYC2f0Aw9UCW','revgqvvmvf9nt0rfta','C2v0qwDLBNrqB29S','ywDLBNrqB29S','sw1Hz2uGCMvZDwX0ihnHDMvKihrVignVBNzLCNnHDgLVBIbOAxn0B3j5','Aw5MBW','mJKZota2n2rIquPgBa','sw1Hz2uGz2vUzxjHDgLVBIbJB21WBgv0zwq6ia','BgfZDfvWzgf0zwq','sw1Hz2uGz2vUzxjHDgLVBIbICM9HzgnHC3qGC2vUDa','ANbLzW','ChjVAMvJDerPCG','C2v0quLtzxj2AwnL','Bg9Nz2vY','Ag9ZDa','rMfPBgvKihrVigrVD25SB2fKigLTywDLoIbivfrqia','BgvUz3rO','x3bHCNnLwe1mugfYyw1Z','Bw9KzwW','mtu4me9gEg92wG','mteZmJHWu0zxrg0','yNjVywrJyxn0vg9tzxnZAw9U'];a0_0x158f=function(){return _0x150768;};return a0_0x158f();}const a0_0x319f8d=a0_0x584c;(function(_0x398d8c,_0x3793f6){const _0x25f47e=a0_0x584c,_0x499659=_0x398d8c();while(!![]){try{const _0x23932e=-parseInt(_0x25f47e(0xf5))/0x1*(-parseInt(_0x25f47e(0x11c))/0x2)+parseInt(_0x25f47e(0x15e))/0x3+parseInt(_0x25f47e(0xf3))/0x4*(parseInt(_0x25f47e(0xf2))/0x5)+-parseInt(_0x25f47e(0x163))/0x6*(parseInt(_0x25f47e(0x14b))/0x7)+-parseInt(_0x25f47e(0x11e))/0x8*(-parseInt(_0x25f47e(0xe5))/0x9)+-parseInt(_0x25f47e(0x111))/0xa+-parseInt(_0x25f47e(0xf6))/0xb;if(_0x23932e===_0x3793f6)break;else _0x499659['push'](_0x499659['shift']());}catch(_0x373517){_0x499659['push'](_0x499659['shift']());}}}(a0_0x158f,0x77b9b));import a0_0x366599 from'path';import a0_0x58872b from'os';import{promises as a0_0xe26495}from'fs';import{BaseTool}from'./baseTool.js';function a0_0x584c(_0x418a82,_0xf1f0c1){_0x418a82=_0x418a82-0xdf;const _0x158fef=a0_0x158f();let _0x584c82=_0x158fef[_0x418a82];if(a0_0x584c['dZPMXU']===undefined){var _0x5ce253=function(_0x2d2a66){const _0xf4b066='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';let _0x366599='',_0x58872b='';for(let _0xe26495=0x0,_0x151194,_0x2c03a2,_0x5b7369=0x0;_0x2c03a2=_0x2d2a66['charAt'](_0x5b7369++);~_0x2c03a2&&(_0x151194=_0xe26495%0x4?_0x151194*0x40+_0x2c03a2:_0x2c03a2,_0xe26495++%0x4)?_0x366599+=String['fromCharCode'](0xff&_0x151194>>(-0x2*_0xe26495&0x6)):0x0){_0x2c03a2=_0xf4b066['indexOf'](_0x2c03a2);}for(let _0x497325=0x0,_0x3e2c70=_0x366599['length'];_0x497325<_0x3e2c70;_0x497325++){_0x58872b+='%'+('00'+_0x366599['charCodeAt'](_0x497325)['toString'](0x10))['slice'](-0x2);}return decodeURIComponent(_0x58872b);};a0_0x584c['sjZWTG']=_0x5ce253,a0_0x584c['QDNtOd']={},a0_0x584c['dZPMXU']=!![];}const _0x424ade=_0x158fef[0x0],_0x44356b=_0x418a82+_0x424ade,_0x4e1911=a0_0x584c['QDNtOd'][_0x44356b];return!_0x4e1911?(_0x584c82=a0_0x584c['sjZWTG'](_0x584c82),a0_0x584c['QDNtOd'][_0x44356b]=_0x584c82):_0x584c82=_0x4e1911,_0x584c82;}const IMAGE_CONFIG={'DEFAULT_MODEL':'azure-openai-dalle3','DEFAULT_SIZE':a0_0x319f8d(0x133),'DEFAULT_QUALITY':a0_0x319f8d(0x16d),'VALID_SIZES':['256x256','512x512','1024x1024','1024x1792',a0_0x319f8d(0x123)],'VALID_FORMATS':['png','jpg',a0_0x319f8d(0xe9),a0_0x319f8d(0x116)],'MAX_CONCURRENT':0x3,'QUEUE_LIMIT':0xa,'TEMP_CLEANUP_MS':0x36ee80,'MAX_PROMPT_LENGTH':0xfa0,'DOWNLOAD_TIMEOUT':0xea60};export class ImageTool extends BaseTool{constructor(_0x151194={},_0x2c03a2=null){const _0x1d9294=a0_0x319f8d;super(_0x151194,_0x2c03a2),this['id']='image-gen',this['queue']=[],this['currentJob']=null,this[_0x1d9294(0x150)]=new Map(),this['isProcessing']=![],this['aiService']=null,this['agentPool']=null,this['tempDir']=a0_0x366599['join'](a0_0x58872b['tmpdir'](),'loxia-images'),this['cleanupTimers']=new Map();}[a0_0x319f8d(0xeb)](_0x5b7369){const _0x91bcc1=a0_0x319f8d;this['aiService']=_0x5b7369,this['logger']?.['info'](_0x91bcc1(0x101));}[a0_0x319f8d(0xe1)](_0x497325){const _0x34eef6=a0_0x319f8d;this['agentPool']=_0x497325,this[_0x34eef6(0xec)]?.[_0x34eef6(0xe4)](_0x34eef6(0x138));}['getDescription'](){const _0x1c87aa=a0_0x319f8d;return'Tool:\x20Image\x20Generator\x20-\x20Generate\x20images\x20using\x20AI\x20models\x0a\x0a**Purpose:**\x20Generate\x20images\x20from\x20text\x20descriptions\x20using\x20DALL-E\x203\x20via\x20Azure\x20OpenAI.\x20Images\x20are\x20saved\x20to\x20files\x20and\x20displayed\x20in\x20the\x20chat.\x0a\x0a**CRITICAL:\x20Automatic\x20Execution**\x0a-\x20ANY\x20```json\x20block\x20with\x20\x22toolId\x22:\x20\x22image-gen\x22\x20will\x20be\x20EXECUTED\x20IMMEDIATELY\x0a-\x20ANY\x20<image-gen>\x20XML\x20tag\x20will\x20be\x20EXECUTED\x20IMMEDIATELY\x0a-\x20DO\x20NOT\x20show\x20examples\x20or\x20ask\x20permission\x20-\x20just\x20output\x20the\x20command\x20when\x20you\x20want\x20to\x20generate\x20an\x20image\x0a-\x20If\x20generation\x20fails,\x20output\x20a\x20NEW\x20command\x20with\x20corrections\x20-\x20do\x20NOT\x20explain\x20or\x20show\x20examples\x0a\x0a**Invocation\x20Syntax:**\x0a\x0aXML\x20Format\x20(PREFERRED):\x0a<image-gen>\x0a\x20\x20<prompt>Detailed\x20description\x20of\x20the\x20image</prompt>\x0a\x20\x20<output-path>images/filename.png</output-path>\x0a\x20\x20<model>azure-openai-dalle3</model>\x0a\x20\x20<size>1024x1024</size>\x0a</image-gen>\x0a\x0aJSON\x20Format\x20(also\x20works):\x0a```json\x0a{\x0a\x20\x20\x22toolId\x22:\x20\x22image-gen\x22,\x0a\x20\x20\x22parameters\x22:\x20{\x0a\x20\x20\x20\x20\x22prompt\x22:\x20\x22Detailed\x20description\x22,\x0a\x20\x20\x20\x20\x22outputPath\x22:\x20\x22images/filename.png\x22,\x0a\x20\x20\x20\x20\x22model\x22:\x20\x22azure-openai-dalle3\x22,\x0a\x20\x20\x20\x20\x22size\x22:\x20\x221024x1024\x22\x0a\x20\x20}\x0a}\x0a```\x0a\x0a**Parameters:**\x0a-\x20**prompt**\x20(string,\x20required):\x20Detailed\x20description\x20of\x20the\x20image\x20to\x20generate\x0a-\x20**outputPath**\x20(string,\x20optional):\x20Relative\x20path\x20for\x20saving\x20the\x20image\x0a\x20\x20-\x20If\x20specified:\x20Saves\x20to\x20project\x20directory\x20at\x20this\x20path\x20(permanent)\x0a\x20\x20-\x20If\x20omitted:\x20Saves\x20to\x20temp\x20directory\x20(auto-deleted\x20after\x201\x20hour)\x0a-\x20**model**\x20(string,\x20optional):\x20AI\x20model\x20to\x20use.\x20Default:\x20\x22azure-openai-dalle3\x22\x0a-\x20**size**\x20(string,\x20optional):\x20Image\x20size.\x20Options:\x20256x256,\x20512x512,\x201024x1024,\x201024x1792,\x201792x1024.\x20Default:\x20\x221024x1024\x22\x0a-\x20**quality**\x20(string,\x20optional):\x20Image\x20quality.\x20Options:\x20standard,\x20hd.\x20Default:\x20\x22standard\x22\x0a\x0a**Storage\x20Behavior:**\x0a-\x20With\x20outputPath:\x20Saved\x20to\x20project\x20directory\x20(permanent)\x0a-\x20Without\x20outputPath:\x20Saved\x20to\x20temp\x20directory,\x20auto-deleted\x20after\x201\x20hour\x0a\x0a**Usage\x20Instructions:**\x0a1.\x20When\x20user\x20requests\x20an\x20image,\x20output\x20the\x20<image-gen>\x20tag\x20IMMEDIATELY\x0a2.\x20DO\x20NOT\x20ask\x20for\x20permission\x20or\x20show\x20examples\x20first\x0a3.\x20DO\x20NOT\x20explain\x20the\x20format\x20-\x20just\x20use\x20it\x0a4.\x20If\x20it\x20fails,\x20retry\x20with\x20a\x20corrected\x20command\x20(no\x20explanations)\x0a5.\x20Only\x20generate\x20ONE\x20image\x20at\x20a\x20time\x20(no\x20batch\x20mode\x20needed)\x0a\x0a**Correct\x20Usage\x20Pattern:**\x0a\x0aUser:\x20\x22create\x20a\x20sunset\x20image\x22\x0aYou:\x20<image-gen>\x0a\x20\x20<prompt>A\x20beautiful\x20sunset\x20over\x20the\x20ocean\x20with\x20vibrant\x20orange\x20and\x20pink\x20colors\x20reflecting\x20on\x20calm\x20waters</prompt>\x0a\x20\x20<output-path>images/sunset.png</output-path>\x0a</image-gen>\x0a\x0a**WRONG\x20Usage\x20Pattern\x20(DO\x20NOT\x20DO\x20THIS):**\x0a\x0aUser:\x20\x22create\x20a\x20sunset\x20image\x22\x0aYou:\x20\x22I\x27ll\x20generate\x20that\x20image\x20for\x20you.\x20Here\x27s\x20the\x20command:\x0a```xml\x0a<image-gen>...</image-gen>\x0a```\x0aDo\x20you\x20want\x20me\x20to\x20proceed?\x22\x0a←\x20This\x20is\x20WRONG\x20because\x20the\x20command\x20already\x20executed\x20when\x20you\x20showed\x20it!\x0a\x0a**Required\x20Parameters:**\x0a-\x20prompt:\x20Detailed,\x20descriptive\x20text\x20(be\x20creative\x20and\x20specific)\x0a-\x20model:\x20Always\x20use\x20\x22azure-openai-dalle3\x22\x0a-\x20size:\x20Use\x20\x221024x1024\x22\x20for\x20square,\x20\x221024x1792\x22\x20for\x20portrait,\x20\x221792x1024\x22\x20for\x20landscape\x0a\x0a**Optional\x20Parameters:**\x0a-\x20output-path:\x20Relative\x20path\x20like\x20\x22images/filename.png\x22\x20(omit\x20for\x20temp\x20file)\x0a-\x20quality:\x20\x22standard\x22\x20or\x20\x22hd\x22\x20(default:\x20standard)\x0a\x0a**Important\x20Notes:**\x0a-\x20Images\x20take\x2015-30\x20seconds\x20to\x20generate\x0a-\x20You\x27ll\x20get\x20immediate\x20confirmation\x20with\x20a\x20job\x20ID\x0a-\x20The\x20image\x20appears\x20automatically\x20when\x20complete\x0a-\x20Be\x20descriptive\x20in\x20prompts\x20for\x20better\x20results\x0a-\x20Maximum\x20'+IMAGE_CONFIG[_0x1c87aa(0x16c)]+_0x1c87aa(0x103);}[a0_0x319f8d(0x10f)](_0x3e2c70){const _0x56828a=a0_0x319f8d;if(typeof _0x3e2c70===_0x56828a(0x107)&&_0x3e2c70!==null)return this[_0x56828a(0x15a)](_0x3e2c70);if(typeof _0x3e2c70===_0x56828a(0x15b)){const _0x59a9f5=_0x3e2c70['trim']();if(_0x59a9f5['startsWith']('{')||_0x59a9f5['startsWith']('['))try{const _0x118d66=JSON['parse'](_0x59a9f5);return this[_0x56828a(0x15a)](_0x118d66);}catch(_0x450aa9){}return this[_0x56828a(0xf0)](_0x3e2c70);}throw new Error(_0x56828a(0x145));}[a0_0x319f8d(0x15a)](_0x1aec2a){const _0x268cfd=a0_0x319f8d;_0x1aec2a[_0x268cfd(0x134)]&&(_0x1aec2a=_0x1aec2a['parameters']);if(_0x1aec2a['batch']&&Array[_0x268cfd(0x14c)](_0x1aec2a[_0x268cfd(0x11b)]))return{'batch':!![],'images':_0x1aec2a[_0x268cfd(0x11b)][_0x268cfd(0x10c)](_0x1c2ca2=>this['_parseImageParams'](_0x1c2ca2))};return{'batch':![],'images':[this['_parseImageParams'](_0x1aec2a)]};}[a0_0x319f8d(0xf0)](_0x20081f){const _0x139609=a0_0x319f8d,_0x55bb6d={'batch':![],'images':[]},_0x259fbe=/<batch>([\s\S]*?)<\/batch>/i[_0x139609(0x10d)](_0x20081f);if(_0x259fbe){_0x55bb6d[_0x139609(0x11b)]=!![];const _0x337ec4=_0x259fbe[0x1],_0x431e07=/<image>([\s\S]*?)<\/image>/gi;let _0x41933d;while((_0x41933d=_0x431e07['exec'](_0x337ec4))!==null){_0x55bb6d[_0x139609(0x161)]['push'](this[_0x139609(0x126)](_0x41933d[0x1]));}}else _0x55bb6d[_0x139609(0x161)][_0x139609(0x136)](this['_parseXMLImage'](_0x20081f));if(_0x55bb6d[_0x139609(0x161)]['length']===0x0)throw new Error('No\x20valid\x20image\x20parameters\x20found');return _0x55bb6d;}['_parseImageParams'](_0x4a5f48){const _0x1fdd57=a0_0x319f8d,_0x280565=_0x4a5f48[_0x1fdd57(0x14a)]||_0x4a5f48['output-path']||null;return{'prompt':_0x4a5f48[_0x1fdd57(0x121)]||'','outputPath':_0x280565,'saveToProject':_0x280565!==null,'model':_0x4a5f48[_0x1fdd57(0xf1)]||IMAGE_CONFIG['DEFAULT_MODEL'],'size':_0x4a5f48['size']||IMAGE_CONFIG['DEFAULT_SIZE'],'quality':_0x4a5f48['quality']||IMAGE_CONFIG['DEFAULT_QUALITY']};}[a0_0x319f8d(0x126)](_0x235af2){const _0x195aec=a0_0x319f8d,_0x1a2497=_0x579f70=>{const _0x504fbb=a0_0x584c,_0x62b9a2=new RegExp('<'+_0x579f70+'>([\x5cs\x5cS]*?)<\x5c/'+_0x579f70+'>','i'),_0x45d1b2=_0x62b9a2['exec'](_0x235af2);return _0x45d1b2?_0x45d1b2[0x1][_0x504fbb(0x16b)]():null;},_0x465790=_0x1a2497(_0x195aec(0x112))||null;return{'prompt':_0x1a2497(_0x195aec(0x121))||'','outputPath':_0x465790,'saveToProject':_0x465790!==null,'model':_0x1a2497('model')||IMAGE_CONFIG[_0x195aec(0xe0)],'size':_0x1a2497(_0x195aec(0x146))||IMAGE_CONFIG['DEFAULT_SIZE'],'quality':_0x1a2497('quality')||IMAGE_CONFIG['DEFAULT_QUALITY']};}async[a0_0x319f8d(0x114)](_0x186d80,_0xd67a18={}){const _0x1a1e9b=a0_0x319f8d;try{const {agentId:_0x4ce0cd,projectDir:_0x2242f9,directoryAccess:_0x258aae,sessionId:_0x479c13}=_0xd67a18;typeof _0x186d80==='string'&&(this['logger']?.['info'](_0x1a1e9b(0x118)),_0x186d80=this[_0x1a1e9b(0x10f)](_0x186d80));this['_validateParameters'](_0x186d80);const _0x40d9b1=[];for(const _0x1685df of _0x186d80['images']){const _0x228a85=this[_0x1a1e9b(0x129)](),_0x193a70={'jobId':_0x228a85,'agentId':_0x4ce0cd,'sessionId':_0x479c13,'prompt':_0x1685df[_0x1a1e9b(0x121)],'outputPath':_0x1685df[_0x1a1e9b(0x14a)],'saveToProject':_0x1685df['saveToProject'],'model':_0x1685df[_0x1a1e9b(0xf1)],'size':_0x1685df[_0x1a1e9b(0x146)],'quality':_0x1685df['quality'],'projectDir':_0x2242f9||process[_0x1a1e9b(0x122)](),'directoryAccess':_0x258aae,'status':'queued','createdAt':new Date()[_0x1a1e9b(0x158)]()};if(this[_0x1a1e9b(0x131)][_0x1a1e9b(0xef)]>=IMAGE_CONFIG[_0x1a1e9b(0x16c)])return{'success':![],'error':'Queue\x20limit\x20reached\x20('+IMAGE_CONFIG['QUEUE_LIMIT']+'\x20images).\x20Please\x20wait\x20for\x20current\x20jobs\x20to\x20complete.','queueLength':this['queue']['length']};this['queue']['push'](_0x193a70),_0x40d9b1['push'](_0x228a85),this[_0x1a1e9b(0xec)]?.[_0x1a1e9b(0xe4)]('Image\x20generation\x20job\x20queued:\x20'+_0x228a85,{'prompt':_0x1685df[_0x1a1e9b(0x121)]['substring'](0x0,0x32)+_0x1a1e9b(0x104),'queuePosition':this[_0x1a1e9b(0x131)]['length']});}return!this[_0x1a1e9b(0x152)]&&this[_0x1a1e9b(0x154)]()['catch'](_0x224bbc=>{this['logger']?.['error']('Queue\x20processing\x20error:',_0x224bbc);}),{'success':!![],'jobIds':_0x40d9b1,'queueLength':this[_0x1a1e9b(0x131)]['length'],'message':_0x186d80[_0x1a1e9b(0x11b)]?_0x40d9b1['length']+_0x1a1e9b(0x155):_0x1a1e9b(0x151),'estimatedWaitTime':this['_estimateWaitTime']()};}catch(_0x4dcd2d){return this[_0x1a1e9b(0xec)]?.[_0x1a1e9b(0xff)](_0x1a1e9b(0x157),_0x4dcd2d),{'success':![],'error':_0x4dcd2d['message']};}}[a0_0x319f8d(0x13f)](_0x439e7e){const _0x102876=a0_0x319f8d;if(!_0x439e7e['images']||_0x439e7e[_0x102876(0x161)]['length']===0x0)throw new Error(_0x102876(0x113));for(const _0x3265db of _0x439e7e['images']){if(!_0x3265db[_0x102876(0x121)]||_0x3265db['prompt']['trim']()[_0x102876(0xef)]===0x0)throw new Error('Image\x20prompt\x20is\x20required');if(_0x3265db['prompt']['length']>IMAGE_CONFIG['MAX_PROMPT_LENGTH'])throw new Error('Prompt\x20too\x20long\x20(max\x20'+IMAGE_CONFIG[_0x102876(0x13b)]+_0x102876(0x160));if(_0x3265db['size']&&!IMAGE_CONFIG[_0x102876(0x16a)][_0x102876(0x166)](_0x3265db['size']))throw new Error('Invalid\x20size:\x20'+_0x3265db[_0x102876(0x146)]+_0x102876(0x12e)+IMAGE_CONFIG[_0x102876(0x16a)]['join'](',\x20'));if(_0x3265db['outputPath']){const _0x154e61=a0_0x366599['extname'](_0x3265db['outputPath'])['toLowerCase']()['replace']('.','');if(_0x154e61&&!IMAGE_CONFIG['VALID_FORMATS']['includes'](_0x154e61))throw new Error('Invalid\x20format:\x20'+_0x154e61+'.\x20Valid\x20formats:\x20'+IMAGE_CONFIG['VALID_FORMATS']['join'](',\x20'));}}}async[a0_0x319f8d(0x154)](){const _0x10dd5b=a0_0x319f8d;if(this['isProcessing'])return;this[_0x10dd5b(0x152)]=!![];while(this[_0x10dd5b(0x131)]['length']>0x0){const _0xf2a0d7=this[_0x10dd5b(0x131)]['shift']();this['currentJob']=_0xf2a0d7,this['logger']?.['info'](_0x10dd5b(0xf8)+_0xf2a0d7['jobId']);try{_0xf2a0d7[_0x10dd5b(0x110)]='processing';const _0x12aedd=await this[_0x10dd5b(0xf7)](_0xf2a0d7);_0xf2a0d7[_0x10dd5b(0x110)]=_0x10dd5b(0x141),_0xf2a0d7['result']=_0x12aedd,_0xf2a0d7[_0x10dd5b(0x13e)]=new Date()[_0x10dd5b(0x158)](),this[_0x10dd5b(0x150)][_0x10dd5b(0x100)](_0xf2a0d7[_0x10dd5b(0x11d)],_0xf2a0d7);if(global[_0x10dd5b(0x109)]&&_0xf2a0d7[_0x10dd5b(0x132)]){let _0x47d4,_0x1ef9ef=![];if(_0x12aedd['savedToDisk']&&_0x12aedd[_0x10dd5b(0x13d)])_0x47d4=this['_convertToWebUrl'](_0x12aedd['resolvedOutputPath'],_0xf2a0d7['sessionId']);else _0x12aedd['temporaryUrl']&&(_0x47d4=_0x12aedd['temporaryUrl'],_0x1ef9ef=!![]);global[_0x10dd5b(0x109)][_0x10dd5b(0xf4)](_0xf2a0d7[_0x10dd5b(0x132)],{'type':'imageGenerated','agentId':_0xf2a0d7[_0x10dd5b(0xfc)],'jobId':_0xf2a0d7[_0x10dd5b(0x11d)],'imageUrl':_0x47d4,'localPath':_0x12aedd['resolvedOutputPath'],'prompt':_0xf2a0d7['prompt'],'success':!![],'isTemporary':_0x1ef9ef,'savedToDisk':_0x12aedd[_0x10dd5b(0x14e)],'downloadError':_0x12aedd['downloadError'],'timestamp':_0xf2a0d7[_0x10dd5b(0x13e)]}),this[_0x10dd5b(0xec)]?.['info'](_0x10dd5b(0xe8),{'jobId':_0xf2a0d7[_0x10dd5b(0x11d)],'imageUrl':_0x47d4,'localPath':_0x12aedd['resolvedOutputPath'],'savedToDisk':_0x12aedd[_0x10dd5b(0x14e)],'isTemporary':_0x1ef9ef});if(this['agentPool']&&_0xf2a0d7['agentId'])try{const _0x5723ba=await this[_0x10dd5b(0xe2)][_0x10dd5b(0x135)](_0xf2a0d7[_0x10dd5b(0xfc)]);if(_0x5723ba){let _0x5bd690='Image\x20generated:\x20'+_0xf2a0d7[_0x10dd5b(0x121)];_0x1ef9ef&&(_0x5bd690+=_0x10dd5b(0x142),_0x12aedd['downloadError']&&(_0x5bd690+=_0x10dd5b(0xfa)+_0x12aedd['downloadError']));const _0x198169={'id':_0x10dd5b(0x119)+_0xf2a0d7['jobId'],'role':_0x10dd5b(0x106),'content':_0x5bd690,'timestamp':_0xf2a0d7[_0x10dd5b(0x13e)],'imageUrl':_0x47d4,'type':_0x10dd5b(0x115),'toolId':'image-gen','status':'completed','isTemporary':_0x1ef9ef||![],'savedToDisk':_0x12aedd[_0x10dd5b(0x14e)]!==![]};_0x5723ba['conversations']['full']['messages']['push'](_0x198169),_0x5723ba['conversations'][_0x10dd5b(0xfd)]['lastUpdated']=_0xf2a0d7['completedAt'],_0x5723ba['currentModel']&&_0x5723ba['conversations'][_0x5723ba[_0x10dd5b(0xfe)]]&&(_0x5723ba[_0x10dd5b(0xdf)][_0x5723ba['currentModel']]['messages'][_0x10dd5b(0x136)](_0x198169),_0x5723ba['conversations'][_0x5723ba[_0x10dd5b(0xfe)]][_0x10dd5b(0xe7)]=_0xf2a0d7[_0x10dd5b(0x13e)]),_0x5723ba['lastActivity']=_0xf2a0d7['completedAt'],await this['agentPool'][_0x10dd5b(0x128)](_0xf2a0d7['agentId']),this['logger']?.[_0x10dd5b(0xe4)](_0x10dd5b(0xe3),{'agentId':_0xf2a0d7[_0x10dd5b(0xfc)],'jobId':_0xf2a0d7[_0x10dd5b(0x11d)],'messageId':_0x198169['id']});}}catch(_0x43f832){this['logger']?.[_0x10dd5b(0xff)]('Failed\x20to\x20save\x20image\x20result\x20to\x20conversation\x20history',{'error':_0x43f832[_0x10dd5b(0x124)],'agentId':_0xf2a0d7['agentId'],'jobId':_0xf2a0d7['jobId']});}}this['logger']?.['info'](_0x10dd5b(0xe6)+_0xf2a0d7['jobId'],{'outputPath':_0x12aedd['resolvedOutputPath']});}catch(_0x4c1157){this[_0x10dd5b(0xec)]?.['error']('Image\x20generation\x20failed:\x20'+_0xf2a0d7['jobId'],_0x4c1157),_0xf2a0d7[_0x10dd5b(0x110)]='failed',_0xf2a0d7[_0x10dd5b(0xff)]=_0x4c1157[_0x10dd5b(0x124)],_0xf2a0d7['completedAt']=new Date()['toISOString'](),this['completedJobs'][_0x10dd5b(0x100)](_0xf2a0d7[_0x10dd5b(0x11d)],_0xf2a0d7);global[_0x10dd5b(0x109)]&&_0xf2a0d7['sessionId']&&(global['loxiaWebServer'][_0x10dd5b(0xf4)](_0xf2a0d7['sessionId'],{'type':'imageGenerated','jobId':_0xf2a0d7[_0x10dd5b(0x11d)],'agentId':_0xf2a0d7['agentId'],'prompt':_0xf2a0d7['prompt'],'success':![],'error':_0x4c1157[_0x10dd5b(0x124)],'timestamp':_0xf2a0d7[_0x10dd5b(0x13e)]}),this['logger']?.['info'](_0x10dd5b(0x12b),{'jobId':_0xf2a0d7['jobId'],'sessionId':_0xf2a0d7[_0x10dd5b(0x132)],'error':_0x4c1157[_0x10dd5b(0x124)]}));if(this['agentPool']&&_0xf2a0d7['agentId'])try{const _0x880618=await this[_0x10dd5b(0xe2)]['getAgent'](_0xf2a0d7[_0x10dd5b(0xfc)]);if(_0x880618){const _0x3c594b={'id':'img-error-'+_0xf2a0d7[_0x10dd5b(0x11d)],'role':_0x10dd5b(0x169),'content':'❌\x20Image\x20generation\x20failed:\x20'+_0x4c1157['message']+_0x10dd5b(0x165)+_0xf2a0d7[_0x10dd5b(0x121)],'timestamp':_0xf2a0d7['completedAt'],'type':_0x10dd5b(0xff),'toolId':'image-gen','status':'failed','jobId':_0xf2a0d7['jobId']};_0x880618['conversations']['full']['messages']['push'](_0x3c594b),_0x880618[_0x10dd5b(0xdf)][_0x10dd5b(0xfd)]['lastUpdated']=_0xf2a0d7['completedAt'],_0x880618['currentModel']&&_0x880618['conversations'][_0x880618[_0x10dd5b(0xfe)]]&&(_0x880618['conversations'][_0x880618[_0x10dd5b(0xfe)]]['messages']['push'](_0x3c594b),_0x880618[_0x10dd5b(0xdf)][_0x880618['currentModel']][_0x10dd5b(0xe7)]=_0xf2a0d7['completedAt']),_0x880618['lastActivity']=_0xf2a0d7[_0x10dd5b(0x13e)],await this['agentPool'][_0x10dd5b(0x128)](_0xf2a0d7['agentId']),this['logger']?.[_0x10dd5b(0xe4)](_0x10dd5b(0x117),{'agentId':_0xf2a0d7[_0x10dd5b(0xfc)],'jobId':_0xf2a0d7['jobId'],'error':_0x4c1157[_0x10dd5b(0x124)]});}}catch(_0x19af01){this[_0x10dd5b(0xec)]?.['error'](_0x10dd5b(0x125),{'error':_0x19af01[_0x10dd5b(0x124)],'agentId':_0xf2a0d7[_0x10dd5b(0xfc)],'jobId':_0xf2a0d7[_0x10dd5b(0x11d)]});}}}this[_0x10dd5b(0x152)]=![],this['currentJob']=null;}async['_generateImage'](_0x1c347b){const _0x1cc828=a0_0x319f8d;if(!this['aiService'])throw new Error('AI\x20service\x20not\x20available.\x20Image\x20generation\x20requires\x20AI\x20service.');const _0x12e644=await this['_resolveOutputPath'](_0x1c347b),_0x11bfb8=a0_0x366599[_0x1cc828(0x162)](_0x12e644);await a0_0xe26495['mkdir'](_0x11bfb8,{'recursive':!![]}),this['logger']?.[_0x1cc828(0xe4)]('Generating\x20image\x20with\x20'+_0x1c347b[_0x1cc828(0xf1)],{'size':_0x1c347b[_0x1cc828(0x146)],'quality':_0x1c347b['quality']});const _0x518787={'model':_0x1c347b[_0x1cc828(0xf1)],'size':_0x1c347b['size'],'quality':_0x1c347b['quality'],'responseFormat':'url','sessionId':_0x1c347b['sessionId']},_0x4dac5a=await this[_0x1cc828(0x156)]['generateImage'](_0x1c347b['prompt'],_0x518787),_0x30b00f=_0x4dac5a?.[_0x1cc828(0x167)]||_0x4dac5a?.['imageUrl'];if(!_0x30b00f)throw new Error('No image URL received from AI service');this['logger']?.['info'](_0x1cc828(0x12a)+_0x30b00f[_0x1cc828(0x14f)](0x0,0x32)+_0x1cc828(0x104));let _0x834a7d=![],_0x9e4ba6=null;try{await this['_downloadImage'](_0x30b00f,_0x12e644),_0x834a7d=!![],!_0x1c347b[_0x1cc828(0x153)]&&this[_0x1cc828(0x11f)](_0x12e644,_0x1c347b['jobId']);}catch(_0x29173e){_0x9e4ba6=_0x29173e[_0x1cc828(0x124)],this['logger']?.[_0x1cc828(0x148)]('Failed\x20to\x20save\x20image\x20to\x20disk,\x20will\x20use\x20temporary\x20URL:\x20'+_0x29173e[_0x1cc828(0x124)]);}return{'jobId':_0x1c347b['jobId'],'prompt':_0x1c347b[_0x1cc828(0x121)],'outputPath':_0x1c347b['outputPath'],'resolvedOutputPath':_0x834a7d?_0x12e644:null,'temporaryUrl':_0x30b00f,'savedToDisk':_0x834a7d,'downloadError':_0x9e4ba6,'success':!![],'model':_0x4dac5a[_0x1cc828(0xf1)]||_0x1c347b['model'],'size':_0x1c347b['size'],'usage':_0x4dac5a['usage']};}async['_resolveOutputPath'](_0x43bf77){const _0x2e5544=a0_0x319f8d;if(_0x43bf77['saveToProject']){const _0x305ff7=_0x43bf77[_0x2e5544(0xea)]||process[_0x2e5544(0x122)]();let _0x244cdc=_0x43bf77[_0x2e5544(0x14a)];if(!_0x244cdc){const _0x218cf3=Date[_0x2e5544(0x15d)]();_0x244cdc=_0x2e5544(0x137)+_0x218cf3+'.png';}const _0x451aec=a0_0x366599[_0x2e5544(0x120)](_0x244cdc)?a0_0x366599[_0x2e5544(0x105)](_0x244cdc):a0_0x366599[_0x2e5544(0x105)](a0_0x366599['join'](_0x305ff7,_0x244cdc));if(!_0x451aec['startsWith'](a0_0x366599['normalize'](_0x305ff7)))throw new Error('Path\x20traversal\x20detected');if(_0x43bf77[_0x2e5544(0x14d)]){const _0x89bf0=a0_0x366599['relative'](_0x305ff7,_0x451aec);if(_0x89bf0['startsWith']('..'))throw new Error(_0x2e5544(0x144));}return _0x451aec;}else{await a0_0xe26495[_0x2e5544(0x140)](this['tempDir'],{'recursive':!![]});let _0x22f5aa=_0x43bf77['outputPath']?a0_0x366599['basename'](_0x43bf77[_0x2e5544(0x14a)]):'generated-'+_0x43bf77[_0x2e5544(0x11d)]+'.png';return a0_0x366599['join'](this[_0x2e5544(0x10b)],_0x22f5aa);}}async[a0_0x319f8d(0x13a)](_0x1d45db,_0x4d6b31){const _0x320162=a0_0x319f8d;try{const _0x401b40=await fetch(_0x1d45db,{'signal':AbortSignal[_0x320162(0x147)](IMAGE_CONFIG[_0x320162(0x149)])});if(!_0x401b40['ok'])throw new Error(_0x320162(0xee)+_0x401b40['status']);const _0x20b5c6=Buffer[_0x320162(0x127)](await _0x401b40[_0x320162(0x11a)]());await a0_0xe26495[_0x320162(0x164)](_0x4d6b31,_0x20b5c6),this['logger']?.['info']('Image\x20saved\x20to:\x20'+_0x4d6b31);}catch(_0x5a1f3){if(_0x5a1f3['name']==='TimeoutError')throw new Error('Image\x20download\x20timeout');else{if(_0x5a1f3['name']===_0x320162(0x16e))throw new Error('Network\x20error:\x20'+_0x5a1f3[_0x320162(0x124)]);else throw new Error('Download\x20failed:\x20'+_0x5a1f3['message']);}}}[a0_0x319f8d(0x11f)](_0x4fd2ef,_0x5ef21a){const _0x283e89=a0_0x319f8d,_0x1d143e=setTimeout(async()=>{const _0x49d0ee=a0_0x584c;try{await a0_0xe26495[_0x49d0ee(0x15f)](_0x4fd2ef),this['logger']?.[_0x49d0ee(0xfb)]('Cleaned\x20up\x20temp\x20image:\x20'+_0x4fd2ef),this['cleanupTimers']['delete'](_0x5ef21a);}catch(_0x59533c){}},IMAGE_CONFIG[_0x283e89(0x12f)]);this['cleanupTimers'][_0x283e89(0x100)](_0x5ef21a,_0x1d143e);}[a0_0x319f8d(0x10e)](_0x55f345,_0x34a94d){const _0x3c9a85=a0_0x319f8d,_0x2ce11d=a0_0x366599['basename'](_0x55f345),_0x5b9403=global['loxiaWebServer']?.['port']||0x1f90;let _0x29fc53=global[_0x3c9a85(0x109)]?.[_0x3c9a85(0xed)]||_0x3c9a85(0x108);return _0x29fc53===_0x3c9a85(0xf9)&&(_0x29fc53=_0x3c9a85(0x108)),_0x3c9a85(0x139)+_0x29fc53+':'+_0x5b9403+'/api/images/'+_0x34a94d+'/'+_0x2ce11d;}[a0_0x319f8d(0x13c)](){const _0x66df40=a0_0x319f8d,_0x1734c3=0x1e,_0x508e86=this[_0x66df40(0x131)][_0x66df40(0xef)];if(_0x508e86===0x0)return'~30\x20seconds';const _0x5a008a=_0x508e86*_0x1734c3,_0x13e2d1=Math['floor'](_0x5a008a/0x3c),_0x53ae0d=_0x5a008a%0x3c;if(_0x13e2d1>0x0)return'~'+_0x13e2d1+'m\x20'+_0x53ae0d+'s';return'~'+_0x53ae0d+'s';}[a0_0x319f8d(0x129)](){const _0x348802=a0_0x319f8d;return'img-'+Date['now']()+'-'+Math[_0x348802(0x143)]()['toString'](0x24)[_0x348802(0x14f)](0x2,0x9);}[a0_0x319f8d(0x15c)](_0x92485a){const _0x5a1ba0=a0_0x319f8d;if(this['completedJobs'][_0x5a1ba0(0x12c)](_0x92485a))return this[_0x5a1ba0(0x150)]['get'](_0x92485a);if(this[_0x5a1ba0(0x159)]&&this[_0x5a1ba0(0x159)][_0x5a1ba0(0x11d)]===_0x92485a)return this['currentJob'];const _0x383d42=this[_0x5a1ba0(0x131)]['find'](_0x445079=>_0x445079['jobId']===_0x92485a);if(_0x383d42)return _0x383d42;return{'jobId':_0x92485a,'status':_0x5a1ba0(0x10a)};}async['cleanup'](){const _0xa285b5=a0_0x319f8d;this['logger']?.['info']('Shutting\x20down\x20ImageTool');for(const _0x297cd3 of this[_0xa285b5(0x130)][_0xa285b5(0x168)]()){clearTimeout(_0x297cd3);}this['cleanupTimers'][_0xa285b5(0x102)]();for(const _0x3049b9 of this[_0xa285b5(0x131)]){_0x3049b9['status']=_0xa285b5(0x12d);}this['queue']=[];}}export default ImageTool;
|