feat(ai): add image storage helpers#1465
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #1465 +/- ##
==========================================
- Coverage 69.27% 69.18% -0.09%
==========================================
Files 368 370 +2
Lines 29276 29338 +62
==========================================
+ Hits 20281 20298 +17
- Misses 8069 8108 +39
- Partials 926 932 +6 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR extends the AI image generation API with first-class storage helpers, making it possible to persist generated image outputs directly to the filesystem (default disk or a named disk) without manual byte handling. It also introduces a focused ai/image package entrypoint for image generation (image.Of(...)) to complement the existing image attachment helpers.
Changes:
- Added
Store([disk])andStoreAs(path[, disk])tocontracts/ai.ImageRequestandcontracts/ai.ImageResponse, with OpenAI implementation support. - Introduced
ai.StoreImage*helper functions to centralize image persistence to the filesystem facade, plus new framework errors for validation/arity. - Added
ai/image.Of(...)convenience entrypoint and a unit test for the new package-level helper.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| mocks/ai/ImageResponse.go | Updates generated mock to support new Store / StoreAs methods on ImageResponse. |
| mocks/ai/ImageRequest.go | Updates generated mock to support new Store / StoreAs methods on ImageRequest. |
| errors/list.go | Adds new AI image storage-related error constants. |
| contracts/ai/response.go | Extends ImageResponse contract with Store / StoreAs. |
| contracts/ai/image.go | Extends ImageRequest contract with Store / StoreAs. |
| ai/openai/response.go | Implements Store / StoreAs for OpenAI image responses, including generated filename logic. |
| ai/image/image.go | Adds image.Of(...) helper that forwards to facades.AI().Image(...). |
| ai/image/image_test.go | New test covering image.Of(...) facade plumbing. |
| ai/image_storage.go | New shared storage helper implementations for writing generated image bytes to the filesystem facade. |
| ai/image_request.go | Adds convenience Store / StoreAs methods that call Generate() and delegate to the response. |
| ai/application_test.go | Adds coverage for ImageRequest.Store() behavior and updates the test stub to implement new response methods. |
| SizeLandscape = contractsai.ImageSizeLandscape | ||
| ) | ||
|
|
||
| func Of(prompt string, options ...contractsai.Option) contractsai.ImageRequest { |
There was a problem hiding this comment.
Remove this function, users will use facades.AI().Image directly.
There was a problem hiding this comment.
I kept image.Of intentionally. The ai/image package already provides convenience wrappers like FromByte, FromURL, and FromStorage, so Of keeps that entrypoint consistent for users who prefer the package-style API over reaching into facades directly.
| Attachments(attachments ...Attachment) ImageRequest | ||
| Timeout(timeout time.Duration) ImageRequest | ||
| Store(disk ...string) (string, error) | ||
| StoreAs(path string, disk ...string) (string, error) |
There was a problem hiding this comment.
| StoreAs(path string, disk ...string) (string, error) | |
| StoreAs(name string, disk ...string) (string, error) |
There was a problem hiding this comment.
I kept StoreAs(path, disk...) because this method accepts the full destination path, not just a filename. The implementation validates path-like inputs such as images/avatar.png, normalizes separators for storage keys, and rejects directory-only values, so path is the more accurate contract name here.
| Content() ([]byte, error) | ||
| MimeType() string | ||
| Store(disk ...string) (string, error) | ||
| StoreAs(path string, disk ...string) (string, error) |
There was a problem hiding this comment.
| StoreAs(path string, disk ...string) (string, error) | |
| StoreAs(name string, disk ...string) (string, error) |
There was a problem hiding this comment.
I kept StoreAs(path, disk...) here as well for the same reason: callers can pass a nested storage target like images/avatar.png, so path better reflects the accepted input than name.
| package ai | ||
|
|
||
| // Response exposes generated text and provider metadata. | ||
| type Response interface { |
There was a problem hiding this comment.
| type TextResponse interface { |
There was a problem hiding this comment.
I did not rename the public Response interface to TextResponse. This package still exposes Response as the primary text-response contract and ImageResponse as the separate image contract, so keeping the existing exported name avoids a broader public API rename while the internal concrete type is now textResponse.
* origin/master: feat(ai): add audio generation support (goravel#1467) chore: Update non-major dependencies (goravel#1468) refactor(ai): rename agent response interfaces (goravel#1466) feat(ai): add image storage helpers (goravel#1465) chore: Update non-major dependencies (goravel#1464)
Summary
image.Of(...)helper that mirrors the AI image entrypoint with a more focused image package API.Store([disk])andStoreAs(path[, disk])without manually reading response bytes.Closes goravel/goravel#918
Why
AI image generation already returned response content, but users still had to handle the raw bytes themselves when saving images to storage. This change adds first-class storage helpers so common image-generation flows look like the rest of the framework and map closely to Laravel's AI image API.
The new API also keeps storage intent explicit:
Store()auto-generates a filename, whileStoreAs()accepts the full destination path and an optional disk. That gives users a straightforward way to save generated images either on the default filesystem disk or on a named disk without extra plumbing.