|
|
const models = {
|
|
|
"o1-preview": "o1-preview",
|
|
|
"o1-mini": "o1-preview",
|
|
|
"claude-3-5-sonnet-20240620": "claude-3-5-sonnet",
|
|
|
"claude-3-5-sonnet-20241022": "claude-3-5-sonnet",
|
|
|
"claude-3-5-haiku-20241022": "claude-3-5-haiku",
|
|
|
"claude-3-haiku-20240307": "claude-3-5-haiku",
|
|
|
"gpt-4o": "gpt-4o",
|
|
|
"gpt-4o-mini": "gpt-4o-mini",
|
|
|
"gpt-4o-file": "gpt-4o",
|
|
|
"gpt-4o-mini-file": "gpt-4o-mini",
|
|
|
"claude-3-5-sonnet-file": "claude-3-5-sonnet",
|
|
|
"claude-3-5-haiku-file": "claude-3-5-haiku",
|
|
|
"gemini-1.5-pro-latest": "gemini-1.5-pro",
|
|
|
"gemini-1.5-flash-latest": "gemini-1.5-flash",
|
|
|
"genspark": "genspark",
|
|
|
"deepl": "deepl",
|
|
|
};
|
|
|
|
|
|
|
|
|
const drawingModels = new Set([
|
|
|
"dall-e-3",
|
|
|
"flux",
|
|
|
"flux-speed",
|
|
|
"flux-pro/ultra",
|
|
|
"ideogram",
|
|
|
"recraft-v3",
|
|
|
]);
|
|
|
|
|
|
|
|
|
const CACHE_CONFIG = {
|
|
|
TTL: 30 * 60,
|
|
|
EXCLUDED_MODELS: ["o1-preview"],
|
|
|
MAX_CACHE_SIZE: 100,
|
|
|
};
|
|
|
|
|
|
|
|
|
class MessageCache {
|
|
|
constructor() {
|
|
|
this.cache = new Map();
|
|
|
this.keyTimestamps = new Map();
|
|
|
}
|
|
|
|
|
|
generateKey(messages, model) {
|
|
|
|
|
|
const contentStr = JSON.stringify(
|
|
|
messages.map((m) => ({
|
|
|
role: m.role,
|
|
|
content: m.content,
|
|
|
}))
|
|
|
);
|
|
|
return `${model}:${contentStr}`;
|
|
|
}
|
|
|
|
|
|
set(key, value) {
|
|
|
|
|
|
if (this.cache.size >= CACHE_CONFIG.MAX_CACHE_SIZE) {
|
|
|
|
|
|
const oldestKey = [...this.keyTimestamps.entries()].sort(
|
|
|
([, a], [, b]) => a - b
|
|
|
)[0][0];
|
|
|
this.cache.delete(oldestKey);
|
|
|
this.keyTimestamps.delete(oldestKey);
|
|
|
}
|
|
|
|
|
|
this.cache.set(key, value);
|
|
|
this.keyTimestamps.set(key, Date.now());
|
|
|
}
|
|
|
|
|
|
get(key) {
|
|
|
const timestamp = this.keyTimestamps.get(key);
|
|
|
if (!timestamp) return null;
|
|
|
|
|
|
|
|
|
if (Date.now() - timestamp > CACHE_CONFIG.TTL * 1000) {
|
|
|
this.cache.delete(key);
|
|
|
this.keyTimestamps.delete(key);
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
return this.cache.get(key);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const messageCache = new MessageCache();
|
|
|
|
|
|
class SessionPool {
|
|
|
constructor() {
|
|
|
this.sessions = [];
|
|
|
this.currentIndex = 0;
|
|
|
}
|
|
|
initialize(sessionString) {
|
|
|
if (!sessionString) return;
|
|
|
this.sessions = sessionString
|
|
|
.split(",")
|
|
|
.map((s) => s.trim())
|
|
|
.filter((s) => s);
|
|
|
this.currentIndex = Math.floor(Math.random() * this.sessions.length);
|
|
|
}
|
|
|
getNext() {
|
|
|
if (this.sessions.length === 0) return null;
|
|
|
const session = this.sessions[this.currentIndex];
|
|
|
this.currentIndex = (this.currentIndex + 1) % this.sessions.length;
|
|
|
return session;
|
|
|
}
|
|
|
size() {
|
|
|
return this.sessions.length;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const sessionPool = new SessionPool();
|
|
|
|
|
|
const corsHeaders = {
|
|
|
"Access-Control-Allow-Origin": "*",
|
|
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
|
"Access-Control-Allow-Headers": "Content-Type, Authorization",
|
|
|
};
|
|
|
|
|
|
function doubleEncode(str) {
|
|
|
return encodeURIComponent(encodeURIComponent(str));
|
|
|
}
|
|
|
|
|
|
function createErrorResponse(message, status = 500) {
|
|
|
return new Response(JSON.stringify({ error: message }), {
|
|
|
status,
|
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
|
});
|
|
|
}
|
|
|
|
|
|
async function cleanupProject(session_id, project_id) {
|
|
|
try {
|
|
|
const headers = {
|
|
|
Cookie: `session_id=${session_id}`,
|
|
|
"User-Agent": "Apifox/1.0.0 (https://apifox.com)",
|
|
|
Accept: "*/*",
|
|
|
Host: "www.genspark.ai",
|
|
|
Connection: "keep-alive",
|
|
|
"Accept-Encoding": "gzip, deflate, br",
|
|
|
};
|
|
|
const response = await fetch(
|
|
|
`https://www.genspark.ai/api/project/delete?project_id=${project_id}`,
|
|
|
{
|
|
|
method: "GET",
|
|
|
headers,
|
|
|
}
|
|
|
);
|
|
|
return response.ok;
|
|
|
} catch (error) {
|
|
|
console.error("Cleanup project error:", error);
|
|
|
return false;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function extractProjectId(text) {
|
|
|
try {
|
|
|
if (text.startsWith("data: ")) {
|
|
|
const content = JSON.parse(text.slice(6));
|
|
|
if (content.type === "project_start" && content.id) {
|
|
|
return content.id;
|
|
|
}
|
|
|
}
|
|
|
} catch (e) {}
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
function processChunk(chunk, messageId, model) {
|
|
|
try {
|
|
|
const content = chunk.replace(/^data:\s*/, "").trim();
|
|
|
if (!content || content === "[DONE]") return null;
|
|
|
|
|
|
const parsed = JSON.parse(content);
|
|
|
if (!parsed) return null;
|
|
|
|
|
|
|
|
|
if (model === "deepl") {
|
|
|
if (parsed.field_name === "content" && parsed.field_value) {
|
|
|
return JSON.stringify({
|
|
|
translations: [
|
|
|
{
|
|
|
detected_source_language:
|
|
|
parsed.session_state?.detected_source_lang || "",
|
|
|
text: parsed.field_value,
|
|
|
},
|
|
|
],
|
|
|
});
|
|
|
}
|
|
|
} else if (drawingModels.has(model)) {
|
|
|
|
|
|
if (parsed.content) {
|
|
|
return JSON.stringify({
|
|
|
created: Date.now(),
|
|
|
data: [
|
|
|
{
|
|
|
url: parsed.content,
|
|
|
revised_prompt: parsed.prompt || "",
|
|
|
},
|
|
|
],
|
|
|
});
|
|
|
}
|
|
|
} else {
|
|
|
if (parsed.delta) {
|
|
|
return `data: ${JSON.stringify({
|
|
|
id: `chatcmpl-${messageId}`,
|
|
|
choices: [
|
|
|
{
|
|
|
index: 0,
|
|
|
delta: { content: parsed.delta },
|
|
|
},
|
|
|
],
|
|
|
created: Math.floor(Date.now() / 1000),
|
|
|
model: models[model],
|
|
|
object: "chat.completion.chunk",
|
|
|
})}\n\n`;
|
|
|
}
|
|
|
if (
|
|
|
parsed.field_value &&
|
|
|
parsed.field_name !== "session_state.answer_is_finished" &&
|
|
|
parsed.field_name !== "content" &&
|
|
|
parsed.field_name !== "session_state" &&
|
|
|
!parsed.delta &&
|
|
|
parsed.type !== "project_field"
|
|
|
) {
|
|
|
return JSON.stringify({
|
|
|
id: `chatcmpl-${messageId}`,
|
|
|
object: "chat.completion",
|
|
|
created: Math.floor(Date.now() / 1000),
|
|
|
model,
|
|
|
choices: [
|
|
|
{
|
|
|
index: 0,
|
|
|
message: {
|
|
|
role: "assistant",
|
|
|
content: parsed.field_value,
|
|
|
},
|
|
|
finish_reason: "stop",
|
|
|
},
|
|
|
],
|
|
|
usage: {
|
|
|
prompt_tokens: 100,
|
|
|
completion_tokens: 50,
|
|
|
total_tokens: parsed.field_value.length,
|
|
|
},
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
} catch (e) {}
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
async function* streamGenerator(reader) {
|
|
|
let buffer = "";
|
|
|
const decoder = new TextDecoder();
|
|
|
|
|
|
try {
|
|
|
while (true) {
|
|
|
const { done, value } = await reader.read();
|
|
|
if (done) break;
|
|
|
|
|
|
buffer += decoder.decode(value, { stream: true });
|
|
|
const lines = buffer.split("\n");
|
|
|
buffer = lines.pop() || "";
|
|
|
|
|
|
for (const line of lines) {
|
|
|
if (line.trim()) yield line;
|
|
|
}
|
|
|
}
|
|
|
if (buffer.trim()) yield buffer;
|
|
|
} catch (e) {
|
|
|
console.error("Stream error:", e);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
async function makeRequest(
|
|
|
session_id,
|
|
|
requestModel,
|
|
|
messages,
|
|
|
target_lang_code,
|
|
|
source_lang_code,
|
|
|
num_images = 1,
|
|
|
size = "1024x1024",
|
|
|
quality = "standard",
|
|
|
style = "vivid"
|
|
|
) {
|
|
|
const headers = {
|
|
|
Cookie: `session_id=${session_id}`,
|
|
|
"User-Agent": "Apifox/1.0.0 (https://apifox.com)",
|
|
|
"Content-Type": "application/json",
|
|
|
Accept: "*/*",
|
|
|
Host: "www.genspark.ai",
|
|
|
Connection: "keep-alive",
|
|
|
};
|
|
|
let retryCount = 0;
|
|
|
while (retryCount < 3) {
|
|
|
try {
|
|
|
let requestBody = {};
|
|
|
if (requestModel === "deepl") {
|
|
|
|
|
|
requestBody = {
|
|
|
type: "COPILOT_MOA_TRANSLATOR",
|
|
|
current_query_string: "type=COPILOT_MOA_TRANSLATOR",
|
|
|
messages,
|
|
|
user_s_input: messages[messages.length - 1].content,
|
|
|
action_params: {},
|
|
|
extra_data: {
|
|
|
models: [models[requestModel]],
|
|
|
run_with_another_model: false,
|
|
|
target_lang_code: target_lang_code || "zh-hans",
|
|
|
source_lang_code: source_lang_code || "",
|
|
|
writingContent: null,
|
|
|
},
|
|
|
};
|
|
|
} else if (drawingModels.has(requestModel)) {
|
|
|
|
|
|
requestBody = {
|
|
|
type: "COPILOT_MOA_IMAGE",
|
|
|
current_query_string: "type=COPILOT_MOA_IMAGE",
|
|
|
messages,
|
|
|
user_s_input: messages[messages.length - 1].content,
|
|
|
action_params: {},
|
|
|
extra_data: {
|
|
|
model_configs: [],
|
|
|
llm_model: "gpt-4",
|
|
|
imageModelMap: {},
|
|
|
writingContent: null,
|
|
|
},
|
|
|
};
|
|
|
for (let i = 0; i < num_images; i++) {
|
|
|
requestBody.extra_data.model_configs.push({
|
|
|
model: requestModel === "dall-e-3" ? "dalle-3" : requestModel,
|
|
|
size: size,
|
|
|
quality: quality,
|
|
|
style: style,
|
|
|
});
|
|
|
}
|
|
|
} else {
|
|
|
|
|
|
requestBody = {
|
|
|
type: "COPILOT_MOA_CHAT",
|
|
|
current_query_string: "type=COPILOT_MOA_CHAT",
|
|
|
messages,
|
|
|
action_params: {},
|
|
|
extra_data: {
|
|
|
models: [models[requestModel] || models["claude-3-5-sonnet-20241022"]],
|
|
|
run_with_another_model: false,
|
|
|
writingContent: null,
|
|
|
},
|
|
|
};
|
|
|
}
|
|
|
|
|
|
const response = await fetch(
|
|
|
"https://www.genspark.ai/api/copilot/ask",
|
|
|
{
|
|
|
method: "POST",
|
|
|
headers,
|
|
|
body: JSON.stringify(requestBody),
|
|
|
}
|
|
|
);
|
|
|
if (!response.ok) {
|
|
|
retryCount++;
|
|
|
await new Promise((resolve) =>
|
|
|
setTimeout(resolve, 1000 * retryCount)
|
|
|
);
|
|
|
continue;
|
|
|
}
|
|
|
return response;
|
|
|
} catch (error) {
|
|
|
retryCount++;
|
|
|
await new Promise((resolve) =>
|
|
|
setTimeout(resolve, 1000 * retryCount)
|
|
|
);
|
|
|
if (retryCount >= 3) throw error;
|
|
|
}
|
|
|
}
|
|
|
throw new Error("请求失败,已重试3次");
|
|
|
}
|
|
|
|
|
|
async function searchModel(session_id, messages) {
|
|
|
const headers = {
|
|
|
Cookie: `session_id=${session_id}`,
|
|
|
"User-Agent": "Apifox/1.0.0 (https://apifox.com)",
|
|
|
"Content-Type": "application/json",
|
|
|
Accept: "*/*",
|
|
|
Host: "www.genspark.ai",
|
|
|
Connection: "keep-alive",
|
|
|
};
|
|
|
let retryCount = 0;
|
|
|
while (retryCount < 3) {
|
|
|
try {
|
|
|
const query = doubleEncode(messages[messages.length - 1].content);
|
|
|
const response = await fetch(
|
|
|
`https://www.genspark.ai/api/search/stream?query=${query}`,
|
|
|
{
|
|
|
method: "POST",
|
|
|
headers,
|
|
|
}
|
|
|
);
|
|
|
if (!response.ok) {
|
|
|
retryCount++;
|
|
|
await new Promise((resolve) =>
|
|
|
setTimeout(resolve, 1000 * retryCount)
|
|
|
);
|
|
|
continue;
|
|
|
}
|
|
|
return response;
|
|
|
} catch (error) {
|
|
|
retryCount++;
|
|
|
await new Promise((resolve) =>
|
|
|
setTimeout(resolve, 1000 * retryCount)
|
|
|
);
|
|
|
if (retryCount >= 3) throw error;
|
|
|
}
|
|
|
}
|
|
|
throw new Error("请求失败,已重试3次");
|
|
|
}
|
|
|
|
|
|
async function pollTaskId(taskId, session_id, maxRetries, retryInterval) {
|
|
|
let retries = 0;
|
|
|
while (retries < maxRetries) {
|
|
|
try {
|
|
|
const statusResponse = await fetch(
|
|
|
`https://www.genspark.ai/api/spark/image_generation_task_status?task_id=${taskId}`,
|
|
|
{
|
|
|
headers: {
|
|
|
Cookie: `session_id=${session_id}`,
|
|
|
"User-Agent": "Apifox/1.0.0 (https://apifox.com)",
|
|
|
Accept: "*/*",
|
|
|
Host: "www.genspark.ai",
|
|
|
Connection: "keep-alive",
|
|
|
},
|
|
|
}
|
|
|
);
|
|
|
if (!statusResponse.ok) {
|
|
|
throw new Error(`Status check failed: ${statusResponse.status}`);
|
|
|
}
|
|
|
const data = await statusResponse.json();
|
|
|
if (data.data.status === "SUCCESS") {
|
|
|
const urls = data.data.image_urls_nowatermark;
|
|
|
if (urls?.length) {
|
|
|
return urls;
|
|
|
}
|
|
|
} else if (data.data.status === "FAILED") {
|
|
|
throw new Error("Image generation failed");
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error(`Poll error for task ${taskId} (retry ${retries}):`, error);
|
|
|
}
|
|
|
await new Promise((r) => setTimeout(r, retryInterval));
|
|
|
retries++;
|
|
|
}
|
|
|
throw new Error(`Timeout waiting for task: ${taskId}`);
|
|
|
}
|
|
|
|
|
|
function arrayBufferToBase64(buffer) {
|
|
|
let binary = '';
|
|
|
const bytes = new Uint8Array(buffer);
|
|
|
const len = bytes.byteLength;
|
|
|
const chunkSize = 8192;
|
|
|
|
|
|
for (let i = 0; i < len; i += chunkSize) {
|
|
|
const chunk = bytes.subarray(i, i + chunkSize);
|
|
|
binary += String.fromCharCode.apply(null, chunk);
|
|
|
}
|
|
|
return btoa(binary);
|
|
|
}
|
|
|
|
|
|
|
|
|
async function processMessages(messages, env) {
|
|
|
|
|
|
for (let message of messages) {
|
|
|
|
|
|
if (Array.isArray(message.content)) {
|
|
|
|
|
|
for (let i = 0; i < message.content.length; i++) {
|
|
|
let entry = message.content[i];
|
|
|
if (entry.type === 'image_url') {
|
|
|
|
|
|
let url = entry.image_url?.url;
|
|
|
if (!url) {
|
|
|
throw new Error('image_url.url is required');
|
|
|
}
|
|
|
|
|
|
let base64ImageData;
|
|
|
if (url.startsWith('data:image')) {
|
|
|
|
|
|
base64ImageData = url;
|
|
|
} else {
|
|
|
|
|
|
base64ImageData = await fetchImageAsDataURL(url, env);
|
|
|
}
|
|
|
|
|
|
entry.image_url.url = base64ImageData;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
async function fetchImageAsDataURL(url, env) {
|
|
|
|
|
|
let cacheKey = `image:${url}`;
|
|
|
let cachedData = await env.kv.get(cacheKey);
|
|
|
if (cachedData) {
|
|
|
return cachedData;
|
|
|
}
|
|
|
|
|
|
let response = await fetch(url);
|
|
|
if (!response.ok) {
|
|
|
throw new Error(`Failed to fetch image from ${url}`);
|
|
|
}
|
|
|
let arrayBuffer = await response.arrayBuffer();
|
|
|
let base64Data = arrayBufferToBase64(arrayBuffer);
|
|
|
|
|
|
let contentType = response.headers.get('Content-Type') || 'image/jpeg';
|
|
|
|
|
|
let dataURL = `data:${contentType};base64,${base64Data}`;
|
|
|
|
|
|
await env.kv.put(cacheKey, dataURL, { expirationTtl: CACHE_CONFIG.TTL });
|
|
|
return dataURL;
|
|
|
}
|
|
|
|
|
|
|
|
|
function extractLastUserInput(messages) {
|
|
|
|
|
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
|
let message = messages[i];
|
|
|
if (message.role === 'user') {
|
|
|
if (Array.isArray(message.content)) {
|
|
|
|
|
|
for (let j = message.content.length - 1; j >= 0; j--) {
|
|
|
let entry = message.content[j];
|
|
|
if (entry.type === 'text' && entry.text) {
|
|
|
return entry.text;
|
|
|
}
|
|
|
}
|
|
|
} else if (typeof message.content === 'string') {
|
|
|
|
|
|
return message.content;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
return '';
|
|
|
}
|
|
|
|
|
|
export default {
|
|
|
async fetch(request, env, ctx) {
|
|
|
if (request.method === "OPTIONS") {
|
|
|
return new Response(null, { headers: corsHeaders });
|
|
|
}
|
|
|
|
|
|
const url = new URL(request.url);
|
|
|
if (url.pathname === "/health") {
|
|
|
return new Response(JSON.stringify({ status: "ok" }), {
|
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
|
});
|
|
|
}
|
|
|
|
|
|
if (url.pathname === "/v1/models" && request.method === "GET") {
|
|
|
return new Response(
|
|
|
JSON.stringify({
|
|
|
object: "list",
|
|
|
data: Object.keys(models).map((model) => ({
|
|
|
id: model,
|
|
|
object: "model",
|
|
|
created: 1706745938,
|
|
|
owned_by: "genspark",
|
|
|
})),
|
|
|
}),
|
|
|
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
|
|
);
|
|
|
}
|
|
|
|
|
|
|
|
|
if (
|
|
|
(url.pathname === "/v1/chat/completions" ||
|
|
|
url.pathname === "/translate") &&
|
|
|
request.method === "POST"
|
|
|
) {
|
|
|
try {
|
|
|
let requestData = await request.json();
|
|
|
let {
|
|
|
messages,
|
|
|
prompt,
|
|
|
stream = false,
|
|
|
model = "deepl",
|
|
|
target_lang_code,
|
|
|
source_lang_code,
|
|
|
} = requestData;
|
|
|
|
|
|
|
|
|
if (url.pathname === "/translate") {
|
|
|
const { text, source_lang, target_lang } = requestData;
|
|
|
messages = [{ role: "user", content: text }];
|
|
|
model = "deepl";
|
|
|
target_lang_code = target_lang || "zh-hans";
|
|
|
source_lang_code = source_lang || "";
|
|
|
}
|
|
|
|
|
|
|
|
|
if (!messages && prompt) {
|
|
|
messages = [{ role: "user", content: prompt }];
|
|
|
}
|
|
|
|
|
|
|
|
|
const {
|
|
|
num_images = 1,
|
|
|
size = "1024x1024",
|
|
|
response_format = "url",
|
|
|
quality = "standard",
|
|
|
style = "vivid",
|
|
|
} = requestData;
|
|
|
|
|
|
const authHeader = request.headers.get("authorization");
|
|
|
|
|
|
if (sessionPool.size() === 0) {
|
|
|
const sessionString = authHeader?.replace("Bearer ", "");
|
|
|
sessionPool.initialize(sessionString);
|
|
|
if (sessionPool.size() === 0) {
|
|
|
return createErrorResponse("未提供有效的 session_id", 401);
|
|
|
}
|
|
|
}
|
|
|
const session_id = sessionPool.getNext();
|
|
|
if (!session_id) {
|
|
|
return createErrorResponse("无可用的会话ID", 500);
|
|
|
}
|
|
|
|
|
|
|
|
|
const cacheKey = messageCache.generateKey(messages, model);
|
|
|
let cachedResponse = null;
|
|
|
if (!CACHE_CONFIG.EXCLUDED_MODELS.includes(model)) {
|
|
|
cachedResponse = messageCache.get(cacheKey);
|
|
|
}
|
|
|
if (cachedResponse) {
|
|
|
return new Response(cachedResponse, {
|
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
|
});
|
|
|
}
|
|
|
|
|
|
|
|
|
await processMessages(messages, env);
|
|
|
const user_s_input = extractLastUserInput(messages);
|
|
|
|
|
|
if (drawingModels.has(model)) {
|
|
|
|
|
|
|
|
|
const requestBody = {
|
|
|
type: "COPILOT_MOA_IMAGE",
|
|
|
current_query_string: "type=COPILOT_MOA_IMAGE",
|
|
|
messages,
|
|
|
user_s_input: user_s_input,
|
|
|
action_params: {},
|
|
|
extra_data: {
|
|
|
model_configs: [],
|
|
|
llm_model: "gpt-4",
|
|
|
imageModelMap: {},
|
|
|
writingContent: null,
|
|
|
},
|
|
|
};
|
|
|
for (let i = 0; i < num_images; i++) {
|
|
|
requestBody.extra_data.model_configs.push({
|
|
|
model: model === "dall-e-3" ? "dalle-3" : model,
|
|
|
size: size,
|
|
|
quality: quality,
|
|
|
style: style,
|
|
|
});
|
|
|
}
|
|
|
const headers = {
|
|
|
Cookie: `session_id=${session_id}`,
|
|
|
"User-Agent": "Apifox/1.0.0 (https://apifox.com)",
|
|
|
"Content-Type": "application/json",
|
|
|
Accept: "*/*",
|
|
|
Host: "www.genspark.ai",
|
|
|
Connection: "keep-alive",
|
|
|
};
|
|
|
if (stream === true || stream === "true") {
|
|
|
|
|
|
const { readable, writable } = new TransformStream();
|
|
|
const writer = writable.getWriter();
|
|
|
|
|
|
ctx.waitUntil(
|
|
|
(async () => {
|
|
|
try {
|
|
|
const response = await fetch(
|
|
|
"https://www.genspark.ai/api/copilot/ask",
|
|
|
{
|
|
|
method: "POST",
|
|
|
headers,
|
|
|
body: JSON.stringify(requestBody),
|
|
|
}
|
|
|
);
|
|
|
|
|
|
if (!response.ok) {
|
|
|
await writer.write(
|
|
|
new TextEncoder().encode(
|
|
|
`data: ${JSON.stringify({
|
|
|
error: "上游服务请求失败",
|
|
|
})}\n\n`
|
|
|
)
|
|
|
);
|
|
|
await writer.close();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const reader = response.body.getReader();
|
|
|
let taskIds = [];
|
|
|
|
|
|
|
|
|
let responseText = "";
|
|
|
for await (const line of streamGenerator(reader)) {
|
|
|
responseText += line + "\n";
|
|
|
if (line.includes("task_id")) {
|
|
|
try {
|
|
|
const contentLine = line.replace("data: ", "");
|
|
|
const { content: innerContent } = JSON.parse(contentLine);
|
|
|
if (innerContent) {
|
|
|
const { generated_images } = JSON.parse(innerContent);
|
|
|
if (generated_images) {
|
|
|
for (const img of generated_images) {
|
|
|
if (img.task_id) {
|
|
|
taskIds.push(img.task_id);
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
} catch (e) {}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
if (!taskIds.length) {
|
|
|
await writer.write(
|
|
|
new TextEncoder().encode(
|
|
|
`data: ${JSON.stringify({
|
|
|
error: "获取任务ID失败",
|
|
|
})}\n\n`
|
|
|
)
|
|
|
);
|
|
|
await writer.close();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
|
|
|
const maxRetries = 30;
|
|
|
const retryInterval = 2000;
|
|
|
|
|
|
for (const taskId of taskIds) {
|
|
|
let retries = 0;
|
|
|
let imageSent = false;
|
|
|
while (retries < maxRetries && !imageSent) {
|
|
|
try {
|
|
|
const statusResponse = await fetch(
|
|
|
`https://www.genspark.ai/api/spark/image_generation_task_status?task_id=${taskId}`,
|
|
|
{
|
|
|
headers,
|
|
|
}
|
|
|
);
|
|
|
|
|
|
if (!statusResponse.ok) {
|
|
|
throw new Error(
|
|
|
`Status check failed: ${statusResponse.status}`
|
|
|
);
|
|
|
}
|
|
|
|
|
|
const data = await statusResponse.json();
|
|
|
if (data.data.status === "SUCCESS") {
|
|
|
const urls = data.data.image_urls_nowatermark;
|
|
|
if (urls?.length) {
|
|
|
|
|
|
for (const url of urls) {
|
|
|
const data = {
|
|
|
id: `chatcmpl-${crypto.randomUUID()}`,
|
|
|
object: "chat.completion.chunk",
|
|
|
created: Math.floor(Date.now() / 1000),
|
|
|
model: model,
|
|
|
choices: [
|
|
|
{
|
|
|
delta: { content: `\n` },
|
|
|
index: 0,
|
|
|
finish_reason: null,
|
|
|
},
|
|
|
],
|
|
|
};
|
|
|
await writer.write(
|
|
|
new TextEncoder().encode(
|
|
|
`data: ${JSON.stringify(data)}\n\n`
|
|
|
)
|
|
|
);
|
|
|
}
|
|
|
imageSent = true;
|
|
|
break;
|
|
|
}
|
|
|
} else if (data.data.status === "FAILED") {
|
|
|
throw new Error("Image generation failed");
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
console.error(
|
|
|
`Poll error for task ${taskId} (retry ${retries}):`,
|
|
|
error
|
|
|
);
|
|
|
}
|
|
|
await new Promise((r) => setTimeout(r, retryInterval));
|
|
|
retries++;
|
|
|
}
|
|
|
if (!imageSent) {
|
|
|
const errorData = {
|
|
|
id: `chatcmpl-${crypto.randomUUID()}`,
|
|
|
object: "chat.completion.chunk",
|
|
|
created: Math.floor(Date.now() / 1000),
|
|
|
model: model,
|
|
|
choices: [
|
|
|
{
|
|
|
delta: {
|
|
|
content: `\n`,
|
|
|
},
|
|
|
index: 0,
|
|
|
finish_reason: null,
|
|
|
},
|
|
|
],
|
|
|
};
|
|
|
await writer.write(
|
|
|
new TextEncoder().encode(
|
|
|
`data: ${JSON.stringify(errorData)}\n\n`
|
|
|
)
|
|
|
);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
await writer.write(new TextEncoder().encode("data: [DONE]\n\n"));
|
|
|
} catch (error) {
|
|
|
console.error("Stream error:", error);
|
|
|
await writer.abort(error);
|
|
|
} finally {
|
|
|
await writer.close();
|
|
|
}
|
|
|
})()
|
|
|
);
|
|
|
|
|
|
return new Response(readable, {
|
|
|
headers: {
|
|
|
...corsHeaders,
|
|
|
"Content-Type": "text/event-stream",
|
|
|
"Cache-Control": "no-cache",
|
|
|
Connection: "keep-alive",
|
|
|
},
|
|
|
});
|
|
|
} else {
|
|
|
|
|
|
|
|
|
const response = await fetch(
|
|
|
"https://www.genspark.ai/api/copilot/ask",
|
|
|
{
|
|
|
method: "POST",
|
|
|
headers,
|
|
|
body: JSON.stringify(requestBody),
|
|
|
}
|
|
|
);
|
|
|
if (!response.ok) {
|
|
|
return createErrorResponse("上游服务请求失败", response.status);
|
|
|
}
|
|
|
|
|
|
const text = await response.text();
|
|
|
const taskIds = [];
|
|
|
const lines = text.split("\n");
|
|
|
for (const line of lines) {
|
|
|
if (line.includes("task_id")) {
|
|
|
try {
|
|
|
const content = line.replace("data: ", "");
|
|
|
if (!content) continue;
|
|
|
const { content: innerContent } = JSON.parse(content);
|
|
|
if (!innerContent) continue;
|
|
|
const { generated_images } = JSON.parse(innerContent);
|
|
|
if (!generated_images) continue;
|
|
|
for (const img of generated_images) {
|
|
|
if (img.task_id) {
|
|
|
taskIds.push(img.task_id);
|
|
|
}
|
|
|
}
|
|
|
} catch (e) {}
|
|
|
}
|
|
|
}
|
|
|
if (!taskIds.length) {
|
|
|
return createErrorResponse("获取任务ID失败");
|
|
|
}
|
|
|
|
|
|
const maxRetries = 30;
|
|
|
const retryInterval = 2000;
|
|
|
|
|
|
const taskPromises = taskIds.map((taskId) =>
|
|
|
pollTaskId(taskId, session_id, maxRetries, retryInterval)
|
|
|
);
|
|
|
let imageUrls = [];
|
|
|
try {
|
|
|
const results = await Promise.all(taskPromises);
|
|
|
results.forEach((urls) => {
|
|
|
imageUrls.push(...urls);
|
|
|
});
|
|
|
} catch (error) {
|
|
|
console.error("Polling error:", error);
|
|
|
return createErrorResponse(error.message);
|
|
|
}
|
|
|
|
|
|
|
|
|
if (response_format === "b64_json") {
|
|
|
|
|
|
const imageDatas = await Promise.all(
|
|
|
imageUrls.map(async (url) => {
|
|
|
const cachedData = await env.kv.get(url);
|
|
|
if (cachedData) {
|
|
|
return { b64_json: cachedData };
|
|
|
} else {
|
|
|
const imageResponse = await fetch(url);
|
|
|
const imageArrayBuffer = await imageResponse.arrayBuffer();
|
|
|
const base64Data = arrayBufferToBase64(imageArrayBuffer);
|
|
|
|
|
|
await env.kv.put(url, base64Data, {
|
|
|
expirationTtl: CACHE_CONFIG.TTL,
|
|
|
});
|
|
|
return { b64_json: base64Data };
|
|
|
}
|
|
|
})
|
|
|
);
|
|
|
|
|
|
|
|
|
const imageMarkdowns = imageDatas
|
|
|
.map(
|
|
|
(imgData, idx) =>
|
|
|
``
|
|
|
)
|
|
|
.join("\n");
|
|
|
|
|
|
const messageId = crypto.randomUUID();
|
|
|
|
|
|
const result = {
|
|
|
id: `chatcmpl-${messageId}`,
|
|
|
object: "chat.completion",
|
|
|
created: Math.floor(Date.now() / 1000),
|
|
|
model: model,
|
|
|
choices: [
|
|
|
{
|
|
|
index: 0,
|
|
|
message: {
|
|
|
role: "assistant",
|
|
|
content: imageMarkdowns,
|
|
|
},
|
|
|
finish_reason: "stop",
|
|
|
},
|
|
|
],
|
|
|
usage: {
|
|
|
prompt_tokens: 0,
|
|
|
completion_tokens: 0,
|
|
|
total_tokens: 0,
|
|
|
},
|
|
|
};
|
|
|
|
|
|
|
|
|
if (!CACHE_CONFIG.EXCLUDED_MODELS.includes(model)) {
|
|
|
messageCache.set(cacheKey, JSON.stringify(result));
|
|
|
}
|
|
|
|
|
|
return new Response(JSON.stringify(result), {
|
|
|
headers: {
|
|
|
...corsHeaders,
|
|
|
"Content-Type": "application/json",
|
|
|
},
|
|
|
});
|
|
|
} else {
|
|
|
|
|
|
const imageMarkdowns = imageUrls
|
|
|
.map((url) => ``)
|
|
|
.join("\n");
|
|
|
|
|
|
const messageId = crypto.randomUUID();
|
|
|
|
|
|
const result = {
|
|
|
id: `chatcmpl-${messageId}`,
|
|
|
object: "chat.completion",
|
|
|
created: Math.floor(Date.now() / 1000),
|
|
|
model: model,
|
|
|
choices: [
|
|
|
{
|
|
|
index: 0,
|
|
|
message: {
|
|
|
role: "assistant",
|
|
|
content: imageMarkdowns,
|
|
|
},
|
|
|
finish_reason: "stop",
|
|
|
},
|
|
|
],
|
|
|
usage: {
|
|
|
prompt_tokens: 0,
|
|
|
completion_tokens: 0,
|
|
|
total_tokens: 0,
|
|
|
},
|
|
|
};
|
|
|
|
|
|
|
|
|
if (!CACHE_CONFIG.EXCLUDED_MODELS.includes(model)) {
|
|
|
messageCache.set(cacheKey, JSON.stringify(result));
|
|
|
}
|
|
|
|
|
|
return new Response(JSON.stringify(result), {
|
|
|
headers: {
|
|
|
...corsHeaders,
|
|
|
"Content-Type": "application/json",
|
|
|
},
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let response;
|
|
|
if (model === "genspark") {
|
|
|
response = await searchModel(session_id, messages);
|
|
|
} else {
|
|
|
response = await makeRequest(
|
|
|
session_id,
|
|
|
model,
|
|
|
messages,
|
|
|
target_lang_code,
|
|
|
source_lang_code
|
|
|
);
|
|
|
}
|
|
|
|
|
|
if (!response.ok) {
|
|
|
return createErrorResponse("上游服务请求失败", response.status);
|
|
|
}
|
|
|
|
|
|
const messageId = crypto.randomUUID();
|
|
|
let project_id = null;
|
|
|
|
|
|
if (stream === true || stream === "true") {
|
|
|
const { readable, writable } = new TransformStream();
|
|
|
const writer = writable.getWriter();
|
|
|
ctx.waitUntil(
|
|
|
(async () => {
|
|
|
try {
|
|
|
const reader = response.body.getReader();
|
|
|
for await (const line of streamGenerator(reader)) {
|
|
|
if (!project_id) {
|
|
|
project_id = extractProjectId(line);
|
|
|
}
|
|
|
const processed = processChunk(line, messageId, model);
|
|
|
if (processed) {
|
|
|
await writer.write(new TextEncoder().encode(processed));
|
|
|
}
|
|
|
}
|
|
|
await writer.write(new TextEncoder().encode("data: [DONE]\n\n"));
|
|
|
} catch (error) {
|
|
|
console.error("Stream error:", error);
|
|
|
} finally {
|
|
|
await writer.close();
|
|
|
if (project_id) {
|
|
|
await cleanupProject(session_id, project_id);
|
|
|
}
|
|
|
}
|
|
|
})()
|
|
|
);
|
|
|
return new Response(readable, {
|
|
|
headers: {
|
|
|
...corsHeaders,
|
|
|
"Content-Type": "text/event-stream",
|
|
|
"Cache-Control": "no-cache",
|
|
|
Connection: "keep-alive",
|
|
|
},
|
|
|
});
|
|
|
}
|
|
|
|
|
|
const chunks = [];
|
|
|
const reader = response.body.getReader();
|
|
|
try {
|
|
|
while (true) {
|
|
|
const { done, value } = await reader.read();
|
|
|
if (done) break;
|
|
|
const text = new TextDecoder().decode(value);
|
|
|
chunks.push(text);
|
|
|
if (!project_id) {
|
|
|
const lines = text.split("\n");
|
|
|
for (const line of lines) {
|
|
|
project_id = extractProjectId(line);
|
|
|
if (project_id) break;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
} finally {
|
|
|
reader.releaseLock();
|
|
|
if (project_id) {
|
|
|
ctx.waitUntil(cleanupProject(session_id, project_id));
|
|
|
}
|
|
|
}
|
|
|
|
|
|
const fullResponse = chunks.join("");
|
|
|
for (const line of fullResponse.split("\n")) {
|
|
|
if (line.startsWith("data: ")) {
|
|
|
try {
|
|
|
const content = JSON.parse(line.slice(6));
|
|
|
if (model === "deepl") {
|
|
|
|
|
|
if (content.field_name === "content" && content.field_value) {
|
|
|
const result = JSON.stringify({
|
|
|
code: 200,
|
|
|
data: {
|
|
|
translations: [
|
|
|
{
|
|
|
detected_source_language:
|
|
|
content.session_state?.detected_source_lang || "",
|
|
|
text: content.field_value,
|
|
|
},
|
|
|
],
|
|
|
},
|
|
|
msg: "ok",
|
|
|
});
|
|
|
|
|
|
if (!CACHE_CONFIG.EXCLUDED_MODELS.includes(model)) {
|
|
|
messageCache.set(cacheKey, result);
|
|
|
}
|
|
|
return new Response(result, {
|
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
|
});
|
|
|
}
|
|
|
} else {
|
|
|
|
|
|
if (
|
|
|
content.field_value &&
|
|
|
content.field_name !== "session_state.answer_is_finished" &&
|
|
|
content.field_name !== "content" &&
|
|
|
content.field_name !== "session_state" &&
|
|
|
!content.delta &&
|
|
|
content.type !== "project_field"
|
|
|
) {
|
|
|
const result = JSON.stringify({
|
|
|
id: `chatcmpl-${messageId}`,
|
|
|
object: "chat.completion",
|
|
|
created: Math.floor(Date.now() / 1000),
|
|
|
model,
|
|
|
choices: [
|
|
|
{
|
|
|
index: 0,
|
|
|
message: {
|
|
|
role: "assistant",
|
|
|
content: content.field_value,
|
|
|
},
|
|
|
finish_reason: "stop",
|
|
|
},
|
|
|
],
|
|
|
usage: {
|
|
|
prompt_tokens: 0,
|
|
|
completion_tokens: 0,
|
|
|
total_tokens: content.field_value.length,
|
|
|
},
|
|
|
});
|
|
|
|
|
|
if (!CACHE_CONFIG.EXCLUDED_MODELS.includes(model)) {
|
|
|
messageCache.set(cacheKey, result);
|
|
|
}
|
|
|
return new Response(result, {
|
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
} catch (e) {}
|
|
|
}
|
|
|
}
|
|
|
return createErrorResponse("无效的响应数据");
|
|
|
} catch (error) {
|
|
|
console.error("Request processing error:", error);
|
|
|
return createErrorResponse("请求处理失败");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
if (
|
|
|
url.pathname === "/v1/images/generations" &&
|
|
|
request.method === "POST"
|
|
|
) {
|
|
|
try {
|
|
|
const requestData = await request.json();
|
|
|
|
|
|
if (!requestData.prompt && !requestData.messages) {
|
|
|
return new Response(
|
|
|
JSON.stringify({ error: "Prompt is required" }),
|
|
|
{
|
|
|
status: 400,
|
|
|
headers: { ...corsHeaders, "Content-Type": "application/json" },
|
|
|
}
|
|
|
);
|
|
|
}
|
|
|
|
|
|
const authHeader = request.headers.get("authorization");
|
|
|
if (sessionPool.size() === 0) {
|
|
|
const sessionString = authHeader?.replace("Bearer ", "");
|
|
|
sessionPool.initialize(sessionString);
|
|
|
if (sessionPool.size() === 0) {
|
|
|
return createErrorResponse("未提供有效的 session_id", 401);
|
|
|
}
|
|
|
}
|
|
|
const session_id = sessionPool.getNext();
|
|
|
if (!session_id) {
|
|
|
return createErrorResponse("无可用的会话ID", 500);
|
|
|
}
|
|
|
|
|
|
const {
|
|
|
response_format = "url",
|
|
|
num_images = 1,
|
|
|
size = "1024x1024",
|
|
|
quality = "standard",
|
|
|
style = "auto",
|
|
|
image,
|
|
|
model = "dall-e-3",
|
|
|
aspect_ratio=auto,
|
|
|
hd=true,
|
|
|
reflection_enabled=false,
|
|
|
} = requestData;
|
|
|
|
|
|
let messages = [];
|
|
|
|
|
|
if (requestData.messages) {
|
|
|
messages = requestData.messages;
|
|
|
|
|
|
await processMessages(messages, env);
|
|
|
} else {
|
|
|
|
|
|
if (image) {
|
|
|
|
|
|
let base64ImageData = image;
|
|
|
if (!base64ImageData.startsWith('data:image')) {
|
|
|
|
|
|
base64ImageData = `data:image/jpeg;base64,${image}`;
|
|
|
}
|
|
|
messages.push({
|
|
|
role: 'user',
|
|
|
content: [
|
|
|
{ type: 'image_url', image_url: { url: base64ImageData } },
|
|
|
{ type: 'text', text: requestData.prompt },
|
|
|
],
|
|
|
});
|
|
|
} else {
|
|
|
|
|
|
messages.push({ role: 'user', content: requestData.prompt });
|
|
|
}
|
|
|
}
|
|
|
|
|
|
await processMessages(messages, env);
|
|
|
const user_s_input = extractLastUserInput(messages);
|
|
|
|
|
|
const requestBody = {
|
|
|
type: "COPILOT_MOA_IMAGE",
|
|
|
current_query_string: "type=COPILOT_MOA_IMAGE",
|
|
|
messages: messages,
|
|
|
user_s_input: user_s_input,
|
|
|
action_params: {},
|
|
|
extra_data: {
|
|
|
model_configs: [],
|
|
|
llm_model: "gpt-4o",
|
|
|
imageModelMap: {},
|
|
|
writingContent: null,
|
|
|
},
|
|
|
};
|
|
|
for (let i = 0; i < num_images; i++) {
|
|
|
requestBody.extra_data.model_configs.push({
|
|
|
model: model === "dall-e-3" ? "dalle-3" : model,
|
|
|
aspect_ratio,
|
|
|
use_personalized_models:false,
|
|
|
fashion_profile_id:null,
|
|
|
hd,
|
|
|
reflection_enabled,
|
|
|
style: style
|
|
|
});
|
|
|
}
|
|
|
|
|
|
const response = await fetch(
|
|
|
"https://www.genspark.ai/api/copilot/ask",
|
|
|
{
|
|
|
method: "POST",
|
|
|
headers: {
|
|
|
Cookie: `session_id=${session_id}`,
|
|
|
"User-Agent": "Apifox/1.0.0 (https://apifox.com)",
|
|
|
"Content-Type": "application/json",
|
|
|
Accept: "*/*",
|
|
|
Host: "www.genspark.ai",
|
|
|
Connection: "keep-alive",
|
|
|
},
|
|
|
body: JSON.stringify(requestBody),
|
|
|
}
|
|
|
);
|
|
|
if (!response.ok) {
|
|
|
return createErrorResponse("上游服务请求失败", response.status);
|
|
|
}
|
|
|
|
|
|
const text = await response.text();
|
|
|
const taskIds = [];
|
|
|
const lines = text.split("\n");
|
|
|
for (const line of lines) {
|
|
|
if (line.includes("task_id")) {
|
|
|
try {
|
|
|
const content = line.replace("data: ", "");
|
|
|
if (!content) continue;
|
|
|
const { content: innerContent } = JSON.parse(content);
|
|
|
if (!innerContent) continue;
|
|
|
const { generated_images } = JSON.parse(innerContent);
|
|
|
if (!generated_images) continue;
|
|
|
for (const img of generated_images) {
|
|
|
if (img.task_id) {
|
|
|
taskIds.push(img.task_id);
|
|
|
}
|
|
|
}
|
|
|
} catch (e) {}
|
|
|
}
|
|
|
}
|
|
|
if (!taskIds.length) {
|
|
|
return createErrorResponse("获取任务ID失败");
|
|
|
}
|
|
|
|
|
|
const maxRetries = 300;
|
|
|
const retryInterval = 2000;
|
|
|
|
|
|
const taskPromises = taskIds.map((taskId) =>
|
|
|
pollTaskId(taskId, session_id, maxRetries, retryInterval)
|
|
|
);
|
|
|
let imageUrls = [];
|
|
|
try {
|
|
|
const results = await Promise.all(taskPromises);
|
|
|
results.forEach((urls) => {
|
|
|
imageUrls.push(...urls);
|
|
|
});
|
|
|
} catch (error) {
|
|
|
console.error("Polling error:", error);
|
|
|
return createErrorResponse(error.message);
|
|
|
}
|
|
|
|
|
|
if (response_format === "b64_json") {
|
|
|
|
|
|
const imageDatas = await Promise.all(
|
|
|
imageUrls.map(async (url) => {
|
|
|
const cachedData = await env.kv.get(url);
|
|
|
if (cachedData) {
|
|
|
return { b64_json: cachedData };
|
|
|
} else {
|
|
|
const imageResponse = await fetch(url);
|
|
|
const imageArrayBuffer = await imageResponse.arrayBuffer();
|
|
|
const base64Data = arrayBufferToBase64(imageArrayBuffer);
|
|
|
|
|
|
await env.kv.put(url, base64Data, { expirationTtl: CACHE_CONFIG.TTL });
|
|
|
return { b64_json: base64Data };
|
|
|
}
|
|
|
})
|
|
|
);
|
|
|
|
|
|
return new Response(
|
|
|
JSON.stringify({
|
|
|
created: Date.now(),
|
|
|
data: imageDatas,
|
|
|
}),
|
|
|
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
|
|
);
|
|
|
} else {
|
|
|
|
|
|
return new Response(
|
|
|
JSON.stringify({
|
|
|
created: Date.now(),
|
|
|
data: imageUrls.map((url) => ({
|
|
|
url,
|
|
|
revised_prompt: requestData.prompt,
|
|
|
})),
|
|
|
}),
|
|
|
{ headers: { ...corsHeaders, "Content-Type": "application/json" } }
|
|
|
);
|
|
|
}
|
|
|
} catch (error) {
|
|
|
console.error("Request processing error:", error);
|
|
|
return createErrorResponse("请求处理失败");
|
|
|
}
|
|
|
}
|
|
|
|
|
|
|
|
|
return new Response("Not Found", {
|
|
|
status: 404,
|
|
|
headers: corsHeaders,
|
|
|
});
|
|
|
},
|
|
|
}; |