1+ const axios = require ( 'axios' )
2+ const { logger } = require ( '../utils/logger' )
3+ const { setResponseHeaders } = require ( './chat.js' )
4+ const accountManager = require ( '../utils/account.js' )
5+ const { uploadFileToQwenOss } = require ( '../utils/upload.js' )
6+
7+ /**
8+ * 主要的聊天完成处理函数
9+ * @param {object } req - Express 请求对象
10+ * @param {object } res - Express 响应对象
11+ */
12+ const handleImageCompletion = async ( req , res ) => {
13+ const { model, messages, size, chat_type } = req . body
14+ console . log ( JSON . stringify ( req . body . messages . filter ( item => item . role == "user" || item . role == "assistant" ) ) )
15+ const token = accountManager . getAccountToken ( )
16+
17+ try {
18+
19+ // 请求体模板
20+ const reqBody = {
21+ "stream" : false ,
22+ "chat_id" : null ,
23+ "model" : model ,
24+ "messages" : [
25+ {
26+ "role" : "user" ,
27+ "content" : "" ,
28+ "files" : [ ] ,
29+ "chat_type" : chat_type ,
30+ "feature_config" : {
31+ "output_schema" : "phase"
32+ }
33+ }
34+ ]
35+ }
36+
37+ const chat_id = await generateChatID ( token )
38+
39+ if ( ! chat_id ) {
40+ // 如果生成chat_id失败,则返回错误
41+ throw new Error ( )
42+ } else {
43+ reqBody . chat_id = chat_id
44+ }
45+
46+ // 拿到用户最后一句消息
47+ const _userPrompt = messages [ messages . length - 1 ] . content
48+ if ( ! _userPrompt ) {
49+ throw new Error ( )
50+ }
51+
52+ //分情况处理
53+ if ( chat_type == 't2i' ) {
54+ if ( Array . isArray ( _userPrompt ) ) {
55+ reqBody . messages [ 0 ] . content = _userPrompt . map ( item => item . type == "text" ? item . text : "" ) . join ( "\n\n" )
56+ } else {
57+ reqBody . messages [ 0 ] . content = _userPrompt
58+ }
59+ } else if ( chat_type == 'image_edit' ) {
60+ if ( ! Array . isArray ( _userPrompt ) ) {
61+ const select_image_list = [ ]
62+ const image_url_regex = / ! \[ i m a g e \] \( ( .* ?) \) / g
63+
64+ const ChatMessages = messages . filter ( item => item . role == "user" || item . role == "assistant" )
65+ if ( ChatMessages . length === 0 ) {
66+ throw new Error ( )
67+ }
68+
69+ // 遍历模型回复消息,拿到所有图片
70+ ChatMessages . forEach ( item => {
71+ if ( item . role == "assistant" ) {
72+ // 匹配图片url
73+ const image_url = image_url_regex . test ( item . content )
74+ // 如果匹配到图片url,则添加到图片列表
75+ if ( image_url ) {
76+ select_image_list . push ( item . content . replace ( " . replace ( ")" , "" ) )
77+ }
78+ } else {
79+ if ( Array . isArray ( item . content ) && item . content . length > 0 ) {
80+ for ( const content of item . content ) {
81+ if ( content . type == "image" ) {
82+ select_image_list . push ( content . image )
83+ }
84+ }
85+ }
86+ }
87+ } )
88+
89+ if ( select_image_list . length >= 1 ) {
90+ console . log ( select_image_list )
91+ reqBody . messages [ 0 ] . files . push ( {
92+ "type" : "image" ,
93+ "url" : select_image_list [ select_image_list . length - 1 ]
94+ } )
95+ reqBody . messages [ 0 ] . content += _userPrompt
96+ } else {
97+ throw new Error ( )
98+ }
99+
100+ } else {
101+ const texts = _userPrompt . filter ( item => item . type == "text" )
102+ if ( texts . length === 0 ) {
103+ throw new Error ( )
104+ }
105+ // 拼接提示词
106+ for ( const item of texts ) {
107+ reqBody . messages [ 0 ] . content += item . text
108+ }
109+ const files = _userPrompt . filter ( item => item . type == "image" )
110+ // 遍历图片
111+ for ( const item of files ) {
112+ reqBody . messages [ 0 ] . files . push ( {
113+ "type" : "image" ,
114+ "url" : item . image
115+ } )
116+ }
117+
118+ }
119+ }
120+
121+
122+ // 处理图片尺寸
123+ if ( chat_type == 't2i' ) {
124+ // 获取图片尺寸,优先级 参数 > 提示词 > 默认
125+ if ( size != undefined && size != null ) {
126+ reqBody . size = size
127+ } else if ( _userPrompt . indexOf ( "@4:3" ) != - 1 ) {
128+ reqBody . size = "4:3" //"1024*768"
129+ } else if ( _userPrompt . indexOf ( "@3:4" ) != - 1 ) {
130+ reqBody . size = "3:4" //"768*1024"
131+ } else if ( _userPrompt . indexOf ( "@16:9" ) != - 1 ) {
132+ reqBody . size = "16:9" //"1280*720"
133+ } else if ( _userPrompt . indexOf ( "@9:16" ) != - 1 ) {
134+ reqBody . size = "9:16" //"720*1280"
135+ }
136+ }
137+
138+ logger . info ( '发送图片请求' , 'CHAT' )
139+ console . log ( JSON . stringify ( reqBody ) )
140+ const response_data = await axios . post ( `https://chat.qwen.ai/api/v2/chat/completions?chat_id=${ chat_id } ` , reqBody , {
141+ headers : {
142+ "Authorization" : `Bearer ${ token } ` ,
143+ "Content-Type" : "application/json"
144+ } ,
145+ responseType : chat_type == 't2i' ? 'stream' : 'json' ,
146+ timeout : 1000 * 60 * 5
147+ } )
148+
149+ try {
150+ let contentUrl = null
151+ if ( chat_type == 't2i' ) {
152+ const decoder = new TextDecoder ( 'utf-8' )
153+ response_data . data . on ( 'data' , async ( chunk ) => {
154+ const data = decoder . decode ( chunk , { stream : true } ) . split ( '\n' ) . filter ( item => item . trim ( ) != "" )
155+ for ( const item of data ) {
156+ const jsonObj = JSON . parse ( item . replace ( "data:" , '' ) . trim ( ) )
157+ if ( jsonObj && jsonObj . choices && jsonObj . choices [ 0 ] && jsonObj . choices [ 0 ] . delta && jsonObj . choices [ 0 ] . delta . content . trim ( ) != "" && contentUrl == null ) {
158+ contentUrl = jsonObj . choices [ 0 ] . delta . content
159+ }
160+ }
161+ } )
162+
163+ response_data . data . on ( 'end' , ( ) => {
164+ return returnResponse ( res , model , contentUrl , req . body . stream )
165+ } )
166+ } else if ( chat_type == 'image_edit' ) {
167+ contentUrl = response_data . data ?. data ?. choices [ 0 ] ?. message ?. content [ 0 ] ?. image
168+ return returnResponse ( res , model , contentUrl , req . body . stream )
169+ }
170+
171+ } catch ( error ) {
172+ logger . error ( '图片处理错误' , 'CHAT' , error )
173+ res . status ( 500 ) . json ( { error : "服务错误!!!" } )
174+ }
175+
176+ } catch ( error ) {
177+ res . status ( 500 ) . json ( {
178+ error : "服务错误,请稍后再试"
179+ } )
180+ }
181+ }
182+
183+ /**
184+ * 生成chat_id
185+ * @param {* } token
186+ * @returns {Promise<string|null> } 返回生成的chat_id,如果失败则返回null
187+ */
188+ const generateChatID = async ( token ) => {
189+ try {
190+ const response_data = await axios . post ( "https://chat.qwen.ai/api/v2/chats/new" , {
191+ "title" : "New Chat" ,
192+ "models" : [
193+ "qwen3-235b-a22b"
194+ ] ,
195+ "chat_mode" : "normal" ,
196+ "chat_type" : "t2i" ,
197+ "timestamp" : new Date ( ) . getTime ( )
198+ } , {
199+ headers : {
200+ "Authorization" : `Bearer ${ token } ` ,
201+ "Content-Type" : "application/json"
202+ }
203+ } )
204+
205+ return response_data . data ?. data ?. id || null
206+
207+ } catch ( error ) {
208+ logger . error ( '生成chat_id失败' , 'CHAT' , '' , error . message )
209+ return null
210+ }
211+ }
212+
213+ /**
214+ * 返回响应
215+ * @param {* } res
216+ * @param {* } model
217+ * @param {* } contentUrl
218+ */
219+ const returnResponse = ( res , model , contentUrl , stream ) => {
220+ setResponseHeaders ( res , stream )
221+
222+ const returnBody = {
223+ "created" : new Date ( ) . getTime ( ) ,
224+ "model" : model ,
225+ "choices" : [
226+ {
227+ "index" : 0 ,
228+ "message" : {
229+ "role" : "assistant" ,
230+ "content" : ``
231+ } ,
232+ "finish_reason" : "stop"
233+ }
234+ ]
235+ }
236+
237+ if ( stream ) {
238+ res . write ( `data: ${ JSON . stringify ( returnBody ) } \n\n` )
239+ res . write ( `data: [DONE]\n\n` )
240+ res . end ( )
241+ } else {
242+ res . json ( returnBody )
243+ }
244+ }
245+
246+ module . exports = {
247+ handleImageCompletion
248+ }
0 commit comments