Skip to content

feat(ai): add image storage helpers#1465

Merged
hwbrzzl merged 3 commits into
masterfrom
bowen/#918-2
May 7, 2026
Merged

feat(ai): add image storage helpers#1465
hwbrzzl merged 3 commits into
masterfrom
bowen/#918-2

Conversation

@hwbrzzl
Copy link
Copy Markdown
Contributor

@hwbrzzl hwbrzzl commented May 4, 2026

Summary

  • Adds an image.Of(...) helper that mirrors the AI image entrypoint with a more focused image package API.
  • Lets generated AI images be stored directly through Store([disk]) and StoreAs(path[, disk]) without manually reading response bytes.
  • Supports both auto-generated filenames and explicit target paths while allowing storage on the default disk or a selected disk.

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, while StoreAs() 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.

package controllers

import (
	"github.com/goravel/framework/ai/image"
	"github.com/goravel/framework/contracts/http"
)

type ImageController struct{}

func (r *ImageController) Store(ctx http.Context) http.Response {
	path, err := image.Of("A donut sitting on the kitchen counter").Store("s3")
	if err != nil {
		return ctx.Response().Json(500, http.Json{"error": err.Error()})
	}

	explicitPath, err := image.Of("A donut sitting on the kitchen counter").StoreAs("sub-folder/image.jpg", "s3")
	if err != nil {
		return ctx.Response().Json(500, http.Json{"error": err.Error()})
	}

	return ctx.Response().Json(200, http.Json{
		"path":          path,
		"explicit_path": explicitPath,
	})
}

Copilot AI review requested due to automatic review settings May 4, 2026 10:22
@hwbrzzl hwbrzzl requested a review from a team as a code owner May 4, 2026 10:22
@codecov
Copy link
Copy Markdown

codecov Bot commented May 4, 2026

Codecov Report

❌ Patch coverage is 52.54237% with 56 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.18%. Comparing base (626a4a1) to head (2b885d8).

Files with missing lines Patch % Lines
ai/response.go 31.03% 39 Missing and 1 partial ⚠️
ai/image_storage.go 74.19% 4 Missing and 4 partials ⚠️
ai/image_request.go 30.00% 6 Missing and 1 partial ⚠️
ai/file.go 66.66% 1 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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]) and StoreAs(path[, disk]) to contracts/ai.ImageRequest and contracts/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.

Comment thread ai/image_storage.go Outdated
Comment thread ai/image_storage.go Outdated
Comment thread ai/image_storage.go Outdated
Comment thread errors/list.go Outdated
Comment thread ai/image_storage.go Outdated
Comment thread ai/image/image.go
SizeLandscape = contractsai.ImageSizeLandscape
)

func Of(prompt string, options ...contractsai.Option) contractsai.ImageRequest {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this function, users will use facades.AI().Image directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread contracts/ai/image.go
Attachments(attachments ...Attachment) ImageRequest
Timeout(timeout time.Duration) ImageRequest
Store(disk ...string) (string, error)
StoreAs(path string, disk ...string) (string, error)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
StoreAs(path string, disk ...string) (string, error)
StoreAs(name string, disk ...string) (string, error)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread contracts/ai/response.go
Content() ([]byte, error)
MimeType() string
Store(disk ...string) (string, error)
StoreAs(path string, disk ...string) (string, error)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
StoreAs(path string, disk ...string) (string, error)
StoreAs(name string, disk ...string) (string, error)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread ai/response.go Outdated
Comment thread ai/response.go Outdated
Comment thread contracts/ai/response.go
package ai

// Response exposes generated text and provider metadata.
type Response interface {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
type TextResponse interface {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread ai/image_storage.go
Copilot AI review requested due to automatic review settings May 7, 2026 08:47
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 2 comments.

Comment thread ai/image_storage.go
Comment thread ai/response.go
@hwbrzzl hwbrzzl merged commit 9b628f1 into master May 7, 2026
21 of 23 checks passed
@hwbrzzl hwbrzzl deleted the bowen/#918-2 branch May 7, 2026 09:10
LinboLen added a commit to LinboLen/framework that referenced this pull request May 11, 2026
* 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feature] AI SDK Phase 4: Multi-Modal (Attachments, Image, and Audio)

2 participants