-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 74b9042
Showing
12 changed files
with
605 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
/art export-ignore | ||
/docs export-ignore | ||
/tests export-ignore | ||
/scripts export-ignore | ||
/.github export-ignore | ||
/.php_cs export-ignore | ||
.editorconfig export-ignore | ||
.gitattributes export-ignore | ||
.gitignore export-ignore | ||
phpstan.neon.dist export-ignore | ||
phpunit.xml.dist export-ignore | ||
CHANGELOG.md export-ignore | ||
CONTRIBUTING.md export-ignore | ||
README.md export-ignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/.DS_Store | ||
/.phpunit.result.cache | ||
/.phpunit.cache | ||
/.php-cs-fixer.cache | ||
/.php-cs-fixer.php | ||
/composer.lock | ||
/phpunit.xml | ||
/.idea/ | ||
*.swp | ||
*.swo | ||
/tests/ | ||
/vendor/ | ||
.env | ||
test.php | ||
documentations.txt |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,162 @@ | ||
# FAL AI PHP Client | ||
|
||
A lightweight PHP client for [FAL.AI](https://fal.ai) built with Saloon v3. Create AI-powered content with ease. | ||
|
||
[![Join FAL.AI Discord](data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTcwIiBoZWlnaHQ9IjE3MSIgdmlld0JveD0iMCAwIDE3MCAxNzEiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbC1ydWxlPSJldmVub2RkIiBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Ik0xMDkuNTcxIDAuNjkwMDQyQzExMi41MTUgMC42OTAwNDIgMTE0Ljg3NCAzLjA4MzQ4IDExNS4xNTUgNi4wMTM1MkMxMTcuNjY1IDMyLjE0OSAxMzguNDY2IDUyLjk0OCAxNjQuNjAzIDU1LjQ1OEMxNjcuNTM0IDU1LjczOTQgMTY5LjkyNyA1OC4wOTg1IDE2OS45MjcgNjEuMDQyVjExMC4yNTVDMTY5LjkyNyAxMTMuMTk4IDE2Ny41MzQgMTE1LjU1NyAxNjQuNjAzIDExNS44MzlDMTM4LjQ2NiAxMTguMzQ5IDExNy42NjUgMTM5LjE0OCAxMTUuMTU1IDE2NS4yODNDMTE0Ljg3NCAxNjguMjEzIDExMi41MTUgMTcwLjYwNyAxMDkuNTcxIDE3MC42MDdINTAuMzU1M0M1Ny40MTE2IDE3MC42MDcgNTUuMDUyNCAxNjguMjEzIDU0Ljc3MDkgMTY1LjI4M0M1Mi4yNjA4IDEzOS4xNDggMzEuNDYwMSAxMTguMzQ5IDUuMzIyODkgMTE1LjgzOUMyLjM5MjY2IDExNS41NTctMC4wMDA5NzY1NjIgMTEzLjE5OCAtMC4wMDA5NzY1NjIgMTEwLjI1NVY2MS4wNDJDLTAuMDAwOTc2NTYyIDU4LjA5ODUgMi4zOTI2NyA1NS43Mzk0IDUuMzIyOSA1NS40NThDMzEuNDYwMSA1Mi45NDggNTIuMjYwOCAzMi4xNDkgNTQuNzcwOSA2LjAxMzUxQzU1LjA1MjQgMy4wODM0OCA1Ny40MTE2IDAuNjkwMDQyIDYwLjM1NTMgMC42OTAwNDJIMTA5LjU3MVpNMzQuMTE4MiA4NS41MDQ1QzM0LjExODIgMTEzLjc3NiA1Ny4wMTI0IDEzNi42OTQgODUuMjUzOSAxMzYuNjk0QzExMy40OTUgMTM2LjY5NCAxMzYuMzkgMTEzLjc3NiAxMzYuMzkgODUuNTA0NUMxMzYuMzkgNTcuMjMzMiAxMTMuNDk1IDM0LjMxNDcgODUuMjUzOSAzNC4zMTQ3QzU3LjAxMjQgMzQuMzE0NyAzNC4xMTgyIDU3LjIzMzIgMzQuMTE4MiA4NS41MDQ1WiIgZmlsbD0iYmxhY2siLz48L3N2Zz4=)](https://discord.gg/fal-ai) | ||
|
||
## Features | ||
|
||
* 🎨 Support for all FAL AI models (Recraft, Flux Pro, etc.) | ||
* 🔄 Full queue system with status tracking | ||
* 📡 Webhook support | ||
* 🛠️ ComfyUI & Workflows support | ||
* ⚡ Simple, intuitive API | ||
|
||
## Installation | ||
|
||
```bash | ||
composer require marceloeatworld/falai-php | ||
``` | ||
|
||
## Quick Start | ||
|
||
```php | ||
use MarceloEatWorld\FalAI\FalAI; | ||
|
||
$falAI = new FalAI('your-api-key'); | ||
|
||
// Generate an image | ||
$result = $falAI->generations()->create('fal-ai/recraft-v3', [ | ||
'prompt' => 'A beautiful landscape', | ||
'negative_prompt' => 'low quality', | ||
'image_size' => 'square_hd' | ||
'seed' => '42' | ||
]); | ||
|
||
// Check generation status using requestId | ||
$status = $falAI->generations()->checkStatus($result->requestId); | ||
|
||
// Get final result when completed | ||
$finalResult = $falAI->generations()->getResult($result->requestId); | ||
``` | ||
|
||
## Models & Workflows | ||
|
||
```php | ||
// FAL AI Models | ||
$result = $falAI->generations()->create('fal-ai/flux-pro/v1.1-ultra', [ | ||
'prompt' => 'A futuristic city', | ||
'negative_prompt' => 'low quality', | ||
'image_size' => 'square_hd' | ||
'seed' => '42' | ||
]); | ||
|
||
// ComfyUI Workflows | ||
$result = $falAI->generations()->create('comfy/youraccount/workflow', [ | ||
'loadimage_1' => 'https://example.com/image.jpg', | ||
'prompt' => 'Make it anime style' | ||
'seed' => '42' | ||
]); | ||
|
||
// Track any generation with requestId | ||
$status = $falAI->generations()->checkStatus($result->requestId); | ||
``` | ||
|
||
## Advanced Usage | ||
|
||
```php | ||
// Use webhooks | ||
$result = $falAI->generations() | ||
->withWebhook('https://your-site.com/webhook') | ||
->create('fal-ai/recraft-v3', [ | ||
'prompt' => 'A serene lake' | ||
'seed' => '42' | ||
]); | ||
|
||
// Cancel a generation using requestId | ||
$cancelled = $falAI->generations()->cancel($result->requestId); | ||
``` | ||
|
||
## Response Structure | ||
|
||
The `GenerationData` object contains: | ||
- `requestId`: Unique identifier for tracking the generation | ||
- `responseUrl`: URL to fetch the result | ||
- `statusUrl`: URL to check status | ||
- `cancelUrl`: URL to cancel generation | ||
- `status`: Current status (IN_QUEUE, IN_PROGRESS, COMPLETED, ERROR) | ||
- `payload`: Generation result data when completed | ||
- `error`: Error message if any | ||
|
||
## Tracking Generations | ||
|
||
```php | ||
// Store the requestId after creation | ||
$requestId = $result->requestId; | ||
|
||
// Later, check status | ||
$status = $falAI->generations()->checkStatus($requestId); | ||
|
||
if ($status->status === 'COMPLETED') { | ||
// Get the final result | ||
$finalResult = $falAI->generations()->getResult($requestId); | ||
// Access the generated images | ||
$images = $finalResult->payload['images'] ?? []; | ||
} | ||
``` | ||
|
||
## Laravel Integration | ||
|
||
Add to `config/services.php`: | ||
|
||
```php | ||
'falai' => [ | ||
'api_key' => env('FAL_API_KEY'), | ||
], | ||
``` | ||
|
||
Register in a service provider: | ||
|
||
```php | ||
public function register() | ||
{ | ||
$this->app->singleton(FalAI::class, function () { | ||
return new FalAI(config('services.falai.api_key')); | ||
}); | ||
} | ||
``` | ||
|
||
Use in controllers: | ||
|
||
```php | ||
use MarceloEatWorld\FalAI\FalAI; | ||
|
||
public function generate(FalAI $falAI) | ||
{ | ||
$result = $falAI->generations()->create('fal-ai/recraft-v3', [ | ||
'prompt' => 'A mountain landscape' | ||
'seed' => '42' | ||
]); | ||
|
||
// Store requestId for later use | ||
$requestId = $result->requestId; | ||
} | ||
|
||
public function checkStatus(FalAI $falAI, string $requestId) | ||
{ | ||
return $falAI->generations()->checkStatus($requestId); | ||
} | ||
``` | ||
|
||
## Support & Security | ||
|
||
For security issues, please email [email protected]. | ||
|
||
## License | ||
|
||
MIT License - see LICENSE. | ||
|
||
## Credits | ||
|
||
- Built with [Saloon v3](https://github.com/saloonphp/saloon) | ||
- Inspired by [replicate-php](https://github.com/replicate-php) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
{ | ||
"name": "marceloeatworld/falai-php", | ||
"description": "A PHP client for the Fal AI API", | ||
"keywords": ["fal-ai", "php", "package"], | ||
"license": "MIT", | ||
"authors": [ | ||
{ | ||
"name": "Marcelo Pereira", | ||
"email": "[email protected]" | ||
} | ||
], | ||
"require": { | ||
"php": "^8.1.0", | ||
"saloonphp/saloon": "^3.0" | ||
}, | ||
"autoload": { | ||
"psr-4": { | ||
"MarceloEatWorld\\FalAI\\": "src/" | ||
} | ||
}, | ||
"autoload-dev": { | ||
"psr-4": { | ||
"Tests\\": "tests/" | ||
} | ||
}, | ||
"require-dev": { | ||
"phpunit/phpunit": "^11.0" | ||
}, | ||
"minimum-stability": "dev", | ||
"prefer-stable": true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
<?php | ||
|
||
namespace MarceloEatWorld\FalAI\Data; | ||
|
||
use Exception; | ||
use Saloon\Http\Response; | ||
|
||
final class GenerationData | ||
{ | ||
public function __construct( | ||
public ?string $requestId, | ||
public ?string $responseUrl, | ||
public ?string $statusUrl, | ||
public ?string $cancelUrl, | ||
public ?string $status, | ||
public ?array $payload, | ||
public ?string $error, | ||
) { | ||
} | ||
|
||
public static function fromResponse(Response $response): self | ||
{ | ||
try { | ||
// On essaie d'abord de décoder la réponse brute | ||
$rawBody = $response->body(); | ||
if (empty($rawBody)) { | ||
throw new Exception("Empty response body"); | ||
} | ||
|
||
// On tente de parser le JSON avec un contrôle d'erreur | ||
$data = json_decode($rawBody, true); | ||
if (json_last_error() !== JSON_ERROR_NONE) { | ||
throw new Exception("JSON decode error: " . json_last_error_msg()); | ||
} | ||
|
||
// Si la réponse contient une erreur directe | ||
if (isset($data['error'])) { | ||
return new self( | ||
requestId: $data['request_id'] ?? null, | ||
responseUrl: $data['response_url'] ?? null, | ||
statusUrl: $data['status_url'] ?? null, | ||
cancelUrl: $data['cancel_url'] ?? null, | ||
status: 'ERROR', | ||
payload: null, | ||
error: $data['error'] | ||
); | ||
} | ||
|
||
// Pour les réponses de statut | ||
if (isset($data['status'])) { | ||
return new self( | ||
requestId: $data['request_id'] ?? null, | ||
responseUrl: $data['response_url'] ?? null, | ||
statusUrl: $data['status_url'] ?? null, | ||
cancelUrl: $data['cancel_url'] ?? null, | ||
status: $data['status'], | ||
payload: $data['response'] ?? null, | ||
error: null | ||
); | ||
} | ||
|
||
// Si on a un request_id, c'est probablement une réponse de création | ||
if (isset($data['request_id'])) { | ||
return new self( | ||
requestId: $data['request_id'], | ||
responseUrl: $data['response_url'] ?? null, | ||
statusUrl: $data['status_url'] ?? null, | ||
cancelUrl: $data['cancel_url'] ?? null, | ||
status: isset($data['status']) ? $data['status'] : null, | ||
payload: null, | ||
error: null | ||
); | ||
} | ||
|
||
// Si on arrive ici, c'est probablement une réponse de résultat direct | ||
return new self( | ||
requestId: null, | ||
responseUrl: null, | ||
statusUrl: null, | ||
cancelUrl: null, | ||
status: 'COMPLETED', | ||
payload: $data, | ||
error: null | ||
); | ||
|
||
} catch (Exception $e) { | ||
// Log pour le débogage si nécessaire | ||
error_log("Error parsing response: " . $e->getMessage()); | ||
error_log("Raw response body: " . $response->body()); | ||
|
||
// On retourne un objet avec l'erreur | ||
return new self( | ||
requestId: null, | ||
responseUrl: null, | ||
statusUrl: null, | ||
cancelUrl: null, | ||
status: 'ERROR', | ||
payload: null, | ||
error: $e->getMessage() | ||
); | ||
} | ||
} | ||
|
||
public function toArray(): array | ||
{ | ||
return [ | ||
'requestId' => $this->requestId, | ||
'responseUrl' => $this->responseUrl, | ||
'statusUrl' => $this->statusUrl, | ||
'cancelUrl' => $this->cancelUrl, | ||
'status' => $this->status, | ||
'payload' => $this->payload, | ||
'error' => $this->error, | ||
]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
<?php | ||
// src/FalAI.php | ||
|
||
namespace MarceloEatWorld\FalAI; | ||
|
||
use Saloon\Http\Connector; | ||
|
||
class FalAI extends Connector | ||
{ | ||
public function __construct(public readonly string $apiKey) {} | ||
|
||
public function resolveBaseUrl(): string | ||
{ | ||
return 'https://queue.fal.run/'; | ||
} | ||
|
||
protected function defaultHeaders(): array | ||
{ | ||
return [ | ||
'Content-Type' => 'application/json', | ||
'Accept' => 'application/json', | ||
'Authorization' => 'Key ' . $this->apiKey, | ||
]; | ||
} | ||
|
||
|
||
public function generations(): GenerationsResource | ||
{ | ||
return new GenerationsResource($this); | ||
} | ||
} |
Oops, something went wrong.