Skip to content

Commit 236a3c5

Browse files
authored
Merge pull request #1575 from ksylvan/0704-advanced-image-output-options
Advanced image generation parameters for OpenAI models
2 parents 152d74d + b241898 commit 236a3c5

File tree

9 files changed

+504
-81
lines changed

9 files changed

+504
-81
lines changed

README.md

Lines changed: 80 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -114,13 +114,11 @@ Keep in mind that many of these were recorded when Fabric was Python-based, so r
114114
>
115115
> July 4, 2025
116116
>
117-
> - Fabric now supports web search using the `--search` and `--search-location` flags
118-
> - Web search is available for both Anthropic and OpenAI providers
119-
> - Previous plugin-level search configurations have been removed in favor of the new flag-based approach.
120-
> - If you used the previous approach, consider cleaning up your `~/.config/fabric/.env` file, removing the unused `ANTHROPIC_WEB_SEARCH_TOOL_ENABLED` and `ANTHROPIC_WEB_SEARCH_TOOL_LOCATION` variables.
121-
> - Fabric now supports image generation using the `--image-file` flag with OpenAI models
122-
> - Image generation works with both text prompts and input images (via `--attachment`) for image editing tasks
123-
>
117+
> - **Web Search**: Fabric now supports web search for Anthropic and OpenAI models using the `--search` and `--search-location` flags. This replaces the previous plugin-based search, so you may want to remove the old `ANTHROPIC_WEB_SEARCH_TOOL_*` variables from your `~/.config/fabric/.env` file.
118+
> - **Image Generation**: Fabric now has powerful image generation capabilities with OpenAI.
119+
> - Generate images from text prompts and save them using `--image-file`.
120+
> - Edit existing images by providing an input image with `--attachment`.
121+
> - Control image `size`, `quality`, `compression`, and `background` with the new `--image-*` flags.
124122
>
125123
>June 17, 2025
126124
>
@@ -292,88 +290,88 @@ yt() {
292290
293291
You can add the below code for the equivalent aliases inside PowerShell by running `notepad $PROFILE` inside a PowerShell window:
294292
295-
```powershell
296-
# Path to the patterns directory
297-
$patternsPath = Join-Path $HOME ".config/fabric/patterns"
298-
foreach ($patternDir in Get-ChildItem -Path $patternsPath -Directory) {
299-
$patternName = $patternDir.Name
300-
301-
# Dynamically define a function for each pattern
302-
$functionDefinition = @"
303-
function $patternName {
304-
[CmdletBinding()]
305-
param(
306-
[Parameter(ValueFromPipeline = `$true)]
307-
[string] `$InputObject,
308-
309-
[Parameter(ValueFromRemainingArguments = `$true)]
310-
[String[]] `$patternArgs
311-
)
312-
313-
begin {
314-
# Initialize an array to collect pipeline input
315-
`$collector = @()
316-
}
293+
```powershell
294+
# Path to the patterns directory
295+
$patternsPath = Join-Path $HOME ".config/fabric/patterns"
296+
foreach ($patternDir in Get-ChildItem -Path $patternsPath -Directory) {
297+
$patternName = $patternDir.Name
298+
299+
# Dynamically define a function for each pattern
300+
$functionDefinition = @"
301+
function $patternName {
302+
[CmdletBinding()]
303+
param(
304+
[Parameter(ValueFromPipeline = `$true)]
305+
[string] `$InputObject,
306+
307+
[Parameter(ValueFromRemainingArguments = `$true)]
308+
[String[]] `$patternArgs
309+
)
310+
311+
begin {
312+
# Initialize an array to collect pipeline input
313+
`$collector = @()
314+
}
317315
318-
process {
319-
# Collect pipeline input objects
320-
if (`$InputObject) {
321-
`$collector += `$InputObject
322-
}
316+
process {
317+
# Collect pipeline input objects
318+
if (`$InputObject) {
319+
`$collector += `$InputObject
323320
}
321+
}
322+
323+
end {
324+
# Join all pipeline input into a single string, separated by newlines
325+
`$pipelineContent = `$collector -join "`n"
324326
325-
end {
326-
# Join all pipeline input into a single string, separated by newlines
327-
`$pipelineContent = `$collector -join "`n"
328-
329-
# If there's pipeline input, include it in the call to fabric
330-
if (`$pipelineContent) {
331-
`$pipelineContent | fabric --pattern $patternName `$patternArgs
332-
} else {
333-
# No pipeline input; just call fabric with the additional args
334-
fabric --pattern $patternName `$patternArgs
335-
}
327+
# If there's pipeline input, include it in the call to fabric
328+
if (`$pipelineContent) {
329+
`$pipelineContent | fabric --pattern $patternName `$patternArgs
330+
} else {
331+
# No pipeline input; just call fabric with the additional args
332+
fabric --pattern $patternName `$patternArgs
336333
}
337334
}
338-
"@
339-
# Add the function to the current session
340-
Invoke-Expression $functionDefinition
341-
}
335+
}
336+
"@
337+
# Add the function to the current session
338+
Invoke-Expression $functionDefinition
339+
}
342340
343-
# Define the 'yt' function as well
344-
function yt {
345-
[CmdletBinding()]
346-
param(
347-
[Parameter()]
348-
[Alias("timestamps")]
349-
[switch]$t,
350-
351-
[Parameter(Position = 0, ValueFromPipeline = $true)]
352-
[string]$videoLink
353-
)
354-
355-
begin {
356-
$transcriptFlag = "--transcript"
357-
if ($t) {
358-
$transcriptFlag = "--transcript-with-timestamps"
359-
}
341+
# Define the 'yt' function as well
342+
function yt {
343+
[CmdletBinding()]
344+
param(
345+
[Parameter()]
346+
[Alias("timestamps")]
347+
[switch]$t,
348+
349+
[Parameter(Position = 0, ValueFromPipeline = $true)]
350+
[string]$videoLink
351+
)
352+
353+
begin {
354+
$transcriptFlag = "--transcript"
355+
if ($t) {
356+
$transcriptFlag = "--transcript-with-timestamps"
360357
}
358+
}
361359
362-
process {
363-
if (-not $videoLink) {
364-
Write-Error "Usage: yt [-t | --timestamps] youtube-link"
365-
return
366-
}
360+
process {
361+
if (-not $videoLink) {
362+
Write-Error "Usage: yt [-t | --timestamps] youtube-link"
363+
return
367364
}
365+
}
368366
369-
end {
370-
if ($videoLink) {
371-
# Execute and allow output to flow through the pipeline
372-
fabric -y $videoLink $transcriptFlag
373-
}
367+
end {
368+
if ($videoLink) {
369+
# Execute and allow output to flow through the pipeline
370+
fabric -y $videoLink $transcriptFlag
374371
}
375372
}
376-
```
373+
}
374+
```
377375
378376
This also creates a `yt` alias that allows you to use `yt https://www.youtube.com/watch?v=4b0iet22VIk` to get transcripts, comments, and metadata.
379377
@@ -559,6 +557,11 @@ Application Options:
559557
--search Enable web search tool for supported models (Anthropic, OpenAI)
560558
--search-location= Set location for web search results (e.g., 'America/Los_Angeles')
561559
--image-file= Save generated image to specified file path (e.g., 'output.png')
560+
--image-size= Image dimensions: 1024x1024, 1536x1024, 1024x1536, auto (default: auto)
561+
--image-quality= Image quality: low, medium, high, auto (default: auto)
562+
--image-compression= Compression level 0-100 for JPEG/WebP formats (default: not set)
563+
--image-background= Background type: opaque, transparent (default: opaque, only for
564+
PNG/WebP)
562565
563566
Help Options:
564567
-h, --help Show this help message

cli/flags.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ type Flags struct {
7878
Search bool `long:"search" description:"Enable web search tool for supported models (Anthropic, OpenAI)"`
7979
SearchLocation string `long:"search-location" description:"Set location for web search results (e.g., 'America/Los_Angeles')"`
8080
ImageFile string `long:"image-file" description:"Save generated image to specified file path (e.g., 'output.png')"`
81+
ImageSize string `long:"image-size" description:"Image dimensions: 1024x1024, 1536x1024, 1024x1536, auto (default: auto)"`
82+
ImageQuality string `long:"image-quality" description:"Image quality: low, medium, high, auto (default: auto)"`
83+
ImageCompression int `long:"image-compression" description:"Compression level 0-100 for JPEG/WebP formats (default: not set)"`
84+
ImageBackground string `long:"image-background" description:"Background type: opaque, transparent (default: opaque, only for PNG/WebP)"`
8185
}
8286

8387
var debug = false
@@ -282,12 +286,95 @@ func validateImageFile(imagePath string) error {
282286
return fmt.Errorf("invalid image file extension '%s'. Supported formats: .png, .jpeg, .jpg, .webp", ext)
283287
}
284288

289+
// validateImageParameters validates image generation parameters
290+
func validateImageParameters(imagePath, size, quality, background string, compression int) error {
291+
if imagePath == "" {
292+
// Check if any image parameters are specified without --image-file
293+
if size != "" || quality != "" || background != "" || compression != 0 {
294+
return fmt.Errorf("image parameters (--image-size, --image-quality, --image-background, --image-compression) can only be used with --image-file")
295+
}
296+
return nil
297+
}
298+
299+
// Validate size
300+
if size != "" {
301+
validSizes := []string{"1024x1024", "1536x1024", "1024x1536", "auto"}
302+
valid := false
303+
for _, validSize := range validSizes {
304+
if size == validSize {
305+
valid = true
306+
break
307+
}
308+
}
309+
if !valid {
310+
return fmt.Errorf("invalid image size '%s'. Supported sizes: 1024x1024, 1536x1024, 1024x1536, auto", size)
311+
}
312+
}
313+
314+
// Validate quality
315+
if quality != "" {
316+
validQualities := []string{"low", "medium", "high", "auto"}
317+
valid := false
318+
for _, validQuality := range validQualities {
319+
if quality == validQuality {
320+
valid = true
321+
break
322+
}
323+
}
324+
if !valid {
325+
return fmt.Errorf("invalid image quality '%s'. Supported qualities: low, medium, high, auto", quality)
326+
}
327+
}
328+
329+
// Validate background
330+
if background != "" {
331+
validBackgrounds := []string{"opaque", "transparent"}
332+
valid := false
333+
for _, validBackground := range validBackgrounds {
334+
if background == validBackground {
335+
valid = true
336+
break
337+
}
338+
}
339+
if !valid {
340+
return fmt.Errorf("invalid image background '%s'. Supported backgrounds: opaque, transparent", background)
341+
}
342+
}
343+
344+
// Get file format for format-specific validations
345+
ext := strings.ToLower(filepath.Ext(imagePath))
346+
347+
// Validate compression (only for jpeg/webp)
348+
if compression != 0 { // 0 means not set
349+
if ext != ".jpg" && ext != ".jpeg" && ext != ".webp" {
350+
return fmt.Errorf("image compression can only be used with JPEG and WebP formats, not %s", ext)
351+
}
352+
if compression < 0 || compression > 100 {
353+
return fmt.Errorf("image compression must be between 0 and 100, got %d", compression)
354+
}
355+
}
356+
357+
// Validate background transparency (only for png/webp)
358+
if background == "transparent" {
359+
if ext != ".png" && ext != ".webp" {
360+
return fmt.Errorf("transparent background can only be used with PNG and WebP formats, not %s", ext)
361+
}
362+
}
363+
364+
return nil
365+
}
366+
285367
func (o *Flags) BuildChatOptions() (ret *common.ChatOptions, err error) {
286368
// Validate image file if specified
287369
if err = validateImageFile(o.ImageFile); err != nil {
288370
return nil, err
289371
}
290372

373+
// Validate image parameters
374+
if err = validateImageParameters(o.ImageFile, o.ImageSize, o.ImageQuality, o.ImageBackground, o.ImageCompression); err != nil {
375+
return nil, err
376+
}
377+
291378
ret = &common.ChatOptions{
292379
Model: o.Model,
293380
Temperature: o.Temperature,
@@ -300,6 +387,10 @@ func (o *Flags) BuildChatOptions() (ret *common.ChatOptions, err error) {
300387
Search: o.Search,
301388
SearchLocation: o.SearchLocation,
302389
ImageFile: o.ImageFile,
390+
ImageSize: o.ImageSize,
391+
ImageQuality: o.ImageQuality,
392+
ImageCompression: o.ImageCompression,
393+
ImageBackground: o.ImageBackground,
303394
}
304395
return
305396
}

0 commit comments

Comments
 (0)