Skip to content

Commit 46d1cfc

Browse files
committed
添加image-edit功能,将t2i模式重命名为image模式
1 parent 0b875ef commit 46d1cfc

File tree

9 files changed

+470
-600
lines changed

9 files changed

+470
-600
lines changed

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -405,10 +405,11 @@ Authorization: Bearer sk-your-api-key
405405
}
406406
```
407407

408-
### 🎨 T2I 图像生成
408+
### 🎨 图像生成/编辑
409409

410-
使用 `-t2i`模型启用文本到图像生成功能。
411-
你可以通过在请求体中添加 `size` 参数或在消息内容中包含特定关键词 `1:1`, `4:3`, `3:4`, `16:9`, `9:16` 来控制图片尺寸。
410+
使用 `-image` 模型启用文本到图像生成功能。
411+
使用 `-image-edit` 模型启用图像修改功能。
412+
当使用``-image` 时你可以通过在请求体中添加 `size` 参数或在消息内容中包含特定关键词 `1:1`, `4:3`, `3:4`, `16:9`, `9:16` 来控制图片尺寸。
412413

413414
```http
414415
POST /v1/chat/completions
@@ -419,7 +420,7 @@ Authorization: Bearer sk-your-api-key
419420
**请求体:**
420421
```json
421422
{
422-
"model": "qwen-max-latest-t2i",
423+
"model": "qwen-max-latest-image",
423424
"messages": [
424425
{
425426
"role": "user",

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "qwen2api",
3-
"version": "2025.08.23.14.00",
3+
"version": "2025.08.24.01.00",
44
"main": "src/server.js",
55
"scripts": {
66
"start": "node src/start.js",

src/controllers/chat.image.js

Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
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 = /!\[image\]\((.*?)\)/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("![image](", "").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": `![image](${contentUrl})`
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

Comments
 (0)