test2 / index.html
Shanny1126's picture
Update index.html
0b8724c verified
Raw History Blame Contribute Delete
11.1 kB
<!DOCTYPE html>
<html lang="zh-Hant">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Gemma 多模態影像分析助手</title>
<style>
:root {
--bg-color: #fff5f8;
--card-bg: rgba(255, 255, 255, 0.9);
--border-glow: #ffb6c1;
--neon-cyan: #ff69b4;
--neon-purple: #ba55d3;
--text-main: #4a4046;
--text-muted: #8c7b82;
}
body {
font-family: "Comic Sans MS", "Chalkboard SE", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
max-width: 650px;
margin: 40px auto;
padding: 20px;
background-color: var(--bg-color);
background-image:
radial-gradient(circle at 15% 15%, rgba(255, 182, 193, 0.4) 0%, transparent 35%),
radial-gradient(circle at 85% 85%, rgba(221, 160, 221, 0.4) 0%, transparent 35%);
color: var(--text-main);
min-height: 100vh;
}
.card {
background: var(--card-bg);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
padding: 35px;
border-radius: 28px;
border: 2px dashed #ffb6c1;
box-shadow: 0 10px 30px rgba(255, 182, 193, 0.3), inset 0 0 15px rgba(255, 255, 255, 0.8);
position: relative;
overflow: hidden;
}
.card::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 3px;
background: linear-gradient(90deg, transparent, #ff69b4, #ba55d3, transparent);
animation: scanline 4s linear infinite;
}
@keyframes scanline {
0% { left: -100%; }
100% { left: 100%; }
}
h2 {
margin-top: 0;
font-size: 1.8rem;
background: linear-gradient(135deg, #ff69b4, #ba55d3);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
letter-spacing: 0.5px;
text-align: center;
}
p {
color: var(--text-muted);
font-size: 0.95rem;
margin-bottom: 24px;
text-align: center;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
font-weight: 700;
margin-bottom: 8px;
font-size: 0.95rem;
color: #ff1493;
letter-spacing: 0.5px;
}
input[type="password"],
input[type="text"],
input[type="file"],
textarea {
width: 100%;
padding: 14px 16px;
background: rgba(255, 255, 255, 0.8);
border: 2px solid #ffc0cb;
border-radius: 16px;
box-sizing: border-box;
color: var(--text-main);
font-size: 0.95rem;
transition: all 0.3s ease;
box-shadow: inset 0 2px 4px rgba(0,0,0,0.02);
}
input:focus {
outline: none;
border-color: #ff69b4;
box-shadow: 0 0 12px rgba(255, 105, 180, 0.4);
background: #fff;
}
input[type="file"]::file-selector-button {
background: #ffe4e1;
color: #db7093;
border: 1px solid #ffb6c1;
padding: 8px 14px;
border-radius: 10px;
cursor: pointer;
transition: all 0.2s;
font-weight: bold;
}
input[type="file"]::file-selector-button:hover {
background: #ff69b4;
color: #fff;
}
button {
background: linear-gradient(135deg, #ff69b4, #dda0dd);
color: #fff;
border: none;
padding: 14px 20px;
font-weight: 700;
border-radius: 16px;
cursor: pointer;
width: 100%;
font-size: 1.05rem;
letter-spacing: 1px;
transition: all 0.3s ease;
box-shadow: 0 6px 20px rgba(255, 105, 180, 0.4);
}
button:hover {
opacity: 0.95;
transform: translateY(-2px) scale(1.01);
box-shadow: 0 8px 25px rgba(186, 85, 211, 0.5);
}
button:disabled {
background: #e0e0e0;
color: #a0a0a0;
cursor: not-allowed;
box-shadow: none;
transform: none;
}
#preview-container {
margin-top: 12px;
text-align: center;
}
#imagePreview {
max-width: 100%;
max-height: 250px;
border-radius: 16px;
display: none;
border: 2px solid #ffb6c1;
box-shadow: 0 4px 12px rgba(255, 182, 193, 0.5);
}
#result {
margin-top: 12px;
padding: 18px;
background: rgba(255, 250, 250, 0.9);
border: 2px dashed #ffb6c1;
border-radius: 16px;
white-space: pre-wrap;
min-height: 60px;
font-size: 0.95rem;
color: #55444e;
box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.03);
}
</style>
</head>
<body>
<div class="card">
<h2>Gemma 多模態影像分析助手</h2>
<p>透過 Hugging Face Router 呼叫 gemma-4-31B-it 模型進行高精度影像辨識與分析。</p>
<div class="form-group">
<label for="apiKey">Hugging Face Token (HF_TOKEN)</label>
<input type="password" id="apiKey" placeholder="請輸入您的 HF_TOKEN">
</div>
<div class="form-group">
<label for="imageInput">上傳圖片</label>
<input type="file" id="imageInput" accept="image/*" onchange="previewSelectedImage(event)">
<div id="preview-container">
<img id="imagePreview" alt="圖片預覽">
</div>
</div>
<div class="form-group">
<label for="promptInput">輸入提示詞 (Prompt)</label>
<input type="text" id="promptInput" value="Describe this image in one sentence in chinese">
</div>
<button id="submitBtn" onclick="analyzeImage()">啟動分析</button>
<div class="form-group" style="margin-top: 24px;">
<label>模型回應</label>
<div id="result">系統待命,尚未開始分析...</div>
</div>
</div>
<script>
function previewSelectedImage(event) {
const file = event.target.files[0];
const preview = document.getElementById('imagePreview');
if (file) {
const reader = new FileReader();
reader.onload = function (e) {
preview.src = e.target.result;
preview.style.display = 'inline-block';
}
reader.readAsDataURL(file);
} else {
preview.src = '';
preview.style.display = 'none';
}
}
function compressImage(file, maxSize = 1024, quality = 0.85) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = function (e) {
const img = new Image();
img.onload = function () {
let width = img.width;
let height = img.height;
if (width > height) {
if (width > maxSize) {
height *= maxSize / width;
width = maxSize;
}
} else {
if (height > maxSize) {
width *= maxSize / height;
height = maxSize;
}
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, width, height);
const dataUrl = canvas.toDataURL('image/jpeg', quality);
resolve(dataUrl);
};
img.src = e.target.result;
};
reader.onerror = reject;
reader.readAsDataURL(file);
});
}
async function analyzeImage() {
const apiKey = document.getElementById('apiKey').value.trim();
const imageFile = document.getElementById('imageInput').files[0];
let prompt = document.getElementById('promptInput').value.trim();
const resultDiv = document.getElementById('result');
const submitBtn = document.getElementById('submitBtn');
if (!apiKey) {
alert('請先輸入 Hugging Face Token!');
return;
}
if (!imageFile) {
alert('請先上傳一張圖片!');
return;
}
if (!prompt) {
prompt = "Describe this image in one sentence.";
}
submitBtn.disabled = true;
resultDiv.innerText = '處理中,神經網路運算中...';
try {
const base64Image = await compressImage(imageFile);
const response = await fetch("https://router.huggingface.co/v1/chat/completions", {
method: "POST",
headers: {
"Authorization": `Bearer ${apiKey}`,
"Content-Type": "application/json"
},
body: JSON.stringify({
model: "google/gemma-4-31B-it:novita",
messages: [{
role: "user",
content: [
{ type: "text", text: prompt },
{ type: "image_url", image_url: { url: base64Image } }
]
}],
max_tokens: 300
})
});
const data = await response.json();
if (response.ok && data.choices && data.choices.length > 0) {
resultDiv.innerText = data.choices[0].message.content;
} else {
resultDiv.innerText = `發生錯誤:${data.error?.message || JSON.stringify(data)}`;
}
} catch (error) {
resultDiv.innerText = `發生錯誤:${error.message}`;
} finally {
submitBtn.disabled = false;
}
}
</script>
</body>
</html>