Create worker.js

This commit is contained in:
programmerWsy 2025-01-11 12:05:59 +08:00 committed by GitHub
parent df609cce2f
commit 62054add3b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

772
worker.js Normal file
View File

@ -0,0 +1,772 @@
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request));
});
async function handleRequest(request) {
const url = new URL(request.url);
// 处理 CORS 预检请求
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
},
});
}
// 处理 POST 请求
if (request.method === 'POST' && url.pathname === '/recognize') {
try {
const { token, imageId } = await request.json();
if (!token || !imageId) {
return new Response(JSON.stringify({ error: 'Missing token or imageId' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
// 调用 QwenLM API
const response = await fetch('https://chat.qwenlm.ai/api/chat/completions', {
method: 'POST',
headers: {
'accept': '*/*',
'authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
stream: false,
model: 'qwen-vl-max-latest',
messages: [
{
role: 'user',
content: [
{ type: 'text', text: '请严格只返回图片中的内容,不要添加任何解释、描述或多余的文字' },
{ type: 'image', image: imageId }, // 使用上传后的图片 ID
],
},
],
session_id: '1',
chat_id: '2',
id: '3',
}),
});
const data = await response.json();
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
} catch (error) {
return new Response(JSON.stringify({ error: 'Internal Server Error' }), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}
// 返回前端界面
return new Response(getHTML(), {
headers: { 'Content-Type': 'text/html' },
});
}
function getHTML() {
return `
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>智能图片识别</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.container {
background: rgba(255, 255, 255, 0.95);
padding: 2.5rem;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
width: 90%;
max-width: 800px;
transition: all 0.3s ease;
}
h1 {
color: #2c3e50;
margin-bottom: 1.5rem;
font-size: 1.8rem;
text-align: center;
}
.upload-area {
border: 2px dashed #8e9eab;
border-radius: 12px;
padding: 2rem;
text-align: center;
transition: all 0.3s ease;
margin-bottom: 1.5rem;
cursor: pointer;
position: relative;
overflow: hidden;
}
.upload-area:hover {
border-color: #3498db;
background: rgba(52, 152, 219, 0.05);
}
.upload-area.dragover {
border-color: #3498db;
background: rgba(52, 152, 219, 0.1);
transform: scale(1.02);
}
.upload-area i {
font-size: 2rem;
color: #8e9eab;
margin-bottom: 1rem;
}
.upload-text {
color: #7f8c8d;
font-size: 0.9rem;
}
#tokens {
width: 100%;
padding: 0.8rem;
border: 1px solid #dcdde1;
border-radius: 8px;
margin-bottom: 1rem;
font-size: 0.9rem;
resize: none;
}
.result-container {
margin-top: 1.5rem;
opacity: 0;
transform: translateY(20px);
transition: all 0.3s ease;
}
.result-container.show {
opacity: 1;
transform: translateY(0);
}
.result {
background: #f8f9fa;
padding: 1.2rem;
border-radius: 8px;
color: #2c3e50;
font-size: 1rem;
line-height: 1.6;
white-space: pre-wrap;
}
.loading {
display: none;
text-align: center;
margin: 1rem 0;
}
.loading::after {
content: '';
display: inline-block;
width: 20px;
height: 20px;
border: 2px solid #3498db;
border-radius: 50%;
border-top-color: transparent;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.preview-image {
max-width: 100%;
max-height: 200px;
margin: 1rem 0;
border-radius: 8px;
display: none;
}
/* 侧边栏样式 */
.sidebar {
position: fixed;
right: -300px;
top: 0;
width: 300px;
height: 100vh;
background: white;
box-shadow: -5px 0 15px rgba(0, 0, 0, 0.1);
transition: right 0.3s ease;
padding: 20px;
z-index: 1000;
}
.sidebar.open {
right: 0;
}
.sidebar-toggle {
position: fixed;
right: 20px;
top: 20px;
background: #3498db;
color: white;
border: none;
padding: 10px 15px;
border-radius: 5px;
cursor: pointer;
z-index: 1001;
}
.token-list {
margin-top: 20px;
}
.token-item {
background: #f8f9fa;
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
cursor: pointer;
word-break: break-all;
}
.token-item:hover {
background: #e9ecef;
}
#tokenInput {
width: 100%;
padding: 10px;
margin-bottom: 10px;
border: 1px solid #dcdde1;
border-radius: 5px;
}
.save-btn {
background: #3498db;
color: white;
border: none;
padding: 10px 15px;
border-radius: 5px;
cursor: pointer;
width: 100%;
}
/* 历史记录样式 */
.history-container {
margin-top: 2rem;
border-top: 1px solid #eee;
padding-top: 1rem;
}
.history-title {
color: #2c3e50;
font-size: 1.2rem;
margin-bottom: 1rem;
}
.history-item {
display: flex;
align-items: flex-start;
padding: 1rem;
background: #f8f9fa;
border-radius: 8px;
margin-bottom: 1rem;
}
.history-image {
width: 100px;
height: 100px;
object-fit: cover;
border-radius: 4px;
margin-right: 1rem;
}
.history-content {
flex: 1;
}
.history-text {
color: #2c3e50;
font-size: 0.9rem;
line-height: 1.4;
}
.history-time {
color: #7f8c8d;
font-size: 0.8rem;
margin-top: 0.5rem;
}
.no-history {
text-align: center;
color: #7f8c8d;
padding: 1rem;
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.9);
z-index: 2000;
cursor: pointer;
}
.modal-content {
max-width: 90%;
max-height: 90vh;
margin: auto;
display: block;
position: relative;
top: 50%;
transform: translateY(-50%);
}
/* 修改侧边栏样式 */
.sidebar {
position: fixed;
right: -300px; /* 改为右侧边栏 */
top: 0;
width: 300px;
height: 100vh;
background: white;
box-shadow: -5px 0 15px rgba(0, 0, 0, 0.1);
transition: right 0.3s ease;
padding: 20px;
z-index: 1000;
}
/* 添加左侧边栏样式 */
.history-sidebar {
position: fixed;
left: -300px;
top: 0;
width: 300px;
height: 100vh;
background: white;
box-shadow: 5px 0 15px rgba(0, 0, 0, 0.1);
transition: left 0.3s ease;
padding: 20px;
z-index: 1000;
overflow-y: auto;
}
.history-sidebar.open {
left: 0;
}
.history-toggle {
position: fixed;
left: 20px;
top: 20px;
background: #3498db;
color: white;
border: none;
padding: 10px 15px;
border-radius: 5px;
cursor: pointer;
z-index: 1001;
}
/* 添加复制按钮样式 */
.result-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.copy-btn {
background: #3498db;
color: white;
border: none;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
transition: background 0.3s ease;
}
.copy-btn:hover {
background: #2980b9;
}
.copy-btn.copied {
background: #27ae60;
}
</style>
</head>
<body>
<button class="sidebar-toggle" id="sidebarToggle"> Token设置</button>
<div class="sidebar" id="sidebar">
<h2>Token 管理</h2>
<textarea id="tokenInput" placeholder="输入Token多个Token请用英文逗号分隔" rows="4"></textarea>
<button class="save-btn" id="saveTokens">保存</button>
<div class="token-list" id="tokenList"></div>
</div>
<div class="container">
<h1>智能图片识别</h1>
<div class="upload-area" id="uploadArea">
<i>📸</i>
<div class="upload-text">
拖拽图片到这里或点击上传<br>
支持复制粘贴图片
</div>
<img id="previewImage" class="preview-image">
</div>
<div class="loading" id="loading"></div>
<div class="result-container" id="resultContainer">
<div class="result-header">
<span>识别结果</span>
<button class="copy-btn" id="copyBtn">复制结果</button>
</div>
<div class="result" id="result"></div>
</div>
<button class="history-toggle" id="historyToggle">📋 识别历史</button>
<div class="history-sidebar" id="historySidebar">
<h2>识别历史</h2>
<div id="historyList"></div>
</div>
</div>
<div id="imageModal" class="modal">
<img class="modal-content" id="modalImage">
</div>
<script>
// 首先定义类
function HistoryManager() {
this.maxHistory = 10;
}
// 添加原型方法
HistoryManager.prototype.getHistoryKey = function(token) {
return 'imageRecognition_history_' + token;
};
HistoryManager.prototype.loadHistory = function(token) {
const history = localStorage.getItem(this.getHistoryKey(token));
return history ? JSON.parse(history) : [];
};
HistoryManager.prototype.saveHistory = function(token, history) {
localStorage.setItem(this.getHistoryKey(token), JSON.stringify(history));
};
HistoryManager.prototype.addHistory = function(token, imageData, result) {
const history = this.loadHistory(token);
const newRecord = {
image: imageData,
result: result,
timestamp: new Date().toISOString()
};
history.unshift(newRecord);
if (history.length > this.maxHistory) {
history.pop();
}
this.saveHistory(token, history);
this.displayHistory(token);
};
HistoryManager.prototype.displayHistory = function(token) {
const history = this.loadHistory(token);
if (history.length === 0) {
historyList.innerHTML = '<div class="no-history">暂无识别历史</div>';
return;
}
var html = '';
for (var i = 0; i < history.length; i++) {
var record = history[i];
html += '<div class="history-item">';
html += '<img src="' + record.image + '" class="history-image" alt="历史图片" onclick="showFullImage(this.src)">';
html += '<div class="history-content">';
html += '<div class="history-text">' + record.result + '</div>';
html += '<div class="history-time">' + new Date(record.timestamp).toLocaleString() + '</div>';
html += '</div></div>';
}
historyList.innerHTML = html;
};
// 在 HistoryManager 类定义后添加 TokenManager 类
function TokenManager() {
this.currentIndex = 0;
}
TokenManager.prototype.getNextToken = function(tokens) {
if (!tokens || tokens.length === 0) return null;
// 轮询获取下一个token
const token = tokens[this.currentIndex];
this.currentIndex = (this.currentIndex + 1) % tokens.length;
return token;
};
// 初始化变量
const uploadArea = document.getElementById('uploadArea');
const tokensInput = document.getElementById('tokenInput');
const resultDiv = document.getElementById('result');
const resultContainer = document.getElementById('resultContainer');
const loading = document.getElementById('loading');
const previewImage = document.getElementById('previewImage');
const historyList = document.getElementById('historyList');
const sidebar = document.getElementById('sidebar');
const sidebarToggle = document.getElementById('sidebarToggle');
const tokenInput = document.getElementById('tokenInput');
const saveTokensBtn = document.getElementById('saveTokens');
const tokenList = document.getElementById('tokenList');
const historySidebar = document.getElementById('historySidebar');
const historyToggle = document.getElementById('historyToggle');
let currentToken = '';
let tokens = [];
const historyManager = new HistoryManager();
const tokenManager = new TokenManager();
// 从localStorage加载保存的tokens
function loadTokens() {
const savedTokens = localStorage.getItem('imageRecognitionTokens');
if (savedTokens) {
tokens = savedTokens.split(',');
updateTokenList();
if (tokens.length > 0) {
currentToken = tokens[0];
}
}
}
// 修改 updateTokenList 函数
function updateTokenList() {
tokenList.innerHTML = '';
tokens.forEach(function(token, index) {
var truncatedToken = token.slice(0, 10) + '...' + token.slice(-10);
var div = document.createElement('div');
div.className = 'token-item';
div.textContent = 'Token ' + (index + 1) + ': ' + truncatedToken;
div.addEventListener('click', function() {
currentToken = token;
historyManager.displayHistory(currentToken);
});
tokenList.appendChild(div);
});
tokenInput.value = tokens.join(',');
}
// 保存tokens
saveTokensBtn.addEventListener('click', () => {
const inputTokens = tokenInput.value.split(',').map(t => t.trim()).filter(t => t);
if (inputTokens.length > 0) {
tokens = inputTokens;
localStorage.setItem('imageRecognitionTokens', tokens.join(','));
updateTokenList();
currentToken = tokens[0];
alert('Tokens已保存');
} else {
alert('请至少输入一个有效的Token');
}
});
// 侧边栏开关
sidebarToggle.addEventListener('click', () => {
sidebar.classList.toggle('open');
});
// 处理文件上传和识别
async function processImage(file) {
// 使用 TokenManager 获取下一个可用的 token
const nextToken = tokenManager.getNextToken(tokens);
if (!nextToken) {
alert('请先设置Token');
sidebar.classList.add('open');
return;
}
currentToken = nextToken;
// 显示图片预览
const reader = new FileReader();
let imageData;
reader.onload = (e) => {
imageData = e.target.result;
previewImage.src = imageData;
previewImage.style.display = 'block';
};
reader.readAsDataURL(file);
// 显示加载动画
loading.style.display = 'block';
resultContainer.classList.remove('show');
try {
// 上传文件
const formData = new FormData();
formData.append('file', file);
const uploadResponse = await fetch('https://chat.qwenlm.ai/api/v1/files/', {
method: 'POST',
headers: {
'accept': 'application/json',
'authorization': 'Bearer ' + currentToken,
},
body: formData,
});
const uploadData = await uploadResponse.json();
if (!uploadData.id) throw new Error('文件上传失败');
// 识别图片
const recognizeResponse = await fetch('/recognize', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: currentToken,
imageId: uploadData.id
}),
});
const recognizeData = await recognizeResponse.json();
// 提取并显示识别结果
const result = recognizeData.choices[0]?.message?.content || '识别失败';
resultDiv.textContent = result;
resultContainer.classList.add('show');
copyBtn.textContent = '复制结果';
copyBtn.classList.remove('copied');
// 添加到历史记录
historyManager.addHistory(currentToken, imageData, result);
} catch (error) {
resultDiv.textContent = '处理失败: ' + error.message;
resultContainer.classList.add('show');
copyBtn.textContent = '复制结果';
copyBtn.classList.remove('copied');
} finally {
loading.style.display = 'none';
}
}
// 文件拖放处理
uploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
uploadArea.classList.add('dragover');
});
uploadArea.addEventListener('dragleave', () => {
uploadArea.classList.remove('dragover');
});
uploadArea.addEventListener('drop', (e) => {
e.preventDefault();
uploadArea.classList.remove('dragover');
const file = e.dataTransfer.files[0];
if (file && file.type.startsWith('image/')) {
processImage(file);
}
});
// 点击上传
uploadArea.addEventListener('click', () => {
const input = document.createElement('input');
input.type = 'file';
input.accept = 'image/*';
input.onchange = (e) => {
const file = e.target.files[0];
if (file) processImage(file);
};
input.click();
});
// 粘贴处理
document.addEventListener('paste', (e) => {
const file = e.clipboardData.files[0];
if (file && file.type.startsWith('image/')) {
processImage(file);
}
});
// 初始化
loadTokens();
if (currentToken) {
historyManager.displayHistory(currentToken);
}
const modal = document.getElementById('imageModal');
const modalImg = document.getElementById('modalImage');
function showFullImage(src) {
modal.style.display = "block";
modalImg.src = src;
}
// 点击模态框关闭
modal.onclick = function() {
modal.style.display = "none";
}
// ESC 键关闭模态框
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape' && modal.style.display === 'block') {
modal.style.display = 'none';
}
});
// 左侧历史记录边栏开关
historyToggle.addEventListener('click', () => {
historySidebar.classList.toggle('open');
});
const copyBtn = document.getElementById('copyBtn');
// 复制结果功能
copyBtn.addEventListener('click', async () => {
const result = resultDiv.textContent;
try {
await navigator.clipboard.writeText(result);
copyBtn.textContent = '已复制';
copyBtn.classList.add('copied');
setTimeout(() => {
copyBtn.textContent = '复制结果';
copyBtn.classList.remove('copied');
}, 2000);
} catch (err) {
console.error('复制失败:', err);
}
});
</script>
</body>
</html>
`;
}