From 56ae965b73397142c54a00e86a802426a9034d41 Mon Sep 17 00:00:00 2001
From: shiyue <935732994@qq.com>
Date: Thu, 5 Feb 2026 19:28:49 +0800
Subject: [PATCH] =?UTF-8?q?1.=20HTML=20=E8=AE=BE=E7=BD=AE=E9=9D=A2?=
=?UTF-8?q?=E6=9D=BF=20(index.html:55-63)?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
新增 API格式 下拉选择框:
- raw - 原始API格式(使用 /v1/images/generations 端点)
- openai - OpenAI格式(使用 /v1/chat/completions 端点)
2. JavaScript 核心逻辑 (script.js)
设置字段 (script.js:10)
- currentSettings 新增 apiFormat: 'raw' 字段
API 服务 (script.js:685-916)
- testConnection: 根据 API 格式选择 /models 或 /v1/models 端点
- generateImage:
- 原始格式: 使用 prompt 请求体,端点 /v1/images/generations
- OpenAI格式: 使用 messages 请求体,端点 /v1/chat/completions
- extractImagesFromContent: 新增方法,支持从 Chat 响应中提取图像:
- ✅ Markdown 图片语法 
- ✅ Base64 图像数据
- ✅ 通用图像 URL(放宽限制)
- ✅ JSON 格式内容(包括代码块)
- ✅ 多模态内容数组
设置持久化 (script.js:1591, 1771, 1909)
- loadSettings / saveSettings 支持 apiFormat
- 自动保存监听器包含 apiFormat
连接测试 (script.js:2027, 2037)
- app.testConnection 传递 apiFormat 参数
---
index.html | 9 +++
script.js | 211 +++++++++++++++++++++++++++++++++++++++++++----------
2 files changed, 182 insertions(+), 38 deletions(-)
diff --git a/index.html b/index.html
index be9f169..0156b1a 100644
--- a/index.html
+++ b/index.html
@@ -52,6 +52,15 @@
+
diff --git a/script.js b/script.js
index 7f33c19..a57c0ea 100644
--- a/script.js
+++ b/script.js
@@ -7,6 +7,7 @@ let generatedImages = [];
let currentSettings = {
apiKey: '',
baseUrl: '',
+ apiFormat: 'raw', // 'raw' 原始API格式, 'openai' OpenAI Chat格式
model: 'grok-imagine-1.0',
timeout: 600,
proxy: '',
@@ -681,9 +682,12 @@ const indexedDBStorage = {
// API服务
const apiService = {
// 测试连接
- testConnection: async function(apiKey, baseUrl) {
+ testConnection: async function(apiKey, baseUrl, apiFormat) {
try {
- const response = await fetch(`${baseUrl}/models`, {
+ const normalizedBaseUrl = baseUrl.replace(/\/+$/, '');
+ // 根据 API 格式选择不同的测试端点
+ const modelsEndpoint = apiFormat === 'openai' ? '/v1/models' : '/models';
+ const response = await fetch(`${normalizedBaseUrl}${modelsEndpoint}`, {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json'
@@ -707,19 +711,41 @@ const apiService = {
console.warn('当前 API 不支持图像编辑,将忽略上传的图像');
}
- // 构建请求 payload(符合 OpenAI 图像生成 API 格式)
- const payload = {
- model: settings.model,
- prompt: message,
- n: settings.n || 1,
- response_format: settings.responseFormat || 'b64_json'
- };
+ const apiFormat = settings.apiFormat || 'raw';
+ const normalizedBaseUrl = settings.baseUrl.replace(/\/+$/, '');
+
+ // 根据 API 格式构建不同的请求
+ let endpoint, payload;
+
+ if (apiFormat === 'openai') {
+ // OpenAI Chat Completions 格式
+ endpoint = `${normalizedBaseUrl}/v1/chat/completions`;
+ payload = {
+ model: settings.model,
+ messages: [
+ {
+ role: 'user',
+ content: message
+ }
+ ],
+ n: settings.n || 1
+ };
+ } else {
+ // 原始 API 格式 (图像生成端点)
+ endpoint = `${normalizedBaseUrl}/v1/images/generations`;
+ payload = {
+ model: settings.model,
+ prompt: message,
+ n: settings.n || 1,
+ response_format: settings.responseFormat || 'b64_json'
+ };
+ }
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), settings.timeout * 1000);
try {
- const response = await fetch(`${settings.baseUrl}/v1/images/generations`, {
+ const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Authorization': `Bearer ${settings.apiKey}`,
@@ -738,32 +764,47 @@ const apiService = {
const data = await response.json();
- // 解析响应(OpenAI 图像生成 API 格式)
- const images = [];
+ // 根据 API 格式解析响应
+ const resultImages = [];
let hasError = false;
- if (data.data && Array.isArray(data.data)) {
- data.data.forEach(item => {
- if (item.b64_json) {
- // 检测 API 返回的错误标记
- if (item.b64_json === 'error' || item.b64_json === 'Error') {
- hasError = true;
- return;
+ if (apiFormat === 'openai') {
+ // 解析 OpenAI Chat Completions 响应
+ if (data.choices && Array.isArray(data.choices)) {
+ data.choices.forEach(choice => {
+ const content = choice.message?.content;
+ if (content) {
+ // 尝试从 content 中提取图像
+ const extractedImages = this.extractImagesFromContent(content);
+ resultImages.push(...extractedImages);
}
- // base64 格式响应
- const base64Data = `data:image/png;base64,${item.b64_json}`;
- const blobUrl = utils.base64ToBlobUrl(base64Data);
- images.push(blobUrl);
- } else if (item.url) {
- // 检测 URL 格式的错误标记
- if (item.url === 'error' || item.url === 'Error') {
- hasError = true;
- return;
+ });
+ }
+ } else {
+ // 解析原始 API 响应(OpenAI 图像生成 API 格式)
+ if (data.data && Array.isArray(data.data)) {
+ data.data.forEach(item => {
+ if (item.b64_json) {
+ // 检测 API 返回的错误标记
+ if (item.b64_json === 'error' || item.b64_json === 'Error') {
+ hasError = true;
+ return;
+ }
+ // base64 格式响应
+ const base64Data = `data:image/png;base64,${item.b64_json}`;
+ const blobUrl = utils.base64ToBlobUrl(base64Data);
+ resultImages.push(blobUrl);
+ } else if (item.url) {
+ // 检测 URL 格式的错误标记
+ if (item.url === 'error' || item.url === 'Error') {
+ hasError = true;
+ return;
+ }
+ // URL 格式响应
+ resultImages.push(item.url);
}
- // URL 格式响应
- images.push(item.url);
- }
- });
+ });
+ }
}
// 如果检测到错误标记,抛出错误以触发重试
@@ -773,14 +814,105 @@ const apiService = {
return {
success: true,
- content: `已生成 ${images.length} 张图像`,
- images: images,
+ content: `已生成 ${resultImages.length} 张图像`,
+ images: resultImages,
usage: data.usage || null
};
} catch (error) {
clearTimeout(timeoutId);
throw error;
}
+ },
+
+ // 从 OpenAI Chat 响应内容中提取图像
+ extractImagesFromContent: function(content) {
+ const images = [];
+
+ // 如果 content 是字符串,尝试解析
+ if (typeof content === 'string') {
+ // 优先匹配 Markdown 图片语法 
+ const markdownImageRegex = /!\[[^\]]*\]\(([^)]+)\)/g;
+ let mdMatch;
+ while ((mdMatch = markdownImageRegex.exec(content)) !== null) {
+ const url = mdMatch[1].trim();
+ if (url && !images.includes(url)) {
+ images.push(url);
+ }
+ }
+
+ // 尝试匹配 base64 图像数据
+ const base64Regex = /data:image\/[a-zA-Z]+;base64,[A-Za-z0-9+/=]+/g;
+ const base64Matches = content.match(base64Regex);
+ if (base64Matches) {
+ base64Matches.forEach(match => {
+ if (!images.includes(match)) {
+ const blobUrl = utils.base64ToBlobUrl(match);
+ images.push(blobUrl);
+ }
+ });
+ }
+
+ // 尝试匹配通用图像 URL(放宽限制,支持无扩展名的签名URL)
+ const urlRegex = /https?:\/\/[^\s"'<>\)]+/gi;
+ const urlMatches = content.match(urlRegex);
+ if (urlMatches) {
+ urlMatches.forEach(url => {
+ // 清理 URL 末尾可能的标点符号
+ const cleanUrl = url.replace(/[.,;:!?]+$/, '');
+ // 检查是否像图像 URL(包含常见图像关键词或扩展名)
+ const isImageUrl = /\.(png|jpg|jpeg|gif|webp|bmp|svg)(\?|$)/i.test(cleanUrl) ||
+ /image|img|photo|picture|generated|upload/i.test(cleanUrl);
+ if (isImageUrl && !images.includes(cleanUrl)) {
+ images.push(cleanUrl);
+ }
+ });
+ }
+
+ // 尝试解析 JSON 格式的内容(包括 Markdown 代码块中的 JSON)
+ let jsonStr = content;
+ // 尝试提取 Markdown 代码块中的 JSON
+ const codeBlockMatch = content.match(/```(?:json)?\s*([\s\S]*?)```/);
+ if (codeBlockMatch) {
+ jsonStr = codeBlockMatch[1].trim();
+ }
+ try {
+ const jsonContent = JSON.parse(jsonStr);
+ if (jsonContent.url && !images.includes(jsonContent.url)) {
+ images.push(jsonContent.url);
+ }
+ if (jsonContent.b64_json) {
+ const base64Data = `data:image/png;base64,${jsonContent.b64_json}`;
+ const blobUrl = utils.base64ToBlobUrl(base64Data);
+ images.push(blobUrl);
+ }
+ if (Array.isArray(jsonContent.data)) {
+ jsonContent.data.forEach(item => {
+ if (item.url && !images.includes(item.url)) images.push(item.url);
+ if (item.b64_json) {
+ const base64Data = `data:image/png;base64,${item.b64_json}`;
+ const blobUrl = utils.base64ToBlobUrl(base64Data);
+ images.push(blobUrl);
+ }
+ });
+ }
+ } catch (e) {
+ // 不是 JSON 格式,忽略
+ }
+ } else if (Array.isArray(content)) {
+ // 处理多模态内容数组
+ content.forEach(part => {
+ if (part.type === 'image_url' && part.image_url?.url) {
+ images.push(part.image_url.url);
+ }
+ // 处理 text 类型中可能包含的图像
+ if (part.type === 'text' && part.text) {
+ const textImages = this.extractImagesFromContent(part.text);
+ images.push(...textImages);
+ }
+ });
+ }
+
+ return images;
}
};
@@ -1587,6 +1719,7 @@ const uiController = {
// 更新UI
document.getElementById('apiKey').value = currentSettings.apiKey;
document.getElementById('baseUrl').value = currentSettings.baseUrl;
+ document.getElementById('apiFormat').value = currentSettings.apiFormat || 'raw';
document.getElementById('model').value = currentSettings.model;
document.getElementById('timeout').value = currentSettings.timeout;
document.getElementById('proxy').value = currentSettings.proxy;
@@ -1766,6 +1899,7 @@ const uiController = {
try {
currentSettings.apiKey = document.getElementById('apiKey').value;
currentSettings.baseUrl = document.getElementById('baseUrl').value;
+ currentSettings.apiFormat = document.getElementById('apiFormat').value;
currentSettings.model = document.getElementById('model').value;
currentSettings.timeout = parseInt(document.getElementById('timeout').value);
currentSettings.proxy = document.getElementById('proxy').value;
@@ -1903,7 +2037,7 @@ const app = {
}
// 设置变化时自动保存
- const inputs = ['apiKey', 'baseUrl', 'model', 'timeout', 'proxy'];
+ const inputs = ['apiKey', 'baseUrl', 'apiFormat', 'model', 'timeout', 'proxy'];
inputs.forEach(id => {
document.getElementById(id).addEventListener('change', utils.debounce(() => {
uiController.saveSettings();
@@ -1920,16 +2054,17 @@ const app = {
testConnection: async function() {
const apiKey = document.getElementById('apiKey').value;
const baseUrl = document.getElementById('baseUrl').value;
-
+ const apiFormat = document.getElementById('apiFormat').value;
+
if (!apiKey) {
utils.showNotification(ERROR_MESSAGES.NO_API_KEY, 'warning');
return;
}
uiController.showLoading(true);
-
+
try {
- const result = await apiService.testConnection(apiKey, baseUrl);
+ const result = await apiService.testConnection(apiKey, baseUrl, apiFormat);
if (result.success) {
uiController.updateConnectionStatus(true);