added comfyui
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -17,3 +17,5 @@ yarn-error.log
|
|||||||
/.fleet
|
/.fleet
|
||||||
/.idea
|
/.idea
|
||||||
/.vscode
|
/.vscode
|
||||||
|
GEMINI.md
|
||||||
|
GEMINI.md.prompt
|
||||||
|
|||||||
162
app/Api/Plugins/ComfyUi.php
Normal file
162
app/Api/Plugins/ComfyUi.php
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Api\Plugins;
|
||||||
|
|
||||||
|
use App\Models\ApiProvider;
|
||||||
|
use Illuminate\Support\Facades\Http;
|
||||||
|
|
||||||
|
class ComfyUi implements ApiPluginInterface
|
||||||
|
{
|
||||||
|
use LoggablePlugin;
|
||||||
|
|
||||||
|
protected $apiProvider;
|
||||||
|
|
||||||
|
public function __construct(ApiProvider $apiProvider)
|
||||||
|
{
|
||||||
|
$this->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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ use Illuminate\Database\Eloquent\SoftDeletingScope;
|
|||||||
use Filament\Forms\Components\TextInput;
|
use Filament\Forms\Components\TextInput;
|
||||||
use Filament\Tables\Columns\TextColumn;
|
use Filament\Tables\Columns\TextColumn;
|
||||||
use Filament\Forms\Components\Select;
|
use Filament\Forms\Components\Select;
|
||||||
|
use Filament\Tables\Actions\Action;
|
||||||
|
|
||||||
class AiModelResource extends Resource
|
class AiModelResource extends Resource
|
||||||
{
|
{
|
||||||
@@ -66,6 +67,12 @@ class AiModelResource extends Resource
|
|||||||
])
|
])
|
||||||
->actions([
|
->actions([
|
||||||
Tables\Actions\EditAction::make(),
|
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([
|
->bulkActions([
|
||||||
Tables\Actions\BulkActionGroup::make([
|
Tables\Actions\BulkActionGroup::make([
|
||||||
|
|||||||
@@ -5,11 +5,24 @@ namespace App\Filament\Resources\AiModelResource\Pages;
|
|||||||
use App\Filament\Resources\AiModelResource;
|
use App\Filament\Resources\AiModelResource;
|
||||||
use Filament\Actions;
|
use Filament\Actions;
|
||||||
use Filament\Resources\Pages\CreateRecord;
|
use Filament\Resources\Pages\CreateRecord;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class CreateAiModel extends CreateRecord
|
class CreateAiModel extends CreateRecord
|
||||||
{
|
{
|
||||||
protected static string $resource = AiModelResource::class;
|
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
|
protected function getRedirectUrl(): string
|
||||||
{
|
{
|
||||||
return $this->getResource()::getUrl('index');
|
return $this->getResource()::getUrl('index');
|
||||||
|
|||||||
@@ -52,7 +52,9 @@ class StyleResource extends Resource
|
|||||||
Textarea::make('parameters')
|
Textarea::make('parameters')
|
||||||
->label(__('filament.resource.style.form.parameters'))
|
->label(__('filament.resource.style.form.parameters'))
|
||||||
->nullable()
|
->nullable()
|
||||||
->rows(5),
|
->rows(15)
|
||||||
|
->json()
|
||||||
|
->helperText(__('filament.resource.style.form.parameters_help')),
|
||||||
Select::make('ai_model_id')
|
Select::make('ai_model_id')
|
||||||
->relationship('aiModel', 'name')
|
->relationship('aiModel', 'name')
|
||||||
->label(__('filament.resource.style.form.ai_model'))
|
->label(__('filament.resource.style.form.ai_model'))
|
||||||
@@ -108,7 +110,9 @@ class StyleResource extends Resource
|
|||||||
])
|
])
|
||||||
->emptyStateActions([
|
->emptyStateActions([
|
||||||
Tables\Actions\CreateAction::make(),
|
Tables\Actions\CreateAction::make(),
|
||||||
]);
|
])
|
||||||
|
->persistFiltersInSession()
|
||||||
|
->persistSortInSession();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function getRelations(): array
|
public static function getRelations(): array
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ return [
|
|||||||
'description' => 'Beschreibung',
|
'description' => 'Beschreibung',
|
||||||
'preview_image' => 'Vorschaubild',
|
'preview_image' => 'Vorschaubild',
|
||||||
'parameters' => 'Parameter',
|
'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',
|
'api_provider' => 'API Anbieter',
|
||||||
'ai_model' => 'AI Modell',
|
'ai_model' => 'AI Modell',
|
||||||
'enabled' => 'Aktiviert',
|
'enabled' => 'Aktiviert',
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ return [
|
|||||||
'description' => 'Description',
|
'description' => 'Description',
|
||||||
'preview_image' => 'Preview Image',
|
'preview_image' => 'Preview Image',
|
||||||
'parameters' => 'Parameters',
|
'parameters' => 'Parameters',
|
||||||
|
'parameters_help' => 'For ComfyUI, paste the workflow JSON here. Use __PROMPT__, __FILENAME__, and __MODEL_ID__ as placeholders.',
|
||||||
'api_provider' => 'API Provider',
|
'api_provider' => 'API Provider',
|
||||||
'ai_model' => 'AI Model',
|
'ai_model' => 'AI Model',
|
||||||
'enabled' => 'Enabled',
|
'enabled' => 'Enabled',
|
||||||
|
|||||||
Reference in New Issue
Block a user