added enabled/disable to styles
This commit is contained in:
28
app/Api/Plugins/LoggablePlugin.php
Normal file
28
app/Api/Plugins/LoggablePlugin.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace App\Api\Plugins;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
trait LoggablePlugin
|
||||
{
|
||||
protected function logInfo(string $message, array $context = []): void
|
||||
{
|
||||
Log::channel('plugins')->info($message, $context);
|
||||
}
|
||||
|
||||
protected function logWarning(string $message, array $context = []): void
|
||||
{
|
||||
Log::channel('plugins')->warning($message, $context);
|
||||
}
|
||||
|
||||
protected function logError(string $message, array $context = []): void
|
||||
{
|
||||
Log::channel('plugins')->error($message, $context);
|
||||
}
|
||||
|
||||
protected function logDebug(string $message, array $context = []): void
|
||||
{
|
||||
Log::channel('plugins')->debug($message, $context);
|
||||
}
|
||||
}
|
||||
@@ -19,11 +19,12 @@ class PluginLoader
|
||||
static::$plugins[$name] = $className;
|
||||
}
|
||||
|
||||
public static function getPlugin(string $name): ApiPluginInterface
|
||||
public static function getPlugin(string $name, $apiProvider = null): ApiPluginInterface
|
||||
{
|
||||
if (!isset(static::$plugins[$name])) {
|
||||
throw new InvalidArgumentException("Plugin {$name} not registered.");
|
||||
}
|
||||
return new static::$plugins[$name]();
|
||||
$className = static::$plugins[$name];
|
||||
return new $className($apiProvider);
|
||||
}
|
||||
}
|
||||
145
app/Api/Plugins/RunwareAi.php
Normal file
145
app/Api/Plugins/RunwareAi.php
Normal file
@@ -0,0 +1,145 @@
|
||||
<?php
|
||||
|
||||
namespace App\Api\Plugins;
|
||||
|
||||
use App\Models\ApiProvider;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class RunwareAi implements ApiPluginInterface
|
||||
{
|
||||
use LoggablePlugin;
|
||||
|
||||
protected $apiProvider;
|
||||
|
||||
public function __construct(ApiProvider $apiProvider)
|
||||
{
|
||||
$this->apiProvider = $apiProvider;
|
||||
$this->logInfo('RunwareAi plugin initialized.', ['provider_name' => $apiProvider->name]);
|
||||
}
|
||||
|
||||
public function getIdentifier(): string
|
||||
{
|
||||
return 'runwareai';
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return 'RunwareAI';
|
||||
}
|
||||
|
||||
public function isEnabled(): bool
|
||||
{
|
||||
return $this->apiProvider->enabled;
|
||||
}
|
||||
|
||||
public function enable(): bool
|
||||
{
|
||||
$this->apiProvider->enabled = true;
|
||||
$result = $this->apiProvider->save();
|
||||
if ($result) {
|
||||
$this->logInfo('RunwareAi plugin enabled.', ['provider_name' => $this->apiProvider->name]);
|
||||
} else {
|
||||
$this->logError('Failed to enable RunwareAi plugin.', ['provider_name' => $this->apiProvider->name]);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function disable(): bool
|
||||
{
|
||||
$this->apiProvider->enabled = false;
|
||||
$result = $this->apiProvider->save();
|
||||
if ($result) {
|
||||
$this->logInfo('RunwareAi plugin disabled.', ['provider_name' => $this->apiProvider->name]);
|
||||
} else {
|
||||
$this->logError('Failed to disable RunwareAi plugin.', ['provider_name' => $this->apiProvider->name]);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function getStatus(string $imageUUID): array
|
||||
{
|
||||
$this->logDebug('Getting status for image.', ['image_uuid' => $imageUUID]);
|
||||
// Implement RunwareAI specific status check
|
||||
return ['status' => 'unknown'];
|
||||
}
|
||||
|
||||
public function getProgress(string $imageUUID): array
|
||||
{
|
||||
$this->logDebug('Getting progress for image.', ['image_uuid' => $imageUUID]);
|
||||
// Implement RunwareAI specific progress check
|
||||
return ['progress' => 0];
|
||||
}
|
||||
|
||||
public function upload(string $imagePath): array
|
||||
{
|
||||
$this->logInfo('Attempting to upload image to RunwareAI.', ['image_path' => $imagePath]);
|
||||
if (!$this->apiProvider->api_url || !$this->apiProvider->token) {
|
||||
$this->logError('RunwareAI API URL or Token not configured for upload.', ['provider_name' => $this->apiProvider->name]);
|
||||
throw new \Exception('RunwareAI API URL or Token not configured.');
|
||||
}
|
||||
|
||||
$apiUrl = rtrim($this->apiProvider->api_url, '/');
|
||||
$token = $this->apiProvider->token;
|
||||
$taskUUID = (string) Str::uuid();
|
||||
|
||||
try {
|
||||
$response = Http::withHeaders([
|
||||
'Authorization' => 'Bearer ' . $token,
|
||||
'Accept' => 'application/json',
|
||||
])->attach(
|
||||
'image', file_get_contents($imagePath), basename($imagePath)
|
||||
)->post($apiUrl, [
|
||||
'taskType' => 'imageUpload',
|
||||
'taskUUID' => $taskUUID,
|
||||
]);
|
||||
|
||||
$response->throw();
|
||||
$this->logInfo('Image uploaded successfully to RunwareAI.', ['task_uuid' => $taskUUID, 'response' => $response->json()]);
|
||||
return $response->json();
|
||||
} catch (\Exception $e) {
|
||||
$this->logError('Image upload to RunwareAI failed.', ['error' => $e->getMessage(), 'image_path' => $imagePath]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
public function styleChangeRequest(string $prompt, string $seedImageUUID, ?string $parameters = null): array
|
||||
{
|
||||
$this->logInfo('Attempting style change request to RunwareAI.', ['prompt' => $prompt, 'seed_image_uuid' => $seedImageUUID]);
|
||||
if (!$this->apiProvider->api_url || !$this->apiProvider->token) {
|
||||
$this->logError('RunwareAI API URL or Token not configured for style change.', ['provider_name' => $this->apiProvider->name]);
|
||||
throw new \Exception('RunwareAI API URL or Token not configured.');
|
||||
}
|
||||
|
||||
$apiUrl = rtrim($this->apiProvider->api_url, '/');
|
||||
$token = $this->apiProvider->token;
|
||||
$taskUUID = (string) Str::uuid();
|
||||
|
||||
$data = [
|
||||
'taskType' => 'imageInference',
|
||||
'taskUUID' => $taskUUID,
|
||||
'positivePrompt' => $prompt,
|
||||
'seedImage' => $seedImageUUID,
|
||||
'outputType' => 'base64Data',
|
||||
];
|
||||
|
||||
$decodedParameters = json_decode($parameters, true) ?? [];
|
||||
foreach ($decodedParameters as $key => $value) {
|
||||
$data[$key] = $value;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = Http::withHeaders([
|
||||
'Authorization' => 'Bearer ' . $token,
|
||||
'Accept' => 'application/json',
|
||||
])->post($apiUrl, $data);
|
||||
|
||||
$response->throw();
|
||||
$this->logInfo('Style change request successful to RunwareAI.', ['task_uuid' => $taskUUID, 'response' => $response->json()]);
|
||||
return $response->json();
|
||||
} catch (\Exception $e) {
|
||||
$this->logError('Style change request to RunwareAI failed.', ['error' => $e->getMessage(), 'task_uuid' => $taskUUID]);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ class InstallPluginPage extends Page implements HasForms
|
||||
|
||||
protected static string $view = 'filament.pages.install-plugin-page';
|
||||
|
||||
protected static ?string $navigationGroup = 'Plugins';
|
||||
protected static ?string $navigationGroup = 'Settings';
|
||||
|
||||
protected static ?string $title = 'Install Plugin';
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ use Filament\Tables\Contracts\HasTable;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Facades\File;
|
||||
use App\Models\ApiProvider;
|
||||
use Filament\Tables\Actions\Action;
|
||||
|
||||
class ListPlugins extends Page implements HasTable
|
||||
@@ -23,7 +24,7 @@ class ListPlugins extends Page implements HasTable
|
||||
|
||||
protected static string $view = 'filament.pages.list-plugins';
|
||||
|
||||
protected static ?string $navigationGroup = 'Plugins';
|
||||
protected static ?string $navigationGroup = 'Settings';
|
||||
|
||||
protected static ?string $title = 'Plugins';
|
||||
|
||||
@@ -40,7 +41,9 @@ class ListPlugins extends Page implements HasTable
|
||||
|
||||
if (class_exists($class) && in_array(ApiPluginInterface::class, class_implements($class))) {
|
||||
try {
|
||||
$instance = new $class();
|
||||
$apiProvider = ApiProvider::where('plugin', $filename)->first();
|
||||
if(!$apiProvider) continue;
|
||||
$instance = new $class($apiProvider);
|
||||
$plugins->add(new Plugin([
|
||||
'id' => $instance->getIdentifier(),
|
||||
'name' => $instance->getName(),
|
||||
@@ -78,8 +81,10 @@ class ListPlugins extends Page implements HasTable
|
||||
->icon(fn ($record) => $record->enabled ? 'heroicon-o-x-circle' : 'heroicon-o-check-circle')
|
||||
->action(function ($record) {
|
||||
try {
|
||||
$apiProvider = ApiProvider::where('plugin', $record->identifier)->first();
|
||||
if(!$apiProvider) throw new \Exception('ApiProvider not found');
|
||||
$pluginClass = 'App\\Api\\Plugins\\' . $record->identifier;
|
||||
$plugin = new $pluginClass();
|
||||
$plugin = new $pluginClass($apiProvider);
|
||||
if ($record->enabled) {
|
||||
$plugin->disable();
|
||||
} else {
|
||||
@@ -97,6 +102,7 @@ class ListPlugins extends Page implements HasTable
|
||||
->send();
|
||||
}
|
||||
}),
|
||||
|
||||
Action::make('delete')
|
||||
->label('Delete')
|
||||
->icon('heroicon-o-trash')
|
||||
|
||||
@@ -41,8 +41,7 @@ class AiModelResource extends Resource
|
||||
Select::make('apiProviders')
|
||||
->relationship('apiProviders', 'name')
|
||||
->multiple()
|
||||
->label(__('filament.resource.ai_model.form.api_providers'))
|
||||
->options(\App\Models\ApiProvider::pluck('name', 'id')),
|
||||
->label(__('filament.resource.ai_model.form.api_providers')),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +85,18 @@ class ApiProviderResource extends Resource
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
Tables\Actions\DeleteBulkAction::make(),
|
||||
Tables\Actions\BulkAction::make('enable')
|
||||
->label(__('filament.resource.api_provider.action.enable_selected'))
|
||||
->icon('heroicon-o-check-circle')
|
||||
->action(function (\Illuminate\Support\Collection $records) {
|
||||
$records->each->update(['enabled' => true]);
|
||||
}),
|
||||
Tables\Actions\BulkAction::make('disable')
|
||||
->label(__('filament.resource.api_provider.action.disable_selected'))
|
||||
->icon('heroicon-o-x-circle')
|
||||
->action(function (\Illuminate\Support\Collection $records) {
|
||||
$records->each->update(['enabled' => false]);
|
||||
}),
|
||||
]),
|
||||
])
|
||||
->emptyStateActions([
|
||||
@@ -122,8 +134,9 @@ class ApiProviderResource extends Resource
|
||||
|
||||
$class = 'App\\Api\\Plugins\\' . $filename;
|
||||
if (class_exists($class) && in_array(ApiPluginInterface::class, class_implements($class))) {
|
||||
$instance = new $class();
|
||||
$plugins[$instance->getIdentifier()] = $instance->getName();
|
||||
// Do not instantiate here, just get identifier and name if possible statically or by convention
|
||||
// For now, we'll use filename as identifier and name
|
||||
$plugins[$filename] = $filename;
|
||||
}
|
||||
}
|
||||
return $plugins;
|
||||
|
||||
@@ -1,26 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Filament\Resources;
|
||||
|
||||
use App\Filament\Resources\StyleResource\Pages;
|
||||
use App\Filament\Resources\StyleResource\RelationManagers;
|
||||
use App\Models\Style;
|
||||
use Filament\Forms;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\SoftDeletingScope;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Components\Textarea;
|
||||
use Filament\Forms\Components\FileUpload;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Columns\ImageColumn;
|
||||
|
||||
use Filament\Forms\Components\Toggle;
|
||||
use Filament\Tables\Columns\IconColumn;
|
||||
use Filament\Tables\Filters\SelectFilter;
|
||||
|
||||
class StyleResource extends Resource
|
||||
{
|
||||
@@ -76,7 +72,7 @@ class StyleResource extends Resource
|
||||
ImageColumn::make('preview_image')->label(__('filament.resource.style.table.preview_image'))->disk('public'),
|
||||
])
|
||||
->filters([
|
||||
Tables\Filters\SelectFilter::make('ai_model')
|
||||
SelectFilter::make('ai_model')
|
||||
->relationship('aiModel', 'name')
|
||||
->label(__('filament.resource.style.table.ai_model')),
|
||||
])
|
||||
@@ -85,7 +81,13 @@ class StyleResource extends Resource
|
||||
Tables\Actions\Action::make('duplicate')
|
||||
->label(__('filament.resource.style.action.duplicate'))
|
||||
->icon('heroicon-o-document-duplicate')
|
||||
->url(fn (\App\Models\Style $record): string => StyleResource::getUrl('create', ['duplicate_id' => $record->id])),
|
||||
->action(function (\App\Models\Style $record) {
|
||||
$newStyle = $record->replicate();
|
||||
$newStyle->title = $record->title . ' (Kopie)';
|
||||
$newStyle->save();
|
||||
|
||||
return redirect()->to(StyleResource::getUrl('edit', ['record' => $newStyle->id]));
|
||||
}),
|
||||
])
|
||||
->bulkActions([
|
||||
Tables\Actions\BulkActionGroup::make([
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Filament\Resources\StyleResource\Pages;
|
||||
|
||||
use App\Filament\Resources\StyleResource;
|
||||
use App\Models\Style;
|
||||
use Filament\Actions;
|
||||
use Filament\Resources\Pages\CreateRecord;
|
||||
|
||||
@@ -15,20 +14,4 @@ class CreateStyle extends CreateRecord
|
||||
{
|
||||
return $this->getResource()::getUrl('index');
|
||||
}
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
parent::mount();
|
||||
|
||||
$duplicateId = request()->query('duplicate_id');
|
||||
|
||||
if ($duplicateId) {
|
||||
$originalStyle = Style::find($duplicateId);
|
||||
if ($originalStyle) {
|
||||
$data = $originalStyle->toArray();
|
||||
$data['title'] = $originalStyle->title . ' (Kopie)';
|
||||
$this->form->fill($data);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -52,23 +52,127 @@ class ImageController extends Controller
|
||||
]);
|
||||
|
||||
$image = Image::find($request->image_id);
|
||||
$style = Style::with('aiModel.apiProviders')->find($request->style_id);
|
||||
$style = Style::with(['aiModel' => function ($query) {
|
||||
$query->where('enabled', true)->with(['apiProviders' => function ($query) {
|
||||
$query->where('enabled', true);
|
||||
}]);
|
||||
}])->find($request->style_id);
|
||||
|
||||
if (!$style || !$style->aiModel || $style->aiModel->apiProviders->isEmpty()) {
|
||||
return response()->json(['error' => __('api.style_or_provider_not_found')], 404);
|
||||
}
|
||||
|
||||
try {
|
||||
$apiProvider = $style->aiModel->apiProviders->first(); // Get the first API provider
|
||||
$apiProvider = $style->aiModel->apiProviders->first(); // Get the first enabled API provider
|
||||
if (!$apiProvider) {
|
||||
return response()->json(['error' => __('api.style_or_provider_not_found')], 404);
|
||||
}
|
||||
$plugin = PluginLoader::getPlugin($apiProvider->name);
|
||||
$result = $plugin->styleChangeRequest($style->prompt, $image->uuid, $style->parameters); // Annahme: Image Model hat eine UUID
|
||||
$plugin = PluginLoader::getPlugin($apiProvider->plugin, $apiProvider);
|
||||
|
||||
// Hier müsste die Logik zum Speichern des neuen Bildes und dessen Verknüpfung implementiert werden
|
||||
// Fürs Erste geben wir das Ergebnis der API zurück
|
||||
return response()->json($result);
|
||||
// Step 1: Upload the original image
|
||||
$originalImagePath = Storage::disk('public')->path($image->path);
|
||||
$uploadResult = $plugin->upload($originalImagePath);
|
||||
|
||||
if (!isset($uploadResult['imageUUID'])) {
|
||||
throw new \Exception('Image upload to AI service failed or returned no UUID.');
|
||||
}
|
||||
$seedImageUUID = $uploadResult['imageUUID'];
|
||||
|
||||
// Step 2: Request style change using the uploaded image's UUID
|
||||
$result = $plugin->styleChangeRequest($style->prompt, $seedImageUUID, $style->parameters);
|
||||
|
||||
if (!isset($result['base64Data'])) {
|
||||
throw new \Exception('AI service did not return base64 image data.');
|
||||
}
|
||||
|
||||
$base64Image = $result['base64Data'];
|
||||
$decodedImage = base64_decode(preg_replace('#^data:image/\w+;base64, #i', '', $base64Image));
|
||||
|
||||
$newImageName = 'styled_' . uniqid() . '.png'; // Assuming PNG for now
|
||||
$newImagePath = 'uploads/' . $newImageName;
|
||||
|
||||
Storage::disk('public')->put($newImagePath, $decodedImage);
|
||||
|
||||
$newImage = Image::create([
|
||||
'path' => $newImagePath,
|
||||
'original_image_id' => $image->id, // Link to original image
|
||||
'style_id' => $style->id, // Link to applied style
|
||||
'is_temp' => true, // Mark as temporary until user keeps it
|
||||
]);
|
||||
|
||||
return response()->json([
|
||||
'message' => 'Style change successful',
|
||||
'styled_image' => [
|
||||
'id' => $newImage->id,
|
||||
'path' => Storage::url($newImage->path),
|
||||
'is_temp' => $newImage->is_temp,
|
||||
],
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function keepImage(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'image_id' => 'required|exists:images,id',
|
||||
]);
|
||||
|
||||
$image = Image::find($request->image_id);
|
||||
|
||||
if (!$image) {
|
||||
return response()->json(['error' => __('api.image_not_found')], 404);
|
||||
}
|
||||
|
||||
$image->is_temp = false;
|
||||
$image->save();
|
||||
|
||||
return response()->json(['message' => __('api.image_kept_successfully')]);
|
||||
}
|
||||
|
||||
public function deleteImage(Image $image)
|
||||
{
|
||||
// Ensure the image is temporary or belongs to the authenticated user if not temporary
|
||||
// For simplicity, we'll allow deletion of any image passed for now.
|
||||
// In a real app, you'd add authorization checks here.
|
||||
|
||||
try {
|
||||
Storage::disk('public')->delete($image->path);
|
||||
$image->delete();
|
||||
return response()->json(['message' => __('api.image_deleted_successfully')]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function keepImage(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'image_id' => 'required|exists:images,id',
|
||||
]);
|
||||
|
||||
$image = Image::find($request->image_id);
|
||||
|
||||
if (!$image) {
|
||||
return response()->json(['error' => __('api.image_not_found')], 404);
|
||||
}
|
||||
|
||||
try {
|
||||
$image->is_temp = false;
|
||||
$image->save();
|
||||
return response()->json(['message' => __('api.image_kept_successfully')]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteImage(Image $image)
|
||||
{
|
||||
try {
|
||||
Storage::disk('public')->delete($image->path);
|
||||
$image->delete();
|
||||
return response()->json(['message' => __('api.image_deleted_successfully')]);
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => $e->getMessage()], 500);
|
||||
}
|
||||
@@ -118,5 +222,4 @@ class ImageController extends Controller
|
||||
} catch (\Exception $e) {
|
||||
return response()->json(['error' => $e->getMessage()], 500);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,7 @@ class ApiPluginServiceProvider extends ServiceProvider
|
||||
|
||||
$class = 'App\\Api\\Plugins\\' . $filename;
|
||||
if (class_exists($class) && in_array(ApiPluginInterface::class, class_implements($class))) {
|
||||
$instance = new $class();
|
||||
PluginLoader::registerPlugin($instance->getIdentifier(), $class);
|
||||
PluginLoader::registerPlugin($filename, $class);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ use Illuminate\Routing\Middleware\SubstituteBindings;
|
||||
use Illuminate\Session\Middleware\AuthenticateSession;
|
||||
use Illuminate\Session\Middleware\StartSession;
|
||||
use Illuminate\View\Middleware\ShareErrorsFromSession;
|
||||
use BezhanSalleh\FilamentLanguageSwitch\LanguageSwitchPlugin;
|
||||
use App\Filament\Resources\StyleResource;
|
||||
|
||||
class AdminPanelProvider extends PanelProvider
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user