【第二弹】用不完,根本用不完,部署cf worker无限免费绘画,可Api,支持多模型切换

【第一弹】用不完,根本用不完,部署cf worker无限免费绘画,可Api,支持多模型切换 - 常规话题 / 人工智能 - LINUX DO

支持/v1/chat/completions,/v1/models接口

前提得有一个cloudflare账号,申请账号token,这一步可以百度或者
复制以下代码,部署到cloudflare,记得把account_id,token改为你自己的

部署到CF的worker.js中

9月7日更新
1、更换新图床(需自行申请:https://sm.ms)有更好的图床可留言
2、增加提示词翻译、优化启用控制(—ntl关闭,—tl开启)


//本项目授权api_key,防止被恶意调用
const API_KEY = "sk-1234567890";
//https://sm.ms 图床key,可自行申请,为空则返回base64编码后的图片
const SMMS_API_KEY = '';
//cloudflare账号列表,每次请求都会随机从列表里取一个账号
const CF_ACCOUNT_LIST = [
  { account_id: "xxxx", token: "xxxx" }
];
//在你输入的prompt中添加 ---ntl可强制禁止提示词翻译、优化功能
//在你输入的prompt中添加 ---tl可强制开启提示词翻译、优化功能
//是否开启提示词翻译、优化功能
const CF_IS_TRANSLATE = true;
//示词翻译、优化模型
const CF_TRANSLATE_MODEL = "@cf/qwen/qwen1.5-14b-chat-awq";
//模型映射,设置客户端可用的模型。one-api,new-api在添加渠道时可使用“获取模型列表”功能,一键添加模型
const CUSTOMER_MODEL_MAP = {
  "dreamshaper-8": "@cf/lykon/dreamshaper-8-lcm",
  "stable-diffusion-xl-base-cf": "@cf/stabilityai/stable-diffusion-xl-base-1.0",
  "stable-diffusion-xl-lightning-cf": "@cf/bytedance/stable-diffusion-xl-lightning"
};

async function handleRequest(request) {
  try {
    if (request.method === "OPTIONS") {
      return new Response("", {
        status: 204,
        headers: {
          'Access-Control-Allow-Origin': '*',
          "Access-Control-Allow-Headers": '*'
        }
      });
    }

    const authHeader = request.headers.get("Authorization");
    if (!authHeader || !authHeader.startsWith("Bearer ") || authHeader.split(" ")[1] !== API_KEY) {
      return new Response("Unauthorized", { status: 401 });
    }

    if (request.url.endsWith("/v1/models")) {
      const arrs = [];
      Object.keys(CUSTOMER_MODEL_MAP).map(element => arrs.push({ id: element, object: "model" }))
      const response = {
        data: arrs,
        success: true
      };
      return new Response(JSON.stringify(response), {
        headers: {
          'Content-Type': 'application/json',
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Headers': '*'
        }
      });
    }

    if (request.method !== "POST") {
      return new Response("Only POST requests are allowed", {
        status: 405,
        headers: {
          'Access-Control-Allow-Origin': '*',
          "Access-Control-Allow-Headers": '*'
        }
      });
    }

    if (!request.url.endsWith("/v1/chat/completions")) {
      return new Response("Not Found", {
        status: 404,
        headers: {
          'Access-Control-Allow-Origin': '*',
          "Access-Control-Allow-Headers": '*'
        }
      });
    }

    const data = await request.json();
    const messages = data.messages || [];
    const model = CUSTOMER_MODEL_MAP[data.model] || CUSTOMER_MODEL_MAP["stable-diffusion-xl-lightning"];
    const stream = data.stream || false;
    const userMessage = messages.reverse().find((msg) => msg.role === "user")?.content;
    if (!userMessage) {
      return new Response(JSON.stringify({ error: "未找到用户消息" }), {
        status: 400,
        headers: {
          'Content-Type': 'application/json',
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Headers': '*'
        }
      });
    }

    const is_translate = extractTranslate(userMessage);
    const originalPrompt = cleanPromptString(userMessage);
    const translatedPrompt = is_translate ? await getPrompt(originalPrompt) : originalPrompt;
    const imageUrl = await generateImageByText(model, translatedPrompt);

    if (stream) {
      return handleStreamResponse(originalPrompt, translatedPrompt, "1024x1024", model, imageUrl);
    } else {
      return handleNonStreamResponse(originalPrompt, translatedPrompt, "1024x1024", model, imageUrl);
    }
  } catch (error) {
    return new Response("Internal Server Error: " + error.message, {
      status: 500,
      headers: {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Headers': '*'
      }
    });
  }
}

async function getPrompt(prompt) {
  const requestBodyJson = {
    messages: [
      {
        role: "system",
        content: `作为 Stable Diffusion Prompt 提示词专家,您将从关键词中创建提示,通常来自 Danbooru 等数据库。

        提示通常描述图像,使用常见词汇,按重要性排列,并用逗号分隔。避免使用"-"或".",但可以接受空格和自然语言。避免词汇重复。

        为了强调关键词,请将其放在括号中以增加其权重。例如,"(flowers)"将'flowers'的权重增加1.1倍,而"(((flowers)))"将其增加1.331倍。使用"(flowers:1.5)"将'flowers'的权重增加1.5倍。只为重要的标签增加权重。

        提示包括三个部分:**前缀**(质量标签+风格词+效果器)+ **主题**(图像的主要焦点)+ **场景**(背景、环境)。

        *   前缀影响图像质量。像"masterpiece"、"best quality"、"4k"这样的标签可以提高图像的细节。像"illustration"、"lensflare"这样的风格词定义图像的风格。像"bestlighting"、"lensflare"、"depthoffield"这样的效果器会影响光照和深度。

        *   主题是图像的主要焦点,如角色或场景。对主题进行详细描述可以确保图像丰富而详细。增加主题的权重以增强其清晰度。对于角色,描述面部、头发、身体、服装、姿势等特征。

        *   场景描述环境。没有场景,图像的背景是平淡的,主题显得过大。某些主题本身包含场景(例如建筑物、风景)。像"花草草地"、"阳光"、"河流"这样的环境词可以丰富场景。你的任务是设计图像生成的提示。请按照以下步骤进行操作:

        1.  我会发送给您一个图像场景。需要你生成详细的图像描述
        2.  图像描述必须是英文,输出为Positive Prompt。

        示例:

        我发送:二战时期的护士。
        您回复只回复:
        A WWII-era nurse in a German uniform, holding a wine bottle and stethoscope, sitting at a table in white attire, with a table in the background, masterpiece, best quality, 4k, illustration style, best lighting, depth of field, detailed character, detailed environment.`
      },
      {
        role: "user",
        content: prompt
      }
    ]
  };

  const response = await postRequest(CF_TRANSLATE_MODEL, requestBodyJson);

  if (!response.ok) {
    return prompt;
  }

  const jsonResponse = await response.json();
  const res = jsonResponse.result.response;
  return res;
}

async function generateImageByText(model, prompt) {
  try {
    const jsonBody = { prompt: prompt, num_steps: 20, guidance: 7.5, strength: 1, width: 1024, height: 1024 };
    const response = await postRequest(model, jsonBody);
    if (SMMS_API_KEY) {
      const imageUrl = await uploadImage(response);
      return imageUrl;
    }
    else {
      const arrayBuffer = await response.arrayBuffer();
      const base64Image = arrayBufferToBase64(arrayBuffer);
      return `data:image/webp;base64,${base64Image}`;
    }
  }
  catch (error) {
    return "图像生成或转换失败,请检查!" + error.message;
  }
}

function arrayBufferToBase64(buffer) {
  let binary = '';
  const bytes = new Uint8Array(buffer);
  const len = bytes.byteLength;
  for (let i = 0; i < len; i++) {
    binary += String.fromCharCode(bytes[i]);
  }
  return btoa(binary);
}


async function uploadImage(response) {
  const imageBlob = await response.blob();
  const formData = new FormData();
  formData.append("smfile", imageBlob, "image.jpg");
  const uploadResponse = await fetch("https://sm.ms/api/v2/upload", {
    method: 'POST',
    headers: {
      'Authorization': `${SMMS_API_KEY}`,
    },
    body: formData,
  });

  if (!uploadResponse.ok) {
    throw new Error("Failed to upload image");
  }

  const uploadResult = await uploadResponse.json();
  //const imageUrl = uploadResult[0].src;
  const imageUrl = uploadResult.data.url;
  return imageUrl;
}

function handleStreamResponse(originalPrompt, translatedPrompt, size, model, imageUrl) {
  const uniqueId = `chatcmpl-${Date.now()}`;
  const createdTimestamp = Math.floor(Date.now() / 1000);
  const systemFingerprint = "fp_" + Math.random().toString(36).substr(2, 9);
  const content = `🎨 原始提示词:${originalPrompt}\n` +
    `🌐 翻译后的提示词:${translatedPrompt}\n` +
    `📐 图像规格:${size}\n` +
    `🌟 图像生成成功!\n` +
    `以下是结果:\n\n` +
    `![生成的图像](${imageUrl})`;

  const responsePayload = {
    id: uniqueId,
    object: "chat.completion.chunk",
    created: createdTimestamp,
    model: model,
    system_fingerprint: systemFingerprint,
    choices: [
      {
        index: 0,
        delta: {
          content: content,
        },
        finish_reason: "stop",
      },
    ],
  };

  const dataString = JSON.stringify(responsePayload);

  return new Response(`data: ${dataString}\n\n`, {
    status: 200,
    headers: {
      "Content-Type": "text/event-stream",
      'Access-Control-Allow-Origin': '*',
      "Access-Control-Allow-Headers": '*',
    },
  });
}

function handleNonStreamResponse(originalPrompt, translatedPrompt, size, model, imageUrl) {
  const uniqueId = `chatcmpl-${Date.now()}`;
  const createdTimestamp = Math.floor(Date.now() / 1000);
  const systemFingerprint = "fp_" + Math.random().toString(36).substr(2, 9);
  const content = `🎨 原始提示词:${originalPrompt}\n` +
    `🌐 翻译后的提示词:${translatedPrompt}\n` +
    `📐 图像规格:${size}\n` +
    `🌟 图像生成成功!\n` +
    `以下是结果:\n\n` +
    `![生成的图像](${imageUrl})`;

  const response = {
    id: uniqueId,
    object: "chat.completion",
    created: createdTimestamp,
    model: model,
    system_fingerprint: systemFingerprint,
    choices: [{
      index: 0,
      message: {
        role: "assistant",
        content: content
      },
      finish_reason: "stop"
    }],
    usage: {
      prompt_tokens: translatedPrompt.length,
      completion_tokens: content.length,
      total_tokens: translatedPrompt.length + content.length
    }
  };

  const dataString = JSON.stringify(response);

  return new Response(dataString, {
    status: 200,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*',
      'Access-Control-Allow-Headers': '*'
    }
  });
}

async function postRequest(model, jsonBody) {
  const cf_account = CF_ACCOUNT_LIST[Math.floor(Math.random() * CF_ACCOUNT_LIST.length)];
  const apiUrl = `https://api.cloudflare.com/client/v4/accounts/${cf_account.account_id}/ai/run/${model}`;
  const response = await fetch(apiUrl, {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${cf_account.token}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify(jsonBody)
  });

  if (!response.ok) {
    throw new Error('Unexpected response ' + response.status);
  }
  return response;
}

function extractTranslate(prompt) {
  const match = prompt.match(/---n?tl/);
  if (match && match[0]) {
    if (match[0] == "---ntl") {
      return false;
    }
    else if (match[0] == "---tl") {
      return true;
    }
  }
  return CF_IS_TRANSLATE;
}

function cleanPromptString(prompt) {
  return prompt.replace(/---n?tl/, "").trim();
}

addEventListener('fetch', event => {
  event.respondWith(handleRequest(event.request));
});
48 个赞

太强了大佬!感谢喂饭

3 个赞

谢谢大佬的分享!

2 个赞

大佬这个好用啊

2 个赞

大佬,填写在new-key,调用的时候提示500

太强啦大佬 :upside_down_face:

感谢分享这个好东西

记一下,回头试试看~~


获取模型列表失败

dreamshaper-8手动填入new-api的model,请求500

1 个赞

感谢分享 点赞收藏

试试怎么用,

From 人工智能 to 资源荟萃

佬,这是需要申请什么类型的令牌呀

收藏了 過幾天學習一波

马克一下,有空试试

泰裤辣,感谢分享

他刚出的时候我给他用来沉浸式翻译,发现不行 :joy:

1 个赞

网络问题,国内访问不了这个域名*.workers.dev需要在设置-触发器里绑定自己的域名,或者使用梯子

佬,sm.ms的key是必须的吗,留空返回失败了
image

1 个赞

需要申请一个