Integrate Cloudflare Workers AI text-to-image into SD extension (#5434)

* feat: integrate Cloudflare Workers AI for text-to-image generation in SD extension

Agent-Logs-Url: https://github.com/SillyTavern/SillyTavern/sessions/efc79e4d-2119-4cdb-8afb-f26e318a38ef

Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com>

* fix: address review - use oai_settings for account ID, sort dropdown alphabetically, remove Account ID input, move debug log

Agent-Logs-Url: https://github.com/SillyTavern/SillyTavern/sessions/bf0dda38-df40-44f4-8a63-0c952b48905d

Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com>

* Clean-up diffs

* feat: add refresh models button to Workers AI section

Agent-Logs-Url: https://github.com/SillyTavern/SillyTavern/sessions/ab6b5e7a-84d2-44d1-9f6e-3d330de04ef1

Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com>

* fix: revert unrelated package-lock.json changes

Agent-Logs-Url: https://github.com/SillyTavern/SillyTavern/sessions/ab6b5e7a-84d2-44d1-9f6e-3d330de04ef1

Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com>

* Fix models loading

* refactor: update model refresh button ID and add class to select elements

* Send formData to BFL models

* fix: adjust use FormData condition

* fix: validate Workers AI account ID before proceeding with image model loading

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Cohee1207 <18619528+Cohee1207@users.noreply.github.com>
This commit is contained in:
Copilot
2026-04-15 22:00:08 +03:00
committed by GitHub
parent 64c96e895c
commit 78628f7dbb
4 changed files with 252 additions and 21 deletions
@@ -99,6 +99,7 @@ const sources = {
google: 'google',
zai: 'zai',
openrouter: 'openrouter',
workersai: 'workersai',
};
const comfyTypes = {
standard: 'standard',
@@ -1747,6 +1748,9 @@ async function loadSamplers() {
case sources.openrouter:
samplers = ['N/A'];
break;
case sources.workersai:
samplers = ['N/A'];
break;
}
for (const sampler of samplers) {
@@ -1997,6 +2001,9 @@ async function loadModels() {
case sources.openrouter:
models = await loadOpenRouterModels();
break;
case sources.workersai:
models = await loadWorkersAIImageModels();
break;
}
if (extension_settings.sd.source === sources.electronhub) {
@@ -2124,6 +2131,33 @@ async function loadXAIModels() {
];
}
async function loadWorkersAIImageModels() {
$('#sd_cf_workers_key').toggleClass('success', !!secret_state[SECRET_KEYS.WORKERS_AI]);
if (!secret_state[SECRET_KEYS.WORKERS_AI]) {
return [];
}
if (!oai_settings.workers_ai_account_id) {
toastr.warning('Workers AI account ID is required. Save it in the "API Connections" panel.', 'Image Generation');
return [];
}
const result = await fetch('/api/sd/workersai/models', {
method: 'POST',
headers: getRequestHeaders(),
body: JSON.stringify({
account_id: oai_settings.workers_ai_account_id,
}),
});
if (result.ok) {
return await result.json();
}
return [];
}
async function loadPollinationsModels() {
$('#sd_pollinations_key').toggleClass('success', !!secret_state[SECRET_KEYS.POLLINATIONS]);
@@ -2609,6 +2643,9 @@ async function loadSchedulers() {
case sources.openrouter:
schedulers = ['N/A'];
break;
case sources.workersai:
schedulers = ['N/A'];
break;
}
for (const scheduler of schedulers) {
@@ -2729,6 +2766,9 @@ async function loadVaes() {
case sources.openrouter:
vaes = ['N/A'];
break;
case sources.workersai:
vaes = ['N/A'];
break;
}
for (const vae of vaes) {
@@ -3432,6 +3472,9 @@ async function sendGenerationRequest(generationType, prompt, additionalNegativeP
case sources.openrouter:
result = await generateOpenRouterImage(prefixedPrompt, signal);
break;
case sources.workersai:
result = await generateWorkersAIImage(prefixedPrompt, negativePrompt, signal);
break;
}
if (!result.data) {
@@ -4748,6 +4791,33 @@ async function generateOpenRouterImage(prompt, signal) {
throw new Error(text);
}
async function generateWorkersAIImage(prompt, negativePrompt, signal) {
const result = await fetch('/api/sd/workersai/generate', {
method: 'POST',
headers: getRequestHeaders(),
signal: signal,
body: JSON.stringify({
prompt: prompt,
negative_prompt: negativePrompt,
model: extension_settings.sd.model,
width: extension_settings.sd.width,
height: extension_settings.sd.height,
steps: extension_settings.sd.steps,
scale: extension_settings.sd.scale,
seed: extension_settings.sd.seed >= 0 ? extension_settings.sd.seed : undefined,
account_id: oai_settings.workers_ai_account_id,
}),
});
if (result.ok) {
const data = await result.json();
return { format: data?.format, data: data?.image };
} else {
const text = await result.text();
throw new Error(text);
}
}
async function onComfyOpenWorkflowEditorClick() {
let workflow = await (await fetch('/api/sd/comfy/workflow', {
method: 'POST',
@@ -5120,6 +5190,8 @@ function isValidState() {
return secret_state[SECRET_KEYS.ZAI];
case sources.openrouter:
return secret_state[SECRET_KEYS.OPENROUTER];
case sources.workersai:
return !!oai_settings.workers_ai_account_id && secret_state[SECRET_KEYS.WORKERS_AI];
default:
return false;
}
@@ -5879,6 +5951,9 @@ export async function init() {
extension_settings.sd.google_duration = Number($(this).val());
saveSettingsDebounced();
});
$('#sd_models_refresh').on('click', async () => {
await loadModels();
});
$('#sd_electronhub_quality').on('change', function () {
extension_settings.sd.electronhub_quality = String($(this).val());
saveSettingsDebounced();
@@ -5922,6 +5997,7 @@ export async function init() {
[sources.aimlapi]: SECRET_KEYS.AIMLAPI,
[sources.comfy]: SECRET_KEYS.COMFY_RUNPOD,
[sources.pollinations]: SECRET_KEYS.POLLINATIONS,
[sources.workersai]: SECRET_KEYS.WORKERS_AI,
};
const shouldReloadOptions = Object.entries(keySourceMap).some(([k, v]) => k === extension_settings.sd.source && v === key);
if (!shouldReloadOptions) {