diff --git a/.gitignore b/.gitignore index 4293c67..8374f9d 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ yarn-error.log /.fleet /.idea /.vscode +GEMINI.md +GEMINI.md.prompt diff --git a/app/Api/Plugins/ComfyUi.php b/app/Api/Plugins/ComfyUi.php new file mode 100644 index 0000000..99fad24 --- /dev/null +++ b/app/Api/Plugins/ComfyUi.php @@ -0,0 +1,162 @@ +apiProvider = $apiProvider; + $this->logInfo('ComfyUi plugin initialized.', ['provider_name' => $apiProvider->name]); + } + + public function getIdentifier(): string + { + return 'comfyui'; + } + + public function getName(): string + { + return 'ComfyUI'; + } + + public function isEnabled(): bool + { + return $this->apiProvider->enabled; + } + + public function enable(): bool + { + $this->apiProvider->enabled = true; + $result = $this->apiProvider->save(); + if ($result) { + $this->logInfo('ComfyUi plugin enabled.', ['provider_name' => $this->apiProvider->name]); + } else { + $this->logError('Failed to enable ComfyUi plugin.', ['provider_name' => $this->apiProvider->name]); + } + return $result; + } + + public function disable(): bool + { + $this->apiProvider->enabled = false; + $result = $this->apiProvider->save(); + if ($result) { + $this->logInfo('ComfyUi plugin disabled.', ['provider_name' => $this->apiProvider->name]); + } else { + $this->logError('Failed to disable ComfyUi plugin.', ['provider_name' => $this->apiProvider->name]); + } + return $result; + } + + public function getStatus(string $imageUUID): array + { + $this->logDebug('Getting status for image.', ['image_uuid' => $imageUUID]); + // Implement ComfyUI specific status check + return ['status' => 'unknown']; + } + + public function getProgress(string $imageUUID): array + { + $this->logDebug('Getting progress for image.', ['image_uuid' => $imageUUID]); + // Implement ComfyUI specific progress check + return ['progress' => 0]; + } + + public function processImageStyleChange(string $imagePath, string $prompt, string $modelId, ?string $parameters = null): array + { + $this->logInfo('Starting ComfyUI style change process.', ['image_path' => $imagePath]); + + // 1. Upload image to ComfyUI + $uploadResponse = $this->uploadImage($imagePath); + $filename = $uploadResponse['name']; + + // 2. Construct the prompt + $promptData = $this->constructPrompt($prompt, $filename, $modelId, $parameters); + + // 3. Queue the prompt + $queueResponse = $this->queuePrompt($promptData); + $promptId = $queueResponse['prompt_id']; + + // 4. Wait for and get the result + $result = $this->waitForResult($promptId); + + return ['base64Data' => $result]; + } + + private function uploadImage(string $imagePath): array + { + $this->logInfo('Uploading image to ComfyUI.', ['image_path' => $imagePath]); + $response = Http::attach( + 'image', file_get_contents($imagePath), basename($imagePath) + )->post($this->apiProvider->api_url . '/upload/image'); + + if ($response->failed()) { + $this->logError('ComfyUI image upload failed.', ['response' => $response->body()]); + throw new \Exception('Failed to upload image to ComfyUI'); + } + + return $response->json(); + } + + private function constructPrompt(string $prompt, string $filename, string $modelId, ?string $parameters): array + { + if (empty($parameters)) { + throw new \Exception('ComfyUI workflow (parameters) is missing.'); + } + + $workflow = $parameters; + $workflow = str_replace('__PROMPT__', $prompt, $workflow); + $workflow = str_replace('__FILENAME__', $filename, $workflow); + $workflow = str_replace('__MODEL_ID__', $modelId, $workflow); + + return json_decode($workflow, true); + } + + private function queuePrompt(array $promptData): array + { + $this->logInfo('Queueing prompt in ComfyUI.'); + $response = Http::post($this->apiProvider->api_url . '/prompt', ['prompt' => $promptData]); + + if ($response->failed()) { + $this->logError('Failed to queue prompt in ComfyUI.', ['response' => $response->body()]); + throw new \Exception('Failed to queue prompt in ComfyUI'); + } + + return $response->json(); + } + + private function waitForResult(string $promptId): string + { + $this->logInfo('Waiting for ComfyUI result.', ['prompt_id' => $promptId]); + while (true) { + $response = Http::get($this->apiProvider->api_url . '/history/' . $promptId); + $data = $response->json(); + + if (!empty($data[$promptId]['outputs'])) { + $outputs = $data[$promptId]['outputs']; + // Assuming the first output with an image is the one we want + foreach ($outputs as $output) { + if (isset($output['images'][0]['type']) && $output['images'][0]['type'] === 'output') { + $imageUrl = sprintf('%s/view?filename=%s&subfolder=%s&type=output', + $this->apiProvider->api_url, + $output['images'][0]['filename'], + $output['images'][0]['subfolder'] + ); + $image_data = file_get_contents($imageUrl); + return base64_encode($image_data); + } + } + } + + sleep(2); // Wait for 2 seconds before polling again + } + } +} diff --git a/app/Filament/Resources/AiModelResource.php b/app/Filament/Resources/AiModelResource.php index 1bf16ce..8078d56 100644 --- a/app/Filament/Resources/AiModelResource.php +++ b/app/Filament/Resources/AiModelResource.php @@ -15,6 +15,7 @@ use Illuminate\Database\Eloquent\SoftDeletingScope; use Filament\Forms\Components\TextInput; use Filament\Tables\Columns\TextColumn; use Filament\Forms\Components\Select; +use Filament\Tables\Actions\Action; class AiModelResource extends Resource { @@ -66,6 +67,12 @@ class AiModelResource extends Resource ]) ->actions([ Tables\Actions\EditAction::make(), + Action::make('duplicate') + ->label(__('filament.resource.style.action.duplicate')) + ->icon('heroicon-o-document-duplicate') + ->action(function (AiModel $record, $livewire) { + $livewire->redirect(AiModelResource::getUrl('create', ['sourceRecord' => $record->id])); + }), ]) ->bulkActions([ Tables\Actions\BulkActionGroup::make([ diff --git a/app/Filament/Resources/AiModelResource/Pages/CreateAiModel.php b/app/Filament/Resources/AiModelResource/Pages/CreateAiModel.php index 88398e2..6f11ffe 100644 --- a/app/Filament/Resources/AiModelResource/Pages/CreateAiModel.php +++ b/app/Filament/Resources/AiModelResource/Pages/CreateAiModel.php @@ -5,11 +5,24 @@ namespace App\Filament\Resources\AiModelResource\Pages; use App\Filament\Resources\AiModelResource; use Filament\Actions; use Filament\Resources\Pages\CreateRecord; +use Illuminate\Http\Request; class CreateAiModel extends CreateRecord { protected static string $resource = AiModelResource::class; + public function mount(): void + { + parent::mount(); + + if ($sourceRecordId = request()->query('sourceRecord')) { + $sourceRecord = \App\Models\AiModel::find($sourceRecordId); + if ($sourceRecord) { + $this->form->fill($sourceRecord->attributesToArray()); + } + } + } + protected function getRedirectUrl(): string { return $this->getResource()::getUrl('index'); diff --git a/app/Filament/Resources/StyleResource.php b/app/Filament/Resources/StyleResource.php index af8d92e..f1eb4a9 100644 --- a/app/Filament/Resources/StyleResource.php +++ b/app/Filament/Resources/StyleResource.php @@ -52,7 +52,9 @@ class StyleResource extends Resource Textarea::make('parameters') ->label(__('filament.resource.style.form.parameters')) ->nullable() - ->rows(5), + ->rows(15) + ->json() + ->helperText(__('filament.resource.style.form.parameters_help')), Select::make('ai_model_id') ->relationship('aiModel', 'name') ->label(__('filament.resource.style.form.ai_model')) @@ -108,7 +110,9 @@ class StyleResource extends Resource ]) ->emptyStateActions([ Tables\Actions\CreateAction::make(), - ]); + ]) + ->persistFiltersInSession() + ->persistSortInSession(); } public static function getRelations(): array diff --git a/resources/lang/de/filament.php b/resources/lang/de/filament.php index 8af0421..2d02dc1 100644 --- a/resources/lang/de/filament.php +++ b/resources/lang/de/filament.php @@ -70,6 +70,7 @@ return [ 'description' => 'Beschreibung', 'preview_image' => 'Vorschaubild', 'parameters' => 'Parameter', + 'parameters_help' => 'Für ComfyUI, fügen Sie hier das Workflow-JSON ein. Verwenden Sie __PROMPT__, __FILENAME__ und __MODEL_ID__ als Platzhalter.', 'api_provider' => 'API Anbieter', 'ai_model' => 'AI Modell', 'enabled' => 'Aktiviert', diff --git a/resources/lang/en/filament.php b/resources/lang/en/filament.php index 5cda98c..860b5b9 100644 --- a/resources/lang/en/filament.php +++ b/resources/lang/en/filament.php @@ -69,6 +69,7 @@ return [ 'description' => 'Description', 'preview_image' => 'Preview Image', 'parameters' => 'Parameters', + 'parameters_help' => 'For ComfyUI, paste the workflow JSON here. Use __PROMPT__, __FILENAME__, and __MODEL_ID__ as placeholders.', 'api_provider' => 'API Provider', 'ai_model' => 'AI Model', 'enabled' => 'Enabled',