1
1
package main
2
2
3
3
import (
4
+ "errors"
5
+ "fmt"
6
+ "net/url"
7
+ "os"
8
+ "path/filepath"
9
+ "regexp"
10
+ "strconv"
11
+
4
12
"github.com/mutablelogic/go-client/pkg/client"
5
13
"github.com/mutablelogic/go-client/pkg/openai"
6
14
)
7
15
16
+ /////////////////////////////////////////////////////////////////////
17
+ // TYPES
18
+
19
+ type openaiImageResponse struct {
20
+ Url string `json:"-"`
21
+ Path string `json:"path"`
22
+ Bytes uint `json:"bytes_written"`
23
+ }
24
+
25
+ /////////////////////////////////////////////////////////////////////
26
+ // GLOBALS
27
+
28
+ var (
29
+ reOpenAISize = regexp .MustCompile (`^(\d+)x(\d+)$` )
30
+ defaultVoice = "alloy"
31
+ )
32
+
8
33
/////////////////////////////////////////////////////////////////////
9
34
// REGISTER FUNCTIONS
10
35
11
36
func OpenAIFlags (flags * Flags ) {
12
37
flags .String ("openai-api-key" , "${OPENAI_API_KEY}" , "OpenAI API key" )
13
- flags .String ("openai-model" , "" , "OpenAI Model" )
14
- flags .Uint ("openai-count" , 0 , "Number of results to return" )
38
+ flags .String ("model" , "" , "Model to use for generation" )
39
+ flags .Uint ("count" , 0 , "Number of results to return" )
40
+ flags .Bool ("natural" , false , "Create more natural images" )
41
+ flags .Bool ("hd" , false , "Create images with finer details and greater consistency across the image" )
42
+ flags .String ("size" , "" , "Size of output image (256x256, 512x512, 1024x1024, 1792x1024 or 1024x1792)" )
43
+ flags .Bool ("open" , false , "Open images in default viewer" )
15
44
}
16
45
17
46
func OpenAIRegister (cmd []Client , opts []client.ClientOpt , flags * Flags ) ([]Client , error ) {
18
- // Get API key
19
- key , err := flags .GetString ("openai-api-key" )
20
- if err != nil {
21
- return nil , err
22
- }
23
-
24
47
// Create client
25
- openai , err := openai .New (key , opts ... )
48
+ openai , err := openai .New (flags . GetString ( "openai-api- key" ) , opts ... )
26
49
if err != nil {
27
50
return nil , err
28
51
}
@@ -32,6 +55,9 @@ func OpenAIRegister(cmd []Client, opts []client.ClientOpt, flags *Flags) ([]Clie
32
55
ns : "openai" ,
33
56
cmd : []Command {
34
57
{Name : "models" , Description : "Return registered models" , MinArgs : 2 , MaxArgs : 2 , Fn : openaiModels (openai , flags )},
58
+ {Name : "model" , Description : "Return model information" , Syntax : "<model>" , MinArgs : 3 , MaxArgs : 3 , Fn : openaiModel (openai , flags )},
59
+ {Name : "image" , Description : "Create images from a prompt" , Syntax : "<prompt>" , MinArgs : 3 , MaxArgs : 3 , Fn : openaiImages (openai , flags )},
60
+ {Name : "speak" , Description : "Create speech from a prompt" , Syntax : "(<voice>) <prompt>" , MinArgs : 3 , MaxArgs : 4 , Fn : openaiSpeak (openai , flags )},
35
61
},
36
62
})
37
63
@@ -52,3 +78,146 @@ func openaiModels(client *openai.Client, flags *Flags) CommandFn {
52
78
return nil
53
79
}
54
80
}
81
+
82
+ func openaiModel (client * openai.Client , flags * Flags ) CommandFn {
83
+ return func () error {
84
+ if model , err := client .GetModel (flags .Arg (2 )); err != nil {
85
+ return err
86
+ } else if err := flags .Write (model ); err != nil {
87
+ return err
88
+ }
89
+ return nil
90
+ }
91
+ }
92
+
93
+ func openaiSpeak (client * openai.Client , flags * Flags ) CommandFn {
94
+ return func () error {
95
+ // Set options
96
+ opts := []openai.Opt {}
97
+ if model := flags .GetString ("model" ); model != "" {
98
+ opts = append (opts , openai .OptModel (model ))
99
+ }
100
+ if format := flags .GetOutExt (); format != "" {
101
+ opts = append (opts , openai .OptResponseFormat (format ))
102
+ }
103
+ var voice , prompt string
104
+ if flags .NArg () == 4 {
105
+ voice = flags .Arg (2 )
106
+ prompt = flags .Arg (3 )
107
+ } else {
108
+ voice = defaultVoice
109
+ prompt = flags .Arg (2 )
110
+ }
111
+
112
+ // Determine the filename
113
+ w , err := os .Create ("output.mp3" )
114
+ if err != nil {
115
+ return err
116
+ }
117
+ defer w .Close ()
118
+
119
+ // Create the audio
120
+ response , err := client .Speech (w , voice , prompt , opts ... )
121
+ if err != nil {
122
+ return err
123
+ }
124
+
125
+ // Open images
126
+ if flags .GetBool ("open" ) {
127
+ if err := open ("output.mp3" ); err != nil {
128
+ return err
129
+ }
130
+ }
131
+
132
+ fmt .Println (response , "bytes written" )
133
+
134
+ // Return any errors
135
+ return nil
136
+ }
137
+ }
138
+
139
+ func openaiImages (client * openai.Client , flags * Flags ) CommandFn {
140
+ return func () error {
141
+ // Set options
142
+ opts := []openai.Opt {}
143
+ if model := flags .GetString ("model" ); model != "" {
144
+ opts = append (opts , openai .OptModel (model ))
145
+ }
146
+ if count , err := flags .GetInt ("count" ); err != nil {
147
+ return err
148
+ } else if count > 0 {
149
+ opts = append (opts , openai .OptCount (count ))
150
+ }
151
+ if flags .GetBool ("hd" ) {
152
+ opts = append (opts , openai .OptQuality ("hd" ), openai .OptModel ("dall-e-3" ))
153
+ }
154
+ if flags .GetBool ("natural" ) {
155
+ opts = append (opts , openai .OptStyle ("natural" ))
156
+ }
157
+ if size := flags .GetString ("size" ); size != "" {
158
+ if width , height , err := openaiSize (size ); err != nil {
159
+ return err
160
+ } else {
161
+ opts = append (opts , openai .OptSize (width , height ))
162
+ }
163
+ }
164
+ if format := flags .GetOutExt (); format != "" {
165
+ opts = append (opts , openai .OptResponseFormat (format ))
166
+ }
167
+
168
+ // Create images
169
+ response , err := client .CreateImages (flags .Arg (2 ), opts ... )
170
+ if err != nil {
171
+ return err
172
+ }
173
+
174
+ // Write out images
175
+ var result error
176
+ var written []openaiImageResponse
177
+ for _ , image := range response {
178
+ if url , err := url .Parse (image .Url ); err != nil {
179
+ result = errors .Join (result , err )
180
+ } else if w , err := os .Create (filepath .Base (url .Path )); err != nil {
181
+ result = errors .Join (result , err )
182
+ } else {
183
+ defer w .Close ()
184
+ if n , err := client .WriteImage (w , image ); err != nil {
185
+ result = errors .Join (result , err )
186
+ } else {
187
+ written = append (written , openaiImageResponse {Url : image .Url , Bytes : uint (n ), Path : w .Name ()})
188
+ }
189
+ }
190
+ }
191
+
192
+ // Open images
193
+ if flags .GetBool ("open" ) {
194
+ var paths []string
195
+ for _ , image := range written {
196
+ paths = append (paths , image .Path )
197
+ }
198
+ if err := open (paths ... ); err != nil {
199
+ result = errors .Join (result , err )
200
+ }
201
+ } else if err := flags .Write (written ); err != nil {
202
+ result = errors .Join (result , err )
203
+ }
204
+
205
+ // Return any errors
206
+ return result
207
+ }
208
+ }
209
+
210
+ /////////////////////////////////////////////////////////////////////
211
+ // PRIVATE METHODS
212
+
213
+ func openaiSize (size string ) (uint , uint , error ) {
214
+ if n := reOpenAISize .FindStringSubmatch (size ); n == nil || len (n ) != 3 {
215
+ return 0 , 0 , errors .New ("invalid size, should be <width>x<height>" )
216
+ } else if w , err := strconv .ParseUint (n [1 ], 10 , 64 ); err != nil {
217
+ return 0 , 0 , err
218
+ } else if h , err := strconv .ParseUint (n [2 ], 10 , 64 ); err != nil {
219
+ return 0 , 0 , err
220
+ } else {
221
+ return uint (w ), uint (h ), nil
222
+ }
223
+ }
0 commit comments