@@ -5,10 +5,14 @@ import (
55 "aurora/internal/tokens"
66 chatgpt_types "aurora/typings/chatgpt"
77 official_types "aurora/typings/official"
8+ "aurora/httpclient"
9+ "encoding/base64"
10+ "io"
11+ "net/http"
812 "strings"
913)
1014
11- func ConvertAPIRequest (api_request official_types.APIRequest , secret * tokens.Secret , proxy string ) chatgpt_types.ChatGPTRequest {
15+ func ConvertAPIRequest (api_request official_types.APIRequest , secret * tokens.Secret , proxy string , client httpclient. AuroraHttpClient ) chatgpt_types.ChatGPTRequest {
1216 chatgpt_request := chatgpt_types .NewChatGPTRequest ()
1317
1418 // Model is passed directly to upstream; default to "auto" if not provided
@@ -22,7 +26,7 @@ func ConvertAPIRequest(api_request official_types.APIRequest, secret *tokens.Sec
2226 if apiMessage .Role == "system" {
2327 apiMessage .Role = "critic"
2428 }
25- parts , metadata := buildMessageParts (apiMessage )
29+ parts , metadata := buildMessageParts (apiMessage , client , secret , proxy )
2630 if len (metadata ) > 0 {
2731 chatgpt_request .AddMultimodalMessage (apiMessage .Role , parts , metadata )
2832 continue
@@ -39,9 +43,9 @@ func ConvertTTSAPIRequest(input string) chatgpt_types.ChatGPTRequest {
3943 return chatgpt_request
4044}
4145
42- func buildMessageParts (message official_types.APIMessage ) ([]interface {}, map [string ]interface {}) {
46+ func buildMessageParts (message official_types.APIMessage , client httpclient. AuroraHttpClient , secret * tokens. Secret , proxy string ) ([]interface {}, map [string ]interface {}) {
4347 text := message .Text ()
44- files := enrichFiles (message .Files ())
48+ files := enrichFiles (message .Files (), client , secret , proxy )
4549 if len (files ) == 0 {
4650 return []interface {}{text }, nil
4751 }
@@ -106,15 +110,26 @@ func buildMessageParts(message official_types.APIMessage) ([]interface{}, map[st
106110 }
107111}
108112
109- func enrichFiles (files []official_types.FileAttachment ) []official_types.FileAttachment {
113+ func enrichFiles (files []official_types.FileAttachment , client httpclient. AuroraHttpClient , secret * tokens. Secret , proxy string ) []official_types.FileAttachment {
110114 enriched := make ([]official_types.FileAttachment , 0 , len (files ))
111115 seen := make (map [string ]bool )
112116 for _ , file := range files {
113117 id := fileID (file )
114118 if id == "" || seen [id ] {
115119 continue
116120 }
117- if uploaded , ok := backendchatgpt .LookupUploadedFile (id ); ok {
121+
122+ // 处理 image_url 的 inline 数据(data: URL 或 http URL)
123+ if file .Source != "" && client != nil && secret != nil {
124+ if uploaded , ok := uploadInlineImage (file , client , secret , proxy ); ok {
125+ file = uploaded
126+ } else {
127+ // 免费账号或上传失败:丢弃图片,只保留文本
128+ continue
129+ }
130+ }
131+
132+ if uploaded , ok := backendchatgpt .LookupUploadedFile (fileID (file )); ok {
118133 if file .ID == "" {
119134 file .ID = uploaded .ID
120135 }
@@ -137,12 +152,109 @@ func enrichFiles(files []official_types.FileAttachment) []official_types.FileAtt
137152 file .LibraryFileID = uploaded .LibraryFileID
138153 }
139154 }
140- seen [id ] = true
155+ seen [fileID ( file ) ] = true
141156 enriched = append (enriched , file )
142157 }
143158 return enriched
144159}
145160
161+ // uploadInlineImage 将 data: URL 或 http URL 图片上传到 ChatGPT 文件服务。
162+ func uploadInlineImage (file official_types.FileAttachment , client httpclient.AuroraHttpClient , secret * tokens.Secret , proxy string ) (official_types.FileAttachment , bool ) {
163+ src := file .Source
164+ var data []byte
165+ var filename string
166+ var contentType string
167+
168+ if strings .HasPrefix (src , "data:" ) {
169+ // data:image/png;base64,iVBOR...
170+ commaIdx := strings .Index (src , "," )
171+ if commaIdx < 0 {
172+ return file , false
173+ }
174+ meta := src [:commaIdx ]
175+ b64data := src [commaIdx + 1 :]
176+ // 提取 mime type
177+ if semiIdx := strings .Index (meta , ";" ); semiIdx > 5 {
178+ contentType = meta [5 :semiIdx ]
179+ }
180+ var err error
181+ data , err = base64 .StdEncoding .DecodeString (b64data )
182+ if err != nil {
183+ // 尝试 raw base64
184+ data , err = base64 .RawStdEncoding .DecodeString (b64data )
185+ if err != nil {
186+ return file , false
187+ }
188+ }
189+ filename = "image.png"
190+ if contentType == "" {
191+ contentType = "image/png"
192+ }
193+ } else if strings .HasPrefix (src , "http://" ) || strings .HasPrefix (src , "https://" ) {
194+ // 下载远程图片
195+ resp , err := http .Get (src )
196+ if err != nil {
197+ return file , false
198+ }
199+ defer resp .Body .Close ()
200+ if resp .StatusCode != 200 {
201+ return file , false
202+ }
203+ data , err = io .ReadAll (resp .Body )
204+ if err != nil {
205+ return file , false
206+ }
207+ contentType = resp .Header .Get ("Content-Type" )
208+ filename = guessFilenameFromURL (src )
209+ } else {
210+ return file , false
211+ }
212+
213+ if len (data ) == 0 {
214+ return file , false
215+ }
216+ if contentType == "" {
217+ contentType = http .DetectContentType (data )
218+ }
219+ if filename == "" {
220+ filename = "image.png"
221+ }
222+
223+ uploaded , _ , err := backendchatgpt .UploadFile (client , secret , proxy , filename , contentType , data )
224+ if err != nil {
225+ // 免费 token 无法上传文件,回退:把 data URL 原样传递
226+ return file , false
227+ }
228+
229+ return official_types.FileAttachment {
230+ ID : uploaded .FileID ,
231+ FileID : uploaded .FileID ,
232+ Name : uploaded .Filename ,
233+ FileName : uploaded .Filename ,
234+ Filename : uploaded .Filename ,
235+ MimeType : uploaded .MimeType ,
236+ MIMEType : uploaded .MimeType ,
237+ Size : uploaded .Bytes ,
238+ Width : uploaded .Width ,
239+ Height : uploaded .Height ,
240+ LibraryFileID : uploaded .LibraryFileID ,
241+ }, true
242+ }
243+
244+ func guessFilenameFromURL (url string ) string {
245+ idx := strings .LastIndex (url , "/" )
246+ if idx >= 0 && idx < len (url )- 1 {
247+ name := url [idx + 1 :]
248+ if q := strings .Index (name , "?" ); q >= 0 {
249+ name = name [:q ]
250+ }
251+ if name != "" {
252+ return name
253+ }
254+ }
255+ return "image.png"
256+ }
257+
146258func fileID (file official_types.FileAttachment ) string {
147259 if strings .TrimSpace (file .FileID ) != "" {
148260 return strings .TrimSpace (file .FileID )
0 commit comments