codex-linux 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +10 -0
- package/.eslintrc.json +27 -0
- package/.github/workflows/ci.yml +156 -0
- package/.huskyrc +7 -0
- package/.lintstagedrc +13 -0
- package/.prettierrc +12 -0
- package/CLAUDE.md +163 -0
- package/DESIGN_SUPERIOR.md +73 -0
- package/Dockerfile +64 -0
- package/INSTALLATION.md +152 -0
- package/LICENSE +21 -0
- package/README.md +245 -0
- package/assets/skills/code-review/instructions.md +102 -0
- package/assets/skills/code-review/skill.yaml +15 -0
- package/assets/skills/refactoring/instructions.md +149 -0
- package/assets/skills/refactoring/skill.yaml +15 -0
- package/assets/skills/testing/skill.yaml +15 -0
- package/commitlint.config.js +23 -0
- package/dist/main/DatabaseManager.js +763 -0
- package/dist/main/DatabaseManager.js.map +1 -0
- package/dist/main/SettingsManager.js +61 -0
- package/dist/main/SettingsManager.js.map +1 -0
- package/dist/main/agents/AgentOrchestrator.js +787 -0
- package/dist/main/agents/AgentOrchestrator.js.map +1 -0
- package/dist/main/agents/AgentSDK.js +219 -0
- package/dist/main/agents/AgentSDK.js.map +1 -0
- package/dist/main/agents/AgentTools.js +348 -0
- package/dist/main/agents/AgentTools.js.map +1 -0
- package/dist/main/agents/CodeIndex.js +233 -0
- package/dist/main/agents/CodeIndex.js.map +1 -0
- package/dist/main/agents/EmbeddingService.js +80 -0
- package/dist/main/agents/EmbeddingService.js.map +1 -0
- package/dist/main/agents/NativeToolCalling.js +206 -0
- package/dist/main/agents/NativeToolCalling.js.map +1 -0
- package/dist/main/api/APIServer.js +278 -0
- package/dist/main/api/APIServer.js.map +1 -0
- package/dist/main/api/RateLimiter.js +138 -0
- package/dist/main/api/RateLimiter.js.map +1 -0
- package/dist/main/api/WebSocketManager.js +300 -0
- package/dist/main/api/WebSocketManager.js.map +1 -0
- package/dist/main/assistant/ContextOptimizer.js +192 -0
- package/dist/main/assistant/ContextOptimizer.js.map +1 -0
- package/dist/main/assistant/PredictedOutputManager.js +172 -0
- package/dist/main/assistant/PredictedOutputManager.js.map +1 -0
- package/dist/main/assistant/PromptCacheManager.js +193 -0
- package/dist/main/assistant/PromptCacheManager.js.map +1 -0
- package/dist/main/assistant/PromptOptimizer.js +626 -0
- package/dist/main/assistant/PromptOptimizer.js.map +1 -0
- package/dist/main/assistant/SmartCodeAssistant.js +224 -0
- package/dist/main/assistant/SmartCodeAssistant.js.map +1 -0
- package/dist/main/auth/SessionManager.js +300 -0
- package/dist/main/auth/SessionManager.js.map +1 -0
- package/dist/main/automations/AdvancedWebhookSystem.js +212 -0
- package/dist/main/automations/AdvancedWebhookSystem.js.map +1 -0
- package/dist/main/automations/AutomationScheduler.js +269 -0
- package/dist/main/automations/AutomationScheduler.js.map +1 -0
- package/dist/main/automations/BatchProcessingSystem.js +159 -0
- package/dist/main/automations/BatchProcessingSystem.js.map +1 -0
- package/dist/main/automations/BrowserAutomationManager.js +195 -0
- package/dist/main/automations/BrowserAutomationManager.js.map +1 -0
- package/dist/main/automations/GitHubActionsManager.js +129 -0
- package/dist/main/automations/GitHubActionsManager.js.map +1 -0
- package/dist/main/automations/GitLabCIManager.js +122 -0
- package/dist/main/automations/GitLabCIManager.js.map +1 -0
- package/dist/main/automations/PriorityQueueManager.js +240 -0
- package/dist/main/automations/PriorityQueueManager.js.map +1 -0
- package/dist/main/background/BackgroundModeManager.js +117 -0
- package/dist/main/background/BackgroundModeManager.js.map +1 -0
- package/dist/main/backup/BackupManager.js +254 -0
- package/dist/main/backup/BackupManager.js.map +1 -0
- package/dist/main/backup/MigrationManager.js +114 -0
- package/dist/main/backup/MigrationManager.js.map +1 -0
- package/dist/main/commands/SlashCommandManager.js +399 -0
- package/dist/main/commands/SlashCommandManager.js.map +1 -0
- package/dist/main/config/ClaudeMdParser.js +519 -0
- package/dist/main/config/ClaudeMdParser.js.map +1 -0
- package/dist/main/config/CustomizationManager.js +381 -0
- package/dist/main/config/CustomizationManager.js.map +1 -0
- package/dist/main/config/LaunchConfigManager.js +211 -0
- package/dist/main/config/LaunchConfigManager.js.map +1 -0
- package/dist/main/config/SettingsManager.js +166 -0
- package/dist/main/config/SettingsManager.js.map +1 -0
- package/dist/main/connectors/ConnectorManager.js +151 -0
- package/dist/main/connectors/ConnectorManager.js.map +1 -0
- package/dist/main/connectors/DatabaseConnector.js +222 -0
- package/dist/main/connectors/DatabaseConnector.js.map +1 -0
- package/dist/main/cowork/CoworkManager.js +324 -0
- package/dist/main/cowork/CoworkManager.js.map +1 -0
- package/dist/main/evals/AgentEvalFramework.js +538 -0
- package/dist/main/evals/AgentEvalFramework.js.map +1 -0
- package/dist/main/evals/GraderManager.js +285 -0
- package/dist/main/evals/GraderManager.js.map +1 -0
- package/dist/main/git/GitWorktreeManager.js +214 -0
- package/dist/main/git/GitWorktreeManager.js.map +1 -0
- package/dist/main/github/GitHubPRMonitor.js +244 -0
- package/dist/main/github/GitHubPRMonitor.js.map +1 -0
- package/dist/main/ide/ContinueInManager.js +181 -0
- package/dist/main/ide/ContinueInManager.js.map +1 -0
- package/dist/main/ide/IDEIntegration.js +277 -0
- package/dist/main/ide/IDEIntegration.js.map +1 -0
- package/dist/main/integrations/LinearManager.js +252 -0
- package/dist/main/integrations/LinearManager.js.map +1 -0
- package/dist/main/integrations/SlackBotManager.js +247 -0
- package/dist/main/integrations/SlackBotManager.js.map +1 -0
- package/dist/main/lsp/LSPManager.js +394 -0
- package/dist/main/lsp/LSPManager.js.map +1 -0
- package/dist/main/main.js +1087 -0
- package/dist/main/main.js.map +1 -0
- package/dist/main/mcp/MCPConfigurationManager.js +281 -0
- package/dist/main/mcp/MCPConfigurationManager.js.map +1 -0
- package/dist/main/mcp/MCPManager.js +710 -0
- package/dist/main/mcp/MCPManager.js.map +1 -0
- package/dist/main/mcp/MCPRegistry.js +272 -0
- package/dist/main/mcp/MCPRegistry.js.map +1 -0
- package/dist/main/monitoring/ErrorRecoveryManager.js +268 -0
- package/dist/main/monitoring/ErrorRecoveryManager.js.map +1 -0
- package/dist/main/monitoring/ErrorTracker.js +57 -0
- package/dist/main/monitoring/ErrorTracker.js.map +1 -0
- package/dist/main/monitoring/MetricsCollector.js +155 -0
- package/dist/main/monitoring/MetricsCollector.js.map +1 -0
- package/dist/main/monitoring/TraceGradingSystem.js +148 -0
- package/dist/main/monitoring/TraceGradingSystem.js.map +1 -0
- package/dist/main/notifications/NotificationManager.js +67 -0
- package/dist/main/notifications/NotificationManager.js.map +1 -0
- package/dist/main/pair/AIPairProgramming.js +200 -0
- package/dist/main/pair/AIPairProgramming.js.map +1 -0
- package/dist/main/plugins/PluginManager.js +222 -0
- package/dist/main/plugins/PluginManager.js.map +1 -0
- package/dist/main/plugins/PluginMarketplace.js +237 -0
- package/dist/main/plugins/PluginMarketplace.js.map +1 -0
- package/dist/main/preload.js +189 -0
- package/dist/main/preload.js.map +1 -0
- package/dist/main/preview/PreviewSessionManager.js +170 -0
- package/dist/main/preview/PreviewSessionManager.js.map +1 -0
- package/dist/main/providers/AIProviderManager.js +327 -0
- package/dist/main/providers/AIProviderManager.js.map +1 -0
- package/dist/main/providers/FineTuningManager.js +276 -0
- package/dist/main/providers/FineTuningManager.js.map +1 -0
- package/dist/main/providers/FreeModelsProvider.js +1104 -0
- package/dist/main/providers/FreeModelsProvider.js.map +1 -0
- package/dist/main/realtime/RealtimeManager.js +116 -0
- package/dist/main/realtime/RealtimeManager.js.map +1 -0
- package/dist/main/remote/CloudEnvironmentManager.js +232 -0
- package/dist/main/remote/CloudEnvironmentManager.js.map +1 -0
- package/dist/main/remote/RemoteSessionManager.js +255 -0
- package/dist/main/remote/RemoteSessionManager.js.map +1 -0
- package/dist/main/search/DeepResearchManager.js +335 -0
- package/dist/main/search/DeepResearchManager.js.map +1 -0
- package/dist/main/search/WebSearchIntegration.js +147 -0
- package/dist/main/search/WebSearchIntegration.js.map +1 -0
- package/dist/main/security/AdminConsoleManager.js +223 -0
- package/dist/main/security/AdminConsoleManager.js.map +1 -0
- package/dist/main/security/AuditLogger.js +136 -0
- package/dist/main/security/AuditLogger.js.map +1 -0
- package/dist/main/security/PermissionManager.js +144 -0
- package/dist/main/security/PermissionManager.js.map +1 -0
- package/dist/main/security/SSOManager.js +173 -0
- package/dist/main/security/SSOManager.js.map +1 -0
- package/dist/main/security/SecurityManager.js +152 -0
- package/dist/main/security/SecurityManager.js.map +1 -0
- package/dist/main/skills/SkillsManager.js +223 -0
- package/dist/main/skills/SkillsManager.js.map +1 -0
- package/dist/main/ssh/SSHManager.js +65 -0
- package/dist/main/ssh/SSHManager.js.map +1 -0
- package/dist/main/streaming/StreamingManager.js +225 -0
- package/dist/main/streaming/StreamingManager.js.map +1 -0
- package/dist/main/sync/CloudSyncManager.js +422 -0
- package/dist/main/sync/CloudSyncManager.js.map +1 -0
- package/dist/main/types.js +28 -0
- package/dist/main/types.js.map +1 -0
- package/dist/main/verification/AutoVerifyManager.js +235 -0
- package/dist/main/verification/AutoVerifyManager.js.map +1 -0
- package/dist/main/vision/ComputerUseManager.js +376 -0
- package/dist/main/vision/ComputerUseManager.js.map +1 -0
- package/dist/main/vision/ImageVideoGenerationManager.js +401 -0
- package/dist/main/vision/ImageVideoGenerationManager.js.map +1 -0
- package/dist/main/vision/VisionManager.js +172 -0
- package/dist/main/vision/VisionManager.js.map +1 -0
- package/dist/renderer/assets/main-DJlZQBCA.js +304 -0
- package/dist/renderer/assets/main-N33ZXEr8.css +1 -0
- package/dist/renderer/index.html +21 -0
- package/dist/renderer/manifest.json +42 -0
- package/dist/renderer/sw.ts +109 -0
- package/dist/shared/types.js +35 -0
- package/dist/shared/types.js.map +1 -0
- package/docker-compose.yml +65 -0
- package/docs/API.md +307 -0
- package/docs/USER_GUIDE.md +476 -0
- package/examples/plugins/sample-plugin/package.json +41 -0
- package/examples/plugins/sample-plugin/src/index.ts +75 -0
- package/index.html +20 -0
- package/jest.config.js +39 -0
- package/package.json +180 -0
- package/packages/cli/package.json +29 -0
- package/packages/cli/src/commands/agents.ts +199 -0
- package/packages/cli/src/commands/tasks.ts +61 -0
- package/packages/cli/src/index.ts +91 -0
- package/packages/cli/src/utils/api.ts +45 -0
- package/packages/cli/src/utils/config.ts +61 -0
- package/packages/npm-installer/bin/codex-linux +126 -0
- package/packages/npm-installer/lib/download.js +273 -0
- package/packages/npm-installer/package.json +42 -0
- package/packages/vscode-extension/package.json +167 -0
- package/packages/vscode-extension/src/api.ts +68 -0
- package/packages/vscode-extension/src/extension.ts +161 -0
- package/packages/vscode-extension/src/panels/chatPanel.ts +265 -0
- package/packages/vscode-extension/src/panels/createAgentPanel.ts +227 -0
- package/packages/vscode-extension/src/providers/agentsProvider.ts +80 -0
- package/postcss.config.js +6 -0
- package/public/manifest.json +42 -0
- package/public/sw.ts +109 -0
- package/scripts/install-dev.sh +103 -0
- package/scripts/install.sh +275 -0
- package/src/main/DatabaseManager.ts +950 -0
- package/src/main/SettingsManager.ts +63 -0
- package/src/main/agents/AgentOrchestrator.ts +930 -0
- package/src/main/agents/AgentSDK.ts +269 -0
- package/src/main/agents/AgentTools.ts +380 -0
- package/src/main/agents/CodeIndex.ts +240 -0
- package/src/main/agents/EmbeddingService.ts +88 -0
- package/src/main/agents/NativeToolCalling.ts +245 -0
- package/src/main/api/APIServer.ts +316 -0
- package/src/main/api/RateLimiter.ts +165 -0
- package/src/main/api/WebSocketManager.ts +398 -0
- package/src/main/assistant/ContextOptimizer.ts +214 -0
- package/src/main/assistant/PredictedOutputManager.ts +265 -0
- package/src/main/assistant/PromptCacheManager.ts +280 -0
- package/src/main/assistant/PromptOptimizer.ts +746 -0
- package/src/main/assistant/SmartCodeAssistant.ts +234 -0
- package/src/main/auth/SessionManager.ts +415 -0
- package/src/main/automations/AdvancedWebhookSystem.ts +281 -0
- package/src/main/automations/AutomationScheduler.ts +272 -0
- package/src/main/automations/BatchProcessingSystem.ts +207 -0
- package/src/main/automations/BrowserAutomationManager.ts +203 -0
- package/src/main/automations/GitHubActionsManager.ts +151 -0
- package/src/main/automations/GitLabCIManager.ts +206 -0
- package/src/main/automations/PriorityQueueManager.ts +328 -0
- package/src/main/background/BackgroundModeManager.ts +130 -0
- package/src/main/backup/BackupManager.ts +287 -0
- package/src/main/backup/MigrationManager.ts +132 -0
- package/src/main/commands/SlashCommandManager.ts +407 -0
- package/src/main/config/ClaudeMdParser.ts +539 -0
- package/src/main/config/CustomizationManager.ts +493 -0
- package/src/main/config/LaunchConfigManager.ts +212 -0
- package/src/main/config/SettingsManager.ts +163 -0
- package/src/main/connectors/ConnectorManager.ts +175 -0
- package/src/main/connectors/DatabaseConnector.ts +212 -0
- package/src/main/cowork/CoworkManager.ts +431 -0
- package/src/main/evals/AgentEvalFramework.ts +665 -0
- package/src/main/evals/GraderManager.ts +417 -0
- package/src/main/git/GitWorktreeManager.ts +211 -0
- package/src/main/github/GitHubPRMonitor.ts +317 -0
- package/src/main/ide/ContinueInManager.ts +180 -0
- package/src/main/ide/IDEIntegration.ts +288 -0
- package/src/main/integrations/LinearManager.ts +327 -0
- package/src/main/integrations/SlackBotManager.ts +312 -0
- package/src/main/lsp/LSPManager.ts +445 -0
- package/src/main/main.ts +1221 -0
- package/src/main/mcp/MCPConfigurationManager.ts +281 -0
- package/src/main/mcp/MCPManager.ts +799 -0
- package/src/main/mcp/MCPRegistry.ts +273 -0
- package/src/main/monitoring/ErrorRecoveryManager.ts +359 -0
- package/src/main/monitoring/ErrorTracker.ts +60 -0
- package/src/main/monitoring/MetricsCollector.ts +196 -0
- package/src/main/monitoring/TraceGradingSystem.ts +196 -0
- package/src/main/notifications/NotificationManager.ts +96 -0
- package/src/main/pair/AIPairProgramming.ts +290 -0
- package/src/main/plugins/PluginManager.ts +266 -0
- package/src/main/plugins/PluginMarketplace.ts +318 -0
- package/src/main/preload.ts +215 -0
- package/src/main/preview/PreviewSessionManager.ts +186 -0
- package/src/main/providers/AIProviderManager.ts +394 -0
- package/src/main/providers/FineTuningManager.ts +390 -0
- package/src/main/providers/FreeModelsProvider.ts +1156 -0
- package/src/main/realtime/RealtimeManager.ts +147 -0
- package/src/main/remote/CloudEnvironmentManager.ts +253 -0
- package/src/main/remote/RemoteSessionManager.ts +323 -0
- package/src/main/search/DeepResearchManager.ts +458 -0
- package/src/main/search/WebSearchIntegration.ts +203 -0
- package/src/main/security/AdminConsoleManager.ts +244 -0
- package/src/main/security/AuditLogger.ts +143 -0
- package/src/main/security/PermissionManager.ts +184 -0
- package/src/main/security/SSOManager.ts +241 -0
- package/src/main/security/SecurityManager.ts +139 -0
- package/src/main/skills/SkillsManager.ts +218 -0
- package/src/main/ssh/SSHManager.ts +86 -0
- package/src/main/streaming/StreamingManager.ts +306 -0
- package/src/main/sync/CloudSyncManager.ts +532 -0
- package/src/main/verification/AutoVerifyManager.ts +285 -0
- package/src/main/vision/ComputerUseManager.ts +475 -0
- package/src/main/vision/ImageVideoGenerationManager.ts +526 -0
- package/src/main/vision/VisionManager.ts +186 -0
- package/src/renderer/App.tsx +314 -0
- package/src/renderer/components/AdvancedSettingsPanel.tsx +225 -0
- package/src/renderer/components/AgentPanel.tsx +760 -0
- package/src/renderer/components/AppPreview.tsx +220 -0
- package/src/renderer/components/AuditTrailPanel.tsx +148 -0
- package/src/renderer/components/AutomationPanel.tsx +220 -0
- package/src/renderer/components/ChatInterface.tsx +595 -0
- package/src/renderer/components/ChatTab.tsx +296 -0
- package/src/renderer/components/CodeEditor.tsx +257 -0
- package/src/renderer/components/CodeReviewPanel.tsx +256 -0
- package/src/renderer/components/CodeWorkspace.tsx +192 -0
- package/src/renderer/components/CodebaseDashboard.tsx +295 -0
- package/src/renderer/components/ComputerUsePanel.tsx +262 -0
- package/src/renderer/components/ConnectorsPanel.tsx +471 -0
- package/src/renderer/components/ContextMenu.tsx +155 -0
- package/src/renderer/components/ContextUsageDisplay.tsx +248 -0
- package/src/renderer/components/CoworkPanel.tsx +415 -0
- package/src/renderer/components/DiffViewer.tsx +452 -0
- package/src/renderer/components/ErrorBoundary.tsx +273 -0
- package/src/renderer/components/ExtendedThinkingToggle.tsx +244 -0
- package/src/renderer/components/FileAttachments.tsx +247 -0
- package/src/renderer/components/FileExplorer.tsx +242 -0
- package/src/renderer/components/FileExplorerPanel.tsx +302 -0
- package/src/renderer/components/GitPanel.tsx +154 -0
- package/src/renderer/components/Header.tsx +113 -0
- package/src/renderer/components/MCPPanel.tsx +326 -0
- package/src/renderer/components/MentionAutocomplete.tsx +239 -0
- package/src/renderer/components/PermissionPanel.tsx +159 -0
- package/src/renderer/components/PermissionSelector.tsx +203 -0
- package/src/renderer/components/PluginMarketplace.tsx +325 -0
- package/src/renderer/components/PromptOptimizerPanel.tsx +399 -0
- package/src/renderer/components/SearchPanel.tsx +173 -0
- package/src/renderer/components/SearchReplace.tsx +284 -0
- package/src/renderer/components/SessionSidebar.tsx +367 -0
- package/src/renderer/components/SettingsPanel.tsx +426 -0
- package/src/renderer/components/Sidebar.tsx +100 -0
- package/src/renderer/components/SkillsPanel.tsx +245 -0
- package/src/renderer/components/SplitPane.tsx +173 -0
- package/src/renderer/components/Terminal.tsx +190 -0
- package/src/renderer/components/VoiceCommand.tsx +129 -0
- package/src/renderer/components/WorktreePanel.tsx +163 -0
- package/src/renderer/components/ui/AriaComponents.tsx +193 -0
- package/src/renderer/components/ui/Button.tsx +68 -0
- package/src/renderer/components/ui/Card.tsx +102 -0
- package/src/renderer/components/ui/Input.tsx +44 -0
- package/src/renderer/components/ui/Skeleton.tsx +55 -0
- package/src/renderer/components/ui/VirtualList.tsx +196 -0
- package/src/renderer/i18n/I18nProvider.tsx +101 -0
- package/src/renderer/i18n/de.ts +161 -0
- package/src/renderer/i18n/en.ts +163 -0
- package/src/renderer/i18n/es.ts +161 -0
- package/src/renderer/i18n/fr.ts +161 -0
- package/src/renderer/i18n/index.ts +44 -0
- package/src/renderer/index.css +129 -0
- package/src/renderer/lib/accessibility.tsx +287 -0
- package/src/renderer/lib/hooks.ts +304 -0
- package/src/renderer/lib/utils.ts +6 -0
- package/src/renderer/main.tsx +25 -0
- package/src/renderer/styles/minimalist.css +539 -0
- package/src/renderer/sw.ts +180 -0
- package/src/renderer/types.d.ts +138 -0
- package/src/shared/types.ts +813 -0
- package/supabase/schema.sql +234 -0
- package/tailwind.config.js +78 -0
- package/tests/e2e/package.json +15 -0
- package/tests/e2e/playwright.config.ts +31 -0
- package/tests/e2e/specs/app.spec.ts +194 -0
- package/tests/setup.ts +99 -0
- package/tests/unit/AgentOrchestrator.test.ts +274 -0
- package/tests/unit/DatabaseManager.test.ts +262 -0
- package/tests/unit/GitWorktreeManager.test.ts +150 -0
- package/tests/unit/SecurityManager.test.ts +110 -0
- package/tsconfig.main.json +22 -0
- package/tsconfig.renderer.json +27 -0
- package/vite.config.ts +28 -0
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
import log from 'electron-log';
|
|
4
|
+
import fetch from 'node-fetch';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
8
|
+
export type GenerationProvider = 'openai' | 'anthropic' | 'stability' | 'runway' | 'custom';
|
|
9
|
+
export type GenerationType = 'image' | 'video';
|
|
10
|
+
export type GenerationStatus = 'pending' | 'processing' | 'completed' | 'failed';
|
|
11
|
+
|
|
12
|
+
export interface ImageGenerationRequest {
|
|
13
|
+
prompt: string;
|
|
14
|
+
negativePrompt?: string;
|
|
15
|
+
model?: string;
|
|
16
|
+
width?: number;
|
|
17
|
+
height?: number;
|
|
18
|
+
numImages?: number;
|
|
19
|
+
style?: string;
|
|
20
|
+
quality?: 'standard' | 'hd';
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface VideoGenerationRequest {
|
|
24
|
+
prompt: string;
|
|
25
|
+
model?: string;
|
|
26
|
+
duration?: number;
|
|
27
|
+
aspectRatio?: string;
|
|
28
|
+
numFrames?: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface GenerationJob {
|
|
32
|
+
id: string;
|
|
33
|
+
type: GenerationType;
|
|
34
|
+
provider: GenerationProvider;
|
|
35
|
+
status: GenerationStatus;
|
|
36
|
+
request: ImageGenerationRequest | VideoGenerationRequest;
|
|
37
|
+
result?: GenerationResult;
|
|
38
|
+
progress: number;
|
|
39
|
+
createdAt: Date;
|
|
40
|
+
startedAt?: Date;
|
|
41
|
+
completedAt?: Date;
|
|
42
|
+
cost?: number;
|
|
43
|
+
metadata: Record<string, unknown>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface GenerationResult {
|
|
47
|
+
urls: string[];
|
|
48
|
+
localPaths?: string[];
|
|
49
|
+
metadata: Record<string, unknown>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface GenerationConfig {
|
|
53
|
+
provider: GenerationProvider;
|
|
54
|
+
apiKey: string;
|
|
55
|
+
baseUrl?: string;
|
|
56
|
+
defaultModel?: string;
|
|
57
|
+
outputDirectory?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export class ImageVideoGenerationManager extends EventEmitter {
|
|
61
|
+
private jobs: Map<string, GenerationJob> = new Map();
|
|
62
|
+
private config: GenerationConfig | null = null;
|
|
63
|
+
|
|
64
|
+
constructor() {
|
|
65
|
+
super();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
configure(config: GenerationConfig): void {
|
|
69
|
+
const outputDir = config.outputDirectory || './generated';
|
|
70
|
+
this.config = {
|
|
71
|
+
...config,
|
|
72
|
+
outputDirectory: outputDir,
|
|
73
|
+
};
|
|
74
|
+
log.info('ImageVideoGenerationManager configured', {
|
|
75
|
+
provider: config.provider,
|
|
76
|
+
outputDirectory: outputDir,
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (!fs.existsSync(outputDir)) {
|
|
80
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
getConfig(): GenerationConfig | null {
|
|
85
|
+
return this.config;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async generateImage(request: ImageGenerationRequest): Promise<GenerationJob> {
|
|
89
|
+
if (!this.config) {
|
|
90
|
+
throw new Error('ImageVideoGenerationManager not configured');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const job: GenerationJob = {
|
|
94
|
+
id: uuidv4(),
|
|
95
|
+
type: 'image',
|
|
96
|
+
provider: this.config.provider,
|
|
97
|
+
status: 'pending',
|
|
98
|
+
request,
|
|
99
|
+
progress: 0,
|
|
100
|
+
createdAt: new Date(),
|
|
101
|
+
metadata: {},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
this.jobs.set(job.id, job);
|
|
105
|
+
this.emit('job:created', job);
|
|
106
|
+
log.info(`Image generation job created: ${job.id}`);
|
|
107
|
+
|
|
108
|
+
this.processImageJob(job);
|
|
109
|
+
return job;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async generateVideo(request: VideoGenerationRequest): Promise<GenerationJob> {
|
|
113
|
+
if (!this.config) {
|
|
114
|
+
throw new Error('ImageVideoGenerationManager not configured');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const job: GenerationJob = {
|
|
118
|
+
id: uuidv4(),
|
|
119
|
+
type: 'video',
|
|
120
|
+
provider: this.config.provider,
|
|
121
|
+
status: 'pending',
|
|
122
|
+
request,
|
|
123
|
+
progress: 0,
|
|
124
|
+
createdAt: new Date(),
|
|
125
|
+
metadata: {},
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
this.jobs.set(job.id, job);
|
|
129
|
+
this.emit('job:created', job);
|
|
130
|
+
log.info(`Video generation job created: ${job.id}`);
|
|
131
|
+
|
|
132
|
+
this.processVideoJob(job);
|
|
133
|
+
return job;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private async processImageJob(job: GenerationJob): Promise<void> {
|
|
137
|
+
job.status = 'processing';
|
|
138
|
+
job.startedAt = new Date();
|
|
139
|
+
this.emit('job:started', job);
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
let result: GenerationResult;
|
|
143
|
+
|
|
144
|
+
switch (job.provider) {
|
|
145
|
+
case 'openai':
|
|
146
|
+
result = await this.generateWithOpenAI(job.request as ImageGenerationRequest);
|
|
147
|
+
break;
|
|
148
|
+
case 'stability':
|
|
149
|
+
result = await this.generateWithStability(job.request as ImageGenerationRequest);
|
|
150
|
+
break;
|
|
151
|
+
case 'custom':
|
|
152
|
+
result = await this.generateWithCustom(job.request as ImageGenerationRequest);
|
|
153
|
+
break;
|
|
154
|
+
default:
|
|
155
|
+
result = await this.mockImageGeneration(job.request as ImageGenerationRequest);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
job.result = result;
|
|
159
|
+
job.progress = 100;
|
|
160
|
+
job.status = 'completed';
|
|
161
|
+
job.completedAt = new Date();
|
|
162
|
+
job.cost = this.estimateCost('image', job.request as ImageGenerationRequest);
|
|
163
|
+
|
|
164
|
+
if (result.urls.length > 0 && this.config?.outputDirectory) {
|
|
165
|
+
const localPaths = await this.downloadAssets(result.urls, job.id);
|
|
166
|
+
job.result.localPaths = localPaths;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.emit('job:completed', job);
|
|
170
|
+
log.info(`Image generation completed: ${job.id}`);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
job.status = 'failed';
|
|
173
|
+
job.completedAt = new Date();
|
|
174
|
+
this.emit('job:failed', { job, error });
|
|
175
|
+
log.error(`Image generation failed: ${job.id}`, error);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private async processVideoJob(job: GenerationJob): Promise<void> {
|
|
180
|
+
job.status = 'processing';
|
|
181
|
+
job.startedAt = new Date();
|
|
182
|
+
this.emit('job:started', job);
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
let result: GenerationResult;
|
|
186
|
+
|
|
187
|
+
switch (job.provider) {
|
|
188
|
+
case 'runway':
|
|
189
|
+
result = await this.generateWithRunway(job.request as VideoGenerationRequest);
|
|
190
|
+
break;
|
|
191
|
+
case 'custom':
|
|
192
|
+
result = await this.generateCustomVideo(job.request as VideoGenerationRequest);
|
|
193
|
+
break;
|
|
194
|
+
default:
|
|
195
|
+
result = await this.mockVideoGeneration(job.request as VideoGenerationRequest);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
job.result = result;
|
|
199
|
+
job.progress = 100;
|
|
200
|
+
job.status = 'completed';
|
|
201
|
+
job.completedAt = new Date();
|
|
202
|
+
job.cost = this.estimateCost('video', job.request as VideoGenerationRequest);
|
|
203
|
+
|
|
204
|
+
if (result.urls.length > 0 && this.config?.outputDirectory) {
|
|
205
|
+
const localPaths = await this.downloadAssets(result.urls, job.id);
|
|
206
|
+
job.result.localPaths = localPaths;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
this.emit('job:completed', job);
|
|
210
|
+
log.info(`Video generation completed: ${job.id}`);
|
|
211
|
+
} catch (error) {
|
|
212
|
+
job.status = 'failed';
|
|
213
|
+
job.completedAt = new Date();
|
|
214
|
+
this.emit('job:failed', { job, error });
|
|
215
|
+
log.error(`Video generation failed: ${job.id}`, error);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
private async generateWithOpenAI(request: ImageGenerationRequest): Promise<GenerationResult> {
|
|
220
|
+
if (!this.config?.apiKey) {
|
|
221
|
+
throw new Error('API key not configured');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const response = await fetch('https://api.openai.com/v1/images/generations', {
|
|
225
|
+
method: 'POST',
|
|
226
|
+
headers: {
|
|
227
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
228
|
+
'Content-Type': 'application/json',
|
|
229
|
+
},
|
|
230
|
+
body: JSON.stringify({
|
|
231
|
+
model: request.model || 'dall-e-3',
|
|
232
|
+
prompt: request.prompt,
|
|
233
|
+
n: request.numImages || 1,
|
|
234
|
+
size: `${request.width || 1024}x${request.height || 1024}`,
|
|
235
|
+
quality: request.quality || 'standard',
|
|
236
|
+
}),
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
if (!response.ok) {
|
|
240
|
+
const error = await response.text();
|
|
241
|
+
throw new Error(`OpenAI API error: ${error}`);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const data = await response.json() as { data: Array<{ url: string; revised_prompt: string }> };
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
urls: data.data.map((d) => d.url),
|
|
248
|
+
metadata: {
|
|
249
|
+
revisedPrompt: data.data[0]?.revised_prompt,
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
private async generateWithStability(request: ImageGenerationRequest): Promise<GenerationResult> {
|
|
255
|
+
if (!this.config?.apiKey) {
|
|
256
|
+
throw new Error('API key not configured');
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const response = await fetch('https://api.stability.ai/v2beta/image generation', {
|
|
260
|
+
method: 'POST',
|
|
261
|
+
headers: {
|
|
262
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
263
|
+
'Content-Type': 'application/json',
|
|
264
|
+
},
|
|
265
|
+
body: JSON.stringify({
|
|
266
|
+
prompt: request.prompt,
|
|
267
|
+
negative_prompt: request.negativePrompt,
|
|
268
|
+
model: request.model || 'stable-diffusion-xl-1024-v1-0',
|
|
269
|
+
width: request.width || 1024,
|
|
270
|
+
height: request.height || 1024,
|
|
271
|
+
samples: request.numImages || 1,
|
|
272
|
+
}),
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (!response.ok) {
|
|
276
|
+
const error = await response.text();
|
|
277
|
+
throw new Error(`Stability AI API error: ${error}`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const data = await response.json() as { artifacts: Array<{ base64: string }> };
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
urls: [],
|
|
284
|
+
metadata: {
|
|
285
|
+
artifacts: data.artifacts,
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
private async generateWithCustom(request: ImageGenerationRequest): Promise<GenerationResult> {
|
|
291
|
+
if (!this.config?.baseUrl) {
|
|
292
|
+
throw new Error('Custom provider requires baseUrl');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const response = await fetch(`${this.config.baseUrl}/generate/image`, {
|
|
296
|
+
method: 'POST',
|
|
297
|
+
headers: {
|
|
298
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
299
|
+
'Content-Type': 'application/json',
|
|
300
|
+
},
|
|
301
|
+
body: JSON.stringify(request),
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
if (!response.ok) {
|
|
305
|
+
throw new Error(`Custom provider error: ${response.statusText}`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const data = await response.json() as { urls: string[] };
|
|
309
|
+
return { urls: data.urls, metadata: {} };
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
private async mockImageGeneration(request: ImageGenerationRequest): Promise<GenerationResult> {
|
|
313
|
+
await this.simulateDelay(3000);
|
|
314
|
+
|
|
315
|
+
const numImages = request.numImages || 1;
|
|
316
|
+
const urls: string[] = [];
|
|
317
|
+
|
|
318
|
+
for (let i = 0; i < numImages; i++) {
|
|
319
|
+
const seed = Math.floor(Math.random() * 100000);
|
|
320
|
+
urls.push(`https://picsum.photos/seed/${seed}/1024/1024`);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return { urls, metadata: { mock: true, prompt: request.prompt } };
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
private async generateWithRunway(request: VideoGenerationRequest): Promise<GenerationResult> {
|
|
327
|
+
if (!this.config?.apiKey) {
|
|
328
|
+
throw new Error('API key not configured');
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
const response = await fetch('https://api.runwayml.com/v1/generation/video', {
|
|
332
|
+
method: 'POST',
|
|
333
|
+
headers: {
|
|
334
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
335
|
+
'Content-Type': 'application/json',
|
|
336
|
+
},
|
|
337
|
+
body: JSON.stringify({
|
|
338
|
+
model: request.model || 'gen3a',
|
|
339
|
+
prompt: request.prompt,
|
|
340
|
+
duration: request.duration || 5,
|
|
341
|
+
aspect_ratio: request.aspectRatio || '16:9',
|
|
342
|
+
}),
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
if (!response.ok) {
|
|
346
|
+
const error = await response.text();
|
|
347
|
+
throw new Error(`Runway API error: ${error}`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const data = await response.json() as { id: string; status: string };
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
urls: [],
|
|
354
|
+
metadata: { jobId: data.id, status: data.status },
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private async generateCustomVideo(request: VideoGenerationRequest): Promise<GenerationResult> {
|
|
359
|
+
if (!this.config?.baseUrl) {
|
|
360
|
+
throw new Error('Custom provider requires baseUrl');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
const response = await fetch(`${this.config.baseUrl}/generate/video`, {
|
|
364
|
+
method: 'POST',
|
|
365
|
+
headers: {
|
|
366
|
+
'Authorization': `Bearer ${this.config.apiKey}`,
|
|
367
|
+
'Content-Type': 'application/json',
|
|
368
|
+
},
|
|
369
|
+
body: JSON.stringify(request),
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
if (!response.ok) {
|
|
373
|
+
throw new Error(`Custom provider error: ${response.statusText}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const data = await response.json() as { urls: string[] };
|
|
377
|
+
return { urls: data.urls, metadata: {} };
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
private async mockVideoGeneration(request: VideoGenerationRequest): Promise<GenerationResult> {
|
|
381
|
+
await this.simulateDelay(10000);
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
urls: ['https://sample-videos.com/video123/mp4/720/big_buck_bunny_720p_1mb.mp4'],
|
|
385
|
+
metadata: { mock: true, prompt: request.prompt, duration: request.duration || 5 },
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
private async downloadAssets(urls: string[], jobId: string): Promise<string[]> {
|
|
390
|
+
if (!this.config?.outputDirectory) return [];
|
|
391
|
+
|
|
392
|
+
const localPaths: string[] = [];
|
|
393
|
+
const outputDir = path.join(this.config.outputDirectory, jobId);
|
|
394
|
+
|
|
395
|
+
if (!fs.existsSync(outputDir)) {
|
|
396
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
for (let i = 0; i < urls.length; i++) {
|
|
400
|
+
const url = urls[i];
|
|
401
|
+
const ext = path.extname(new URL(url).pathname) || '.jpg';
|
|
402
|
+
const filename = `asset_${i}${ext}`;
|
|
403
|
+
const localPath = path.join(outputDir, filename);
|
|
404
|
+
|
|
405
|
+
try {
|
|
406
|
+
const response = await fetch(url);
|
|
407
|
+
if (response.ok) {
|
|
408
|
+
const buffer = await response.arrayBuffer();
|
|
409
|
+
fs.writeFileSync(localPath, Buffer.from(buffer));
|
|
410
|
+
localPaths.push(localPath);
|
|
411
|
+
}
|
|
412
|
+
} catch (error) {
|
|
413
|
+
log.debug(`Failed to download ${url}:`, error);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return localPaths;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private estimateCost(
|
|
421
|
+
type: GenerationType,
|
|
422
|
+
request: ImageGenerationRequest | VideoGenerationRequest
|
|
423
|
+
): number {
|
|
424
|
+
if (type === 'image') {
|
|
425
|
+
const req = request as ImageGenerationRequest;
|
|
426
|
+
const size = (req.width || 1024) * (req.height || 1024);
|
|
427
|
+
const baseCost = size > 1024 * 1024 ? 0.08 : 0.04;
|
|
428
|
+
return (req.numImages || 1) * baseCost;
|
|
429
|
+
} else {
|
|
430
|
+
const req = request as VideoGenerationRequest;
|
|
431
|
+
return (req.duration || 5) * 0.5;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
private async simulateDelay(ms: number): Promise<void> {
|
|
436
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
getJob(id: string): GenerationJob | undefined {
|
|
440
|
+
return this.jobs.get(id);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
listJobs(filter?: {
|
|
444
|
+
type?: GenerationType;
|
|
445
|
+
provider?: GenerationProvider;
|
|
446
|
+
status?: GenerationStatus;
|
|
447
|
+
}): GenerationJob[] {
|
|
448
|
+
let jobs = Array.from(this.jobs.values());
|
|
449
|
+
|
|
450
|
+
if (filter?.type) {
|
|
451
|
+
jobs = jobs.filter((j) => j.type === filter.type);
|
|
452
|
+
}
|
|
453
|
+
if (filter?.provider) {
|
|
454
|
+
jobs = jobs.filter((j) => j.provider === filter.provider);
|
|
455
|
+
}
|
|
456
|
+
if (filter?.status) {
|
|
457
|
+
jobs = jobs.filter((j) => j.status === filter.status);
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
return jobs.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
cancelJob(id: string): boolean {
|
|
464
|
+
const job = this.jobs.get(id);
|
|
465
|
+
if (!job || job.status !== 'pending' && job.status !== 'processing') {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
job.status = 'failed';
|
|
470
|
+
job.completedAt = new Date();
|
|
471
|
+
this.emit('job:cancelled', job);
|
|
472
|
+
log.info(`Generation job cancelled: ${id}`);
|
|
473
|
+
return true;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
deleteJob(id: string): boolean {
|
|
477
|
+
const job = this.jobs.get(id);
|
|
478
|
+
if (!job) return false;
|
|
479
|
+
|
|
480
|
+
if (job.result?.localPaths) {
|
|
481
|
+
const outputDir = path.dirname(job.result.localPaths[0]);
|
|
482
|
+
try {
|
|
483
|
+
fs.rmSync(outputDir, { recursive: true, force: true });
|
|
484
|
+
} catch (error) {
|
|
485
|
+
log.debug(`Failed to clean up job files: ${outputDir}`, error);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
this.jobs.delete(id);
|
|
490
|
+
this.emit('job:deleted', { id });
|
|
491
|
+
return true;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
getStats(): {
|
|
495
|
+
totalJobs: number;
|
|
496
|
+
completed: number;
|
|
497
|
+
failed: number;
|
|
498
|
+
byType: Record<GenerationType, number>;
|
|
499
|
+
totalCost: number;
|
|
500
|
+
} {
|
|
501
|
+
const jobs = Array.from(this.jobs.values());
|
|
502
|
+
const byType: Record<GenerationType, number> = { image: 0, video: 0 };
|
|
503
|
+
let totalCost = 0;
|
|
504
|
+
|
|
505
|
+
jobs.forEach((job) => {
|
|
506
|
+
byType[job.type]++;
|
|
507
|
+
if (job.cost) totalCost += job.cost;
|
|
508
|
+
});
|
|
509
|
+
|
|
510
|
+
return {
|
|
511
|
+
totalJobs: jobs.length,
|
|
512
|
+
completed: jobs.filter((j) => j.status === 'completed').length,
|
|
513
|
+
failed: jobs.filter((j) => j.status === 'failed').length,
|
|
514
|
+
byType,
|
|
515
|
+
totalCost,
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
cleanup(): void {
|
|
520
|
+
this.jobs.clear();
|
|
521
|
+
this.removeAllListeners();
|
|
522
|
+
log.info('ImageVideoGenerationManager cleaned up');
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
export default ImageVideoGenerationManager;
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { EventEmitter } from 'events';
|
|
2
|
+
import log from 'electron-log';
|
|
3
|
+
import { AIProviderManager } from '../providers/AIProviderManager';
|
|
4
|
+
|
|
5
|
+
export interface ImageAnalysisRequest {
|
|
6
|
+
imagePath?: string;
|
|
7
|
+
imageBase64?: string;
|
|
8
|
+
imageUrl?: string;
|
|
9
|
+
prompt: string;
|
|
10
|
+
model?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface ImageAnalysisResult {
|
|
14
|
+
content: string;
|
|
15
|
+
model: string;
|
|
16
|
+
tokensUsed?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ScreenshotCapture {
|
|
20
|
+
dataUrl: string;
|
|
21
|
+
width: number;
|
|
22
|
+
height: number;
|
|
23
|
+
timestamp: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class VisionManager extends EventEmitter {
|
|
27
|
+
private aiProviderManager: AIProviderManager;
|
|
28
|
+
private supportedModels: string[] = [
|
|
29
|
+
'gpt-4o',
|
|
30
|
+
'gpt-4o-mini',
|
|
31
|
+
'gpt-4-turbo',
|
|
32
|
+
'claude-3-5-sonnet-20241022',
|
|
33
|
+
'claude-3-opus-20240229',
|
|
34
|
+
'claude-3-5-haiku-20241022'
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
constructor(aiProviderManager: AIProviderManager) {
|
|
38
|
+
super();
|
|
39
|
+
this.aiProviderManager = aiProviderManager;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async analyzeImage(request: ImageAnalysisRequest): Promise<ImageAnalysisResult> {
|
|
43
|
+
const model = request.model || 'gpt-4o';
|
|
44
|
+
|
|
45
|
+
if (!this.isVisionSupported(model)) {
|
|
46
|
+
throw new Error(`Model ${model} does not support vision`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
log.info('Vision: Analyzing image...');
|
|
51
|
+
|
|
52
|
+
// Prepare image data
|
|
53
|
+
let imageData: string;
|
|
54
|
+
if (request.imageBase64) {
|
|
55
|
+
imageData = request.imageBase64;
|
|
56
|
+
} else if (request.imagePath) {
|
|
57
|
+
imageData = await this.imagePathToBase64(request.imagePath);
|
|
58
|
+
} else if (request.imageUrl) {
|
|
59
|
+
imageData = request.imageUrl;
|
|
60
|
+
} else {
|
|
61
|
+
throw new Error('No image provided');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const provider = this.aiProviderManager.getActiveProviderInstance();
|
|
65
|
+
if (!provider) {
|
|
66
|
+
throw new Error('No AI provider available');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Call vision API
|
|
70
|
+
const messages = [{
|
|
71
|
+
role: 'user' as const,
|
|
72
|
+
content: [
|
|
73
|
+
{ type: 'text' as const, text: request.prompt },
|
|
74
|
+
{
|
|
75
|
+
type: 'image_url' as const,
|
|
76
|
+
image_url: {
|
|
77
|
+
url: imageData.startsWith('http') ? imageData : `data:image/jpeg;base64,${imageData}`,
|
|
78
|
+
detail: 'high'
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
}];
|
|
83
|
+
|
|
84
|
+
const response = await (provider as any).sendMessage(model, messages);
|
|
85
|
+
|
|
86
|
+
log.info('Vision: Analysis complete');
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
content: response.content,
|
|
90
|
+
model: response.metadata?.model || model,
|
|
91
|
+
tokensUsed: response.metadata?.usage?.total_tokens
|
|
92
|
+
};
|
|
93
|
+
} catch (error) {
|
|
94
|
+
log.error('Vision: Analysis failed:', error);
|
|
95
|
+
throw error;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async analyzeScreenshot(
|
|
100
|
+
screenshotData: string,
|
|
101
|
+
prompt: string = 'Analyze this screenshot and describe what you see.'
|
|
102
|
+
): Promise<ImageAnalysisResult> {
|
|
103
|
+
return this.analyzeImage({
|
|
104
|
+
imageBase64: screenshotData,
|
|
105
|
+
prompt
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async analyzeUI(
|
|
110
|
+
screenshotData: string,
|
|
111
|
+
context?: {
|
|
112
|
+
currentTask?: string;
|
|
113
|
+
expectedElements?: string[];
|
|
114
|
+
}
|
|
115
|
+
): Promise<{
|
|
116
|
+
description: string;
|
|
117
|
+
issues: string[];
|
|
118
|
+
suggestions: string[];
|
|
119
|
+
}> {
|
|
120
|
+
const prompt = context?.currentTask
|
|
121
|
+
? `Analyze this UI screenshot in the context of: ${context.currentTask}. Identify any issues, missing elements, or areas for improvement.`
|
|
122
|
+
: 'Analyze this UI screenshot. Identify any visual issues, accessibility concerns, or UI/UX improvements.';
|
|
123
|
+
|
|
124
|
+
const result = await this.analyzeScreenshot(screenshotData, prompt);
|
|
125
|
+
|
|
126
|
+
// Parse the response to extract structured information
|
|
127
|
+
const lines = result.content.split('\n');
|
|
128
|
+
const issues: string[] = [];
|
|
129
|
+
const suggestions: string[] = [];
|
|
130
|
+
let description = '';
|
|
131
|
+
let currentSection = '';
|
|
132
|
+
|
|
133
|
+
for (const line of lines) {
|
|
134
|
+
const trimmed = line.trim();
|
|
135
|
+
if (trimmed.toLowerCase().includes('issue') || trimmed.toLowerCase().includes('problem')) {
|
|
136
|
+
currentSection = 'issues';
|
|
137
|
+
} else if (trimmed.toLowerCase().includes('suggestion') || trimmed.toLowerCase().includes('improvement')) {
|
|
138
|
+
currentSection = 'suggestions';
|
|
139
|
+
} else if (trimmed.startsWith('-') || trimmed.startsWith('•')) {
|
|
140
|
+
if (currentSection === 'issues') {
|
|
141
|
+
issues.push(trimmed.substring(1).trim());
|
|
142
|
+
} else if (currentSection === 'suggestions') {
|
|
143
|
+
suggestions.push(trimmed.substring(1).trim());
|
|
144
|
+
}
|
|
145
|
+
} else if (!currentSection) {
|
|
146
|
+
description += trimmed + ' ';
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
description: description.trim() || result.content,
|
|
152
|
+
issues,
|
|
153
|
+
suggestions
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
async captureAndAnalyze(
|
|
158
|
+
captureFn: () => Promise<string>,
|
|
159
|
+
prompt?: string
|
|
160
|
+
): Promise<ImageAnalysisResult> {
|
|
161
|
+
const screenshot = await captureFn();
|
|
162
|
+
return this.analyzeScreenshot(screenshot, prompt);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private async imagePathToBase64(imagePath: string): Promise<string> {
|
|
166
|
+
const fs = await import('fs/promises');
|
|
167
|
+
const imageBuffer = await fs.readFile(imagePath);
|
|
168
|
+
return imageBuffer.toString('base64');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
private isVisionSupported(model: string): boolean {
|
|
172
|
+
return this.supportedModels.some(supported =>
|
|
173
|
+
model.toLowerCase().includes(supported.toLowerCase())
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
getSupportedModels(): string[] {
|
|
178
|
+
return [...this.supportedModels];
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
cleanup(): void {
|
|
182
|
+
this.removeAllListeners();
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export default VisionManager;
|