感谢大佬的喂饭教程https://linux.do/t/topic/184550
1、在原代码基础上加了翻译接口,使用openai类型的ai翻译,可以直接中文输入提示词生成图片
2、保留了10张历史记录图片
在原变量后新增加翻译模型变量
TS_URL:openai格式,eg:https://api.siliconflow.cn/v1/chat/completions
TS_KEY:对应API KEY
TS_MODEL:自选模型,我用的 siliconflow模型deepseek-ai/DeepSeek-V2-Chat
代码如下:
// Function to generate a secure random token
function generateToken() {
return crypto.randomUUID();
}
// Function to verify Turnstile token
async function verifyTurnstileToken(token) {
const formData = new FormData();
formData.append('secret', TURNSTILE_SECRET_KEY);
formData.append('response', token);
const url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify';
const result = await fetch(url, {
body: formData,
method: 'POST',
});
const outcome = await result.json();
return outcome.success;
}
// Function to set a secure cookie
function setCookie(name, value, maxAge = 3600) {
return `${name}=${value}; HttpOnly; Secure; SameSite=Strict; Max-Age=${maxAge}; Path=/`;
}
// Function to get cookie value
function getCookie(request, name) {
const cookieString = request.headers.get('Cookie') || '';
const cookies = cookieString.split(';').map(cookie => cookie.trim());
const targetCookie = cookies.find(cookie => cookie.startsWith(`${name}=`));
return targetCookie ? targetCookie.split('=')[1] : null;
}
// Middleware to check authentication
async function checkAuth(request) {
const sessionToken = getCookie(request, 'session');
if (!sessionToken) return false;
try {
const [username, expirationTime] = atob(sessionToken).split(':');
if (Date.now() > parseInt(expirationTime)) return false;
return username === AUTH_USERNAME;
} catch {
return false;
}
}
// Handle login request
async function handleLogin(request) {
if (request.method !== 'POST') {
return new Response('Method Not Allowed', { status: 405 });
}
const formData = await request.formData();
const username = formData.get('username');
const password = formData.get('password');
const turnstileResponse = formData.get('cf-turnstile-response');
if (!await verifyTurnstileToken(turnstileResponse)) {
return new Response('Turnstile verification failed', { status: 400 });
}
if (username === AUTH_USERNAME && password === AUTH_PASSWORD) {
const expirationTime = Date.now() + 3600000; // 1 hour from now
const sessionToken = btoa(`${username}:${expirationTime}`);
const headers = new Headers();
headers.append('Set-Cookie', setCookie('session', sessionToken));
headers.append('Location', '/');
return new Response(null, { status: 302, headers });
} else {
return new Response('Invalid credentials', { status: 401 });
}
}
// Handle logout request
function handleLogout() {
const headers = new Headers();
headers.append('Set-Cookie', setCookie('session', '', 0)); // Expire the cookie
headers.append('Location', '/login');
return new Response(null, { status: 302, headers });
}
// 语言检测函数
function detectLanguage(text) {
// 简单的语言检测:检查是否包含中文字符
const chineseRegex = /[\u4e00-\u9fa5]/;
return chineseRegex.test(text) ? 'zh' : 'en';
}
async function translatePrompt(prompt) {
const detectedLanguage = detectLanguage(prompt);
if (detectedLanguage === 'en') {
console.log('Input is already in English, skipping translation');
return prompt;
}
try {
console.log('Translating from Chinese to English');
const response = await fetch(TS_URL, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${TS_KEY}`
},
body: JSON.stringify({
model: TS_MODEL,
messages: [
{ "role": "system", "content": "Translate the following Chinese text to English." },
{ "role": "user", "content": prompt }
]
})
});
if (!response.ok) {
throw new Error(`API Error: ${response.statusText}`);
}
const result = await response.json();
const translatedText = result.choices[0].message.content.trim();
return translatedText;
} catch (error) {
console.error('Translation Error:', error);
throw new Error('Failed to translate the prompt.');
}
}
// Generate image
async function generateImage(request) {
try {
const { prompt, width, height, num_inference_steps } = await request.json();
console.log('Original prompt:', prompt);
const translatedPrompt = await translatePrompt(prompt);
console.log('Prompt after translation check:', translatedPrompt);
const response = await fetch(API_URL, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify({ prompt: translatedPrompt, width, height, num_inference_steps })
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(`API request failed: ${errorData.error || response.statusText}`);
}
const result = await response.json();
// 添加翻译信息到结果中
result.wasTranslated = (prompt !== translatedPrompt);
result.originalPrompt = prompt;
result.translatedPrompt = translatedPrompt;
return new Response(JSON.stringify(result), {
status: 200,
headers: { 'Content-Type': 'application/json' }
});
} catch (error) {
console.error('Error in generateImage:', error);
return new Response(JSON.stringify({ error: error.message }), {
status: 500,
headers: { 'Content-Type': 'application/json' }
});
}
}
// Get login page HTML
function getLoginHTML() {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login - Flux.1-schnell Generator</title>
<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<style>
body {
font-family: 'Roboto', sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f4f4f4;
}
form {
background-color: white;
padding: 2rem;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
width: 100%;
max-width: 400px;
}
h2 {
margin-top: 0;
color: #333;
text-align: center;
}
input {
display: block;
width: 100%;
padding: 0.75rem;
margin: 1rem 0;
border: 1px solid #ddd;
border-radius: 4px;
box-sizing: border-box;
font-size: 1rem;
}
button {
width: 100%;
padding: 0.75rem;
background-color: #3498db;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: background-color 0.3s ease;
}
button:hover {
background-color: #2980b9;
}
</style>
</head>
<body>
<form method="POST" action="/login">
<h2>Login</h2>
<input type="text" name="username" placeholder="Username" required>
<input type="password" name="password" placeholder="Password" required>
<div class="cf-turnstile" data-sitekey="${TURNSTILE_SITE_KEY}"></div>
<button type="submit">Login</button>
</form>
</body>
</html>
`;
}
// Get main page HTML
function getMainHTML() {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Flux.1-schnell Generator</title>
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;700&display=swap" rel="stylesheet">
<style>
:root {
--background-color: #f4f4f4;
--card-background: #ffffff;
--text-color: #333333;
--primary-color: #3498db;
--primary-hover: #2980b9;
--error-color: #e74c3c;
}
body {
font-family: 'Roboto', sans-serif;
line-height: 1.6;
color: var(--text-color);
background-color: var(--background-color);
margin: 0;
padding: 0;
min-height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.container {
width: 100%;
max-width: 800px;
margin: 2rem auto;
padding: 2rem;
background-color: var(--card-background);
border-radius: 10px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
h1 {
color: var(--text-color);
text-align: center;
margin-bottom: 1.5rem;
}
.image-container {
margin-bottom: 2rem;
text-align: center;
background-color: var(--background-color);
padding: 2rem;
border-radius: 8px;
min-height: 300px;
display: flex;
align-items: center;
justify-content: center;
}
.image-container img {
max-width: 100%;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.form-group {
margin-bottom: 1.5rem;
}
label {
display: block;
margin-bottom: 0.5rem;
font-weight: bold;
}
input[type="text"] {
width: 100%;
padding: 0.8rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.image-ratio-group {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 1rem;
margin-top: 0.5rem;
}
.image-ratio-option input[type="radio"] {
display: none;
}
.image-ratio-option label {
display: block;
padding: 0.5rem;
text-align: center;
background-color: var(--background-color);
border: 2px solid #ddd;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
}
.image-ratio-option input[type="radio"]:checked + label {
background-color: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
.generate-btn {
display: block;
width: 100%;
padding: 1rem;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: 4px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.3s ease;
}
.generate-btn:hover {
background-color: var(--primary-hover);
}
.generate-btn:disabled {
background-color: #bdc3c7;
cursor: not-allowed;
}
.logout-btn {
display: inline-block;
margin-top: 2rem;
padding: 0.8rem 1.5rem;
background-color: var(--error-color);
color: white;
text-decoration: none;
border-radius: 4px;
transition: background-color 0.3s ease;
}
.logout-btn:hover {
background-color: #c0392b;
}
#error-message {
color: var(--error-color);
text-align: center;
margin-top: 1rem;
}
.loading {
font-style: italic;
color: #7f8c8d;
}
.history-container {
margin-top: 2rem;
}
.history-container h2 {
text-align: center;
margin-bottom: 1rem;
}
#image-history {
display: flex;
flex-wrap: wrap;
gap: 1rem;
justify-content: center;
}
.history-image {
width: 200px;
height: 200px;
object-fit: cover;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
cursor: pointer;
transition: transform 0.3s ease;
}
.history-image:hover {
transform: scale(1.05);
}
#translation-info {
margin-top: 0.5rem;
font-size: 0.9em;
color: #666;
}
</style>
</head>
<body>
<div class="container">
<h1>Flux.1-schnell Generator</h1>
<div class="image-container" id="image-container">
<p>Your generated image will appear here</p>
</div>
<form id="image-form">
<div class="form-group">
<label for="prompt">Describe your image:</label>
<input type="text" id="prompt" name="prompt" placeholder="Enter your image description" required>
<div id="translation-info"></div>
</div>
<div class="form-group">
<label>Select image ratio:</label>
<div class="image-ratio-group">
<div class="image-ratio-option">
<input type="radio" id="ratio-1-1" name="image_ratio" value="1:1" checked>
<label for="ratio-1-1">1:1</label>
</div>
<div class="image-ratio-option">
<input type="radio" id="ratio-1-2" name="image_ratio" value="1:2">
<label for="ratio-1-2">1:2</label>
</div>
<div class="image-ratio-option">
<input type="radio" id="ratio-3-2" name="image_ratio" value="3:2">
<label for="ratio-3-2">3:2</label>
</div>
<div class="image-ratio-option">
<input type="radio" id="ratio-3-4" name="image_ratio" value="3:4">
<label for="ratio-3-4">3:4</label>
</div>
<div class="image-ratio-option">
<input type="radio" id="ratio-16-9" name="image_ratio" value="16:9">
<label for="ratio-16-9">16:9</label>
</div>
<div class="image-ratio-option">
<input type="radio" id="ratio-9-16" name="image_ratio" value="9:16">
<label for="ratio-9-16">9:16</label>
</div>
</div>
</div>
<button type="submit" class="generate-btn">Generate Image</button>
</form>
<div class="history-container">
<h2>Generated Images History</h2>
<div id="image-history"></div>
</div>
<div id="error-message"></div>
<a href="/logout" class="logout-btn">Logout</a>
</div>
<script>
document.getElementById('image-form').addEventListener('submit', async function(event) {
event.preventDefault();
const prompt = document.getElementById('prompt').value;
const imageRatio = document.querySelector('input[name="image_ratio"]:checked').value;
const imageContainer = document.getElementById('image-container');
const errorMessage = document.getElementById('error-message');
const generateButton = document.querySelector('.generate-btn');
const translationInfo = document.getElementById('translation-info');
const numInferenceSteps = 20;
updateUI('generating');
const { width, height } = getImageDimensions(imageRatio);
try {
const response = await fetch('/generate-image', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
prompt,
width,
height,
num_inference_steps: numInferenceSteps
})
});
if (!response.ok) {
throw new Error('Failed to generate image. Please try again.');
}
const data = await response.json();
if (data.images && data.images.length > 0) {
const imageUrl = data.images[0].url;
imageContainer.innerHTML = '<img src="' + imageUrl + '" alt="Generated Image">';
addImageToHistory(imageUrl);
// 添加这段代码来更新翻译信息
const translationInfo = document.getElementById('translation-info');
if (data.wasTranslated) {
translationInfo.textContent = 'Original: "' + data.originalPrompt + '" | Translated: "' + data.translatedPrompt + '"';
} else {
translationInfo.textContent = 'No translation needed';
}
} else {
throw new Error('Failed to generate image. Please try again.');
}
} catch (error) {
console.error('Error:', error);
errorMessage.textContent = error.message;
} finally {
updateUI('idle');
}
});
function getImageDimensions(ratio) {
const dimensions = {
'1:1': { width: 1024, height: 1024 },
'1:2': { width: 512, height: 1024 },
'3:2': { width: 768, height: 512 },
'3:4': { width: 768, height: 1024 },
'16:9': { width: 1024, height: 576 },
'9:16': { width: 576, height: 1024 }
};
return dimensions[ratio] || dimensions['1:1'];
}
function updateUI(state) {
const imageContainer = document.getElementById('image-container');
const errorMessage = document.getElementById('error-message');
const generateButton = document.querySelector('.generate-btn');
if (state === 'generating') {
imageContainer.innerHTML = '<p class="loading">Translating prompt and generating your image... This may take a moment.</p>';
errorMessage.textContent = '';
generateButton.disabled = true;
generateButton.textContent = 'Generating...';
} else {
generateButton.disabled = false;
generateButton.textContent = 'Generate Image';
}
}
// 在这里添加新的 JavaScript 代码
let imageHistory = [];
function addImageToHistory(imageUrl) {
imageHistory.unshift(imageUrl);
if (imageHistory.length > 10) {
imageHistory.pop();
}
updateImageHistory();
saveImageHistory();
}
function updateImageHistory() {
const historyContainer = document.getElementById('image-history');
historyContainer.innerHTML = '';
imageHistory.forEach(url => {
const img = document.createElement('img');
img.src = url;
img.alt = 'Generated Image';
img.className = 'history-image';
img.onclick = () => {
document.getElementById('image-container').innerHTML = '<img src="' + url + '" alt="Generated Image">';
};
historyContainer.appendChild(img);
});
}
function saveImageHistory() {
localStorage.setItem('imageHistory', JSON.stringify(imageHistory));
}
function loadImageHistory() {
const savedHistory = localStorage.getItem('imageHistory');
if (savedHistory) {
imageHistory = JSON.parse(savedHistory);
updateImageHistory();
}
}
// 在页面加载时调用
loadImageHistory();
</script>
</body>
</html>
`;
}
// Main request handler
async function handleRequest(request) {
const url = new URL(request.url);
// Login route
if (url.pathname === '/login') {
if (request.method === 'GET') {
return new Response(getLoginHTML(), {
headers: { 'Content-Type': 'text/html' },
});
} else if (request.method === 'POST') {
return handleLogin(request);
}
}
// Logout route
if (url.pathname === '/logout') {
return handleLogout();
}
// Check authentication for all other routes
const isAuthenticated = await checkAuth(request);
if (!isAuthenticated) {
const headers = new Headers();
headers.append('Location', '/login');
return new Response(null, { status: 302, headers });
}
// Generate image route
if (request.method === 'POST' && url.pathname === '/generate-image') {
return generateImage(request);
}
// Main page route
if (request.method === 'GET' && url.pathname === '/') {
return new Response(getMainHTML(), {
headers: { 'Content-Type': 'text/html' },
});
}
// 404 for all other routes
return new Response('Not Found', { status: 404 });
}
// Event listener
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});