Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
marceloeatworld committed Nov 20, 2024
0 parents commit 74b9042
Show file tree
Hide file tree
Showing 12 changed files with 605 additions and 0 deletions.
14 changes: 14 additions & 0 deletions .gitattributes
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
15 changes: 15 additions & 0 deletions .gitignore
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
162 changes: 162 additions & 0 deletions README.md
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)
31 changes: 31 additions & 0 deletions composer.json
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
}
116 changes: 116 additions & 0 deletions src/Data/GenerationData.php
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,
];
}
}
31 changes: 31 additions & 0 deletions src/FalAI.php
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);
}
}
Loading

0 comments on commit 74b9042

Please sign in to comment.