Spaces:
Running
Running
<html lang="zh-CN"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>{% block title %}Gemini Balance{% endblock %}</title> | |
<link rel="manifest" href="/static/manifest.json" /> | |
<meta name="theme-color" content="#4F46E5" /> | |
<meta name="apple-mobile-web-app-capable" content="yes" /> | |
<meta name="apple-mobile-web-app-status-bar-style" content="black" /> | |
<meta name="apple-mobile-web-app-title" content="GBalance" /> | |
<link rel="icon" href="/static/icons/icon-192x192.png" /> | |
<link | |
href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" | |
rel="stylesheet" | |
/> | |
<link | |
rel="stylesheet" | |
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" | |
/> | |
<script src="https://cdn.tailwindcss.com"></script> | |
<script> | |
tailwind.config = { | |
theme: { | |
extend: { | |
colors: { | |
primary: { | |
50: "#eef2ff", | |
100: "#e0e7ff", | |
200: "#c7d2fe", | |
300: "#a5b4fc", | |
400: "#818cf8", | |
500: "#6366f1", | |
600: "#4f46e5", | |
700: "#4338ca", | |
800: "#3730a3", | |
900: "#312e81", | |
}, | |
success: { | |
50: "#ecfdf5", | |
500: "#10b981", | |
600: "#059669", | |
}, | |
danger: { | |
50: "#fef2f2", | |
500: "#ef4444", | |
600: "#dc2626", | |
}, | |
}, | |
fontFamily: { | |
sans: ["Inter", "sans-serif"], | |
mono: [ | |
"JetBrains Mono", | |
"SFMono-Regular", | |
"Menlo", | |
"Monaco", | |
"Consolas", | |
"monospace", | |
], | |
}, | |
animation: { | |
"fade-in": "fadeIn 0.5s ease-out", | |
"slide-up": "slideUp 0.5s ease-out", | |
"slide-down": "slideDown 0.5s ease-out", | |
shake: "shake 0.5s ease-in-out", | |
spin: "spin 1s linear infinite", | |
}, | |
keyframes: { | |
fadeIn: { | |
"0%": { opacity: "0" }, | |
"100%": { opacity: "1" }, | |
}, | |
slideUp: { | |
"0%": { transform: "translateY(20px)", opacity: "0" }, | |
"100%": { transform: "translateY(0)", opacity: "1" }, | |
}, | |
slideDown: { | |
"0%": { transform: "translateY(-20px)", opacity: "0" }, | |
"100%": { transform: "translateY(0)", opacity: "1" }, | |
}, | |
shake: { | |
"0%, 100%": { transform: "translateX(0)" }, | |
"25%": { transform: "translateX(-5px)" }, | |
"75%": { transform: "translateX(5px)" }, | |
}, | |
spin: { | |
"0%": { transform: "rotate(0deg)" }, | |
"100%": { transform: "rotate(360deg)" }, | |
}, | |
}, | |
}, | |
}, | |
}; | |
</script> | |
<style> | |
.glass-card { | |
background: rgba(255, 255, 255, 0.95); /* High opacity white for light theme */ | |
backdrop-filter: blur(16px); | |
-webkit-backdrop-filter: blur(16px); | |
border: 1px solid rgba(0, 0, 0, 0.08); /* Light gray border */ | |
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); | |
} | |
.bg-gradient { | |
background: #ffffff; /* Clean white background */ | |
} | |
/* Scrollbar styling */ | |
::-webkit-scrollbar { | |
width: 8px; | |
height: 8px; | |
} | |
::-webkit-scrollbar-track { | |
background: rgba(243, 244, 246, 0.8); /* bg-gray-100 with opacity */ | |
border-radius: 10px; | |
} | |
::-webkit-scrollbar-thumb { | |
background: rgba(107, 114, 128, 0.6); /* gray-500 for light theme */ | |
border-radius: 10px; | |
} | |
::-webkit-scrollbar-thumb:hover { | |
background: rgba(75, 85, 99, 0.8); /* gray-600 for light theme */ | |
} | |
/* Basic modal styles */ | |
.modal { | |
display: none; | |
position: fixed; | |
z-index: 50; | |
left: 0; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
background-color: rgba(0,0,0,0.5); | |
backdrop-filter: blur(4px); | |
} | |
.modal.show { | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
} | |
/* Global modal content styling for light theme consistency */ | |
.modal .w-full[style*="background-color: rgba(70, 50, 150"], | |
.modal .w-full[style*="background-color: rgba(80, 60, 160"] { | |
background-color: rgba(255, 255, 255, 0.98) ; | |
color: #374151 ; /* gray-700 */ | |
border: 1px solid rgba(0, 0, 0, 0.08) ; | |
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04) ; | |
} | |
/* Global modal text color fixes */ | |
.modal .text-gray-100, .modal h2.text-gray-100, .modal h3.text-gray-100 { | |
color: #1f2937 ; /* gray-800 */ | |
font-weight: 600 ; | |
} | |
.modal .text-gray-200, .modal .text-gray-300 { | |
color: #6b7280 ; /* gray-500 */ | |
} | |
.modal .text-gray-300:hover { | |
color: #374151 ; /* gray-700 */ | |
} | |
/* Global modal button styling */ | |
.modal .bg-violet-600, .modal button.bg-violet-600 { | |
background-color: #3b82f6 ; /* blue-500 - light blue */ | |
color: #ffffff ; | |
} | |
.modal .bg-violet-600:hover, .modal button.bg-violet-600:hover { | |
background-color: #2563eb ; /* blue-600 - darker light blue */ | |
} | |
/* Global modal blue button styling */ | |
.modal .bg-blue-500, .modal button.bg-blue-500, | |
.modal .bg-blue-600, .modal button.bg-blue-600, | |
.modal .bg-blue-700, .modal button.bg-blue-700 { | |
background-color: #3b82f6 ; /* blue-500 - light blue */ | |
color: #ffffff ; | |
} | |
.modal .bg-blue-500:hover, .modal button.bg-blue-500:hover, | |
.modal .bg-blue-600:hover, .modal button.bg-blue-600:hover, | |
.modal .bg-blue-700:hover, .modal button.bg-blue-700:hover { | |
background-color: #2563eb ; /* blue-600 - darker light blue */ | |
} | |
/* Global modal red button styling */ | |
.modal .bg-red-500, .modal button.bg-red-500, | |
.modal .bg-red-600, .modal button.bg-red-600, | |
.modal .bg-red-700, .modal button.bg-red-700 { | |
background-color: #f87171 ; /* red-400 - bright light red */ | |
color: #ffffff ; | |
} | |
.modal .bg-red-500:hover, .modal button.bg-red-500:hover, | |
.modal .bg-red-600:hover, .modal button.bg-red-600:hover, | |
.modal .bg-red-700:hover, .modal button.bg-red-700:hover { | |
background-color: #ef4444 ; /* red-500 - darker bright light red */ | |
} | |
/* Global modal gray button styling */ | |
.modal .bg-gray-500, .modal button.bg-gray-500, | |
.modal .bg-gray-600, .modal button.bg-gray-600, | |
.modal .bg-gray-700, .modal button.bg-gray-700 { | |
background-color: #e5e7eb ; /* gray-200 - light gray */ | |
color: #374151 ; /* gray-700 - dark text for contrast */ | |
} | |
.modal .bg-gray-500:hover, .modal button.bg-gray-500:hover, | |
.modal .bg-gray-600:hover, .modal button.bg-gray-600:hover, | |
.modal .bg-gray-700:hover, .modal button.bg-gray-700:hover { | |
background-color: #d1d5db ; /* gray-300 - darker light gray */ | |
color: #374151 ; /* gray-700 - dark text for contrast */ | |
} | |
/* Comprehensive button contrast fixes */ | |
/* Ensure all dark background buttons have white text */ | |
.bg-blue-500, .bg-blue-600, .bg-blue-700, .bg-blue-800, .bg-blue-900, | |
.bg-red-500, .bg-red-600, .bg-red-700, .bg-red-800, .bg-red-900, | |
.bg-green-500, .bg-green-600, .bg-green-700, .bg-green-800, .bg-green-900, | |
.bg-purple-500, .bg-purple-600, .bg-purple-700, .bg-purple-800, .bg-purple-900, | |
.bg-indigo-500, .bg-indigo-600, .bg-indigo-700, .bg-indigo-800, .bg-indigo-900, | |
.bg-violet-500, .bg-violet-600, .bg-violet-700, .bg-violet-800, .bg-violet-900, | |
.bg-sky-500, .bg-sky-600, .bg-sky-700, .bg-sky-800, .bg-sky-900, | |
.bg-teal-500, .bg-teal-600, .bg-teal-700, .bg-teal-800, .bg-teal-900, | |
.bg-gray-700, .bg-gray-800, .bg-gray-900, | |
.bg-slate-500, .bg-slate-600, .bg-slate-700, .bg-slate-800, .bg-slate-900 { | |
color: #ffffff ; | |
} | |
/* Ensure all light background buttons have dark text */ | |
.bg-gray-50, .bg-gray-100, .bg-gray-200, .bg-gray-300, | |
.bg-white, .bg-transparent { | |
color: #374151 ; /* gray-700 */ | |
} | |
/* Fix button children text inheritance */ | |
.bg-blue-500 *, .bg-blue-600 *, .bg-blue-700 *, .bg-blue-800 *, .bg-blue-900 *, | |
.bg-red-500 *, .bg-red-600 *, .bg-red-700 *, .bg-red-800 *, .bg-red-900 *, | |
.bg-green-500 *, .bg-green-600 *, .bg-green-700 *, .bg-green-800 *, .bg-green-900 *, | |
.bg-purple-500 *, .bg-purple-600 *, .bg-purple-700 *, .bg-purple-800 *, .bg-purple-900 *, | |
.bg-violet-500 *, .bg-violet-600 *, .bg-violet-700 *, .bg-violet-800 *, .bg-violet-900 *, | |
.bg-sky-500 *, .bg-sky-600 *, .bg-sky-700 *, .bg-sky-800 *, .bg-sky-900 *, | |
.bg-teal-500 *, .bg-teal-600 *, .bg-teal-700 *, .bg-teal-800 *, .bg-teal-900 *, | |
.bg-gray-700 *, .bg-gray-800 *, .bg-gray-900 *, | |
.bg-slate-500 *, .bg-slate-600 *, .bg-slate-700 *, .bg-slate-800 *, .bg-slate-900 * { | |
color: inherit ; | |
} | |
/* Global form element styling for consistency */ | |
select, input[type="text"], input[type="number"], input[type="search"], | |
input[type="email"], input[type="password"], input[type="datetime-local"], | |
textarea, .form-input, .form-select { | |
background-color: rgba(255, 255, 255, 0.95) ; | |
color: #374151 ; /* gray-700 */ | |
border: 1px solid rgba(0, 0, 0, 0.12) ; | |
border-radius: 0.375rem ; /* rounded-md */ | |
} | |
select:focus, input:focus, textarea:focus, | |
.form-input:focus, .form-select:focus { | |
border-color: #3b82f6 ; /* blue-500 */ | |
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1) ; | |
outline: none ; | |
} | |
/* Fix dropdown option styling */ | |
select option { | |
background-color: rgba(255, 255, 255, 0.98) ; | |
color: #374151 ; /* gray-700 */ | |
padding: 8px ; | |
} | |
/* Fix pagination controls globally */ | |
.pagination-button, .pagination a, .pagination button { | |
background-color: rgba(255, 255, 255, 0.9) ; | |
color: #374151 ; /* gray-700 */ | |
border: 1px solid rgba(0, 0, 0, 0.08) ; | |
transition: all 0.15s ease-in-out ; | |
} | |
.pagination-button:hover, .pagination a:hover, .pagination button:hover { | |
background-color: rgba(229, 231, 235, 1) ; /* gray-200 */ | |
border-color: rgba(0, 0, 0, 0.12) ; | |
transform: translateY(-1px) ; | |
} | |
.pagination-button.active, .pagination a.active, .pagination button.active { | |
background-color: #3b82f6 ; /* blue-500 - light blue */ | |
color: #ffffff ; | |
border-color: #2563eb ; /* blue-600 - darker light blue */ | |
font-weight: 600 ; | |
} | |
/* Loading spinner */ | |
.loading-spin { | |
animation: spin 1s linear infinite; | |
} | |
@keyframes spin { | |
from { transform: rotate(0deg); } | |
to { transform: rotate(360deg); } | |
} | |
/* Notification */ | |
.notification { | |
position: fixed; | |
bottom: 5rem; /* Adjusted from bottom-20 */ | |
left: 50%; | |
transform: translateX(-50%); | |
padding: 0.75rem 1.25rem; /* px-5 py-3 */ | |
border-radius: 0.5rem; /* rounded-lg */ | |
background-color: rgba(34, 197, 94, 0.95); /* green-500 for success */ | |
color: white; | |
font-weight: 500; /* font-medium */ | |
z-index: 1000; /* Increased z-index */ | |
opacity: 0; | |
transition: opacity 0.3s ease-in-out, transform 0.3s ease-in-out; | |
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); | |
} | |
.notification.show { | |
opacity: 1; | |
transform: translate(-50%, 0); | |
} | |
.notification.error { | |
background-color: rgba(239, 68, 68, 0.95); /* red-500 for error */ | |
} | |
/* Scroll buttons */ | |
.scroll-buttons { | |
position: fixed; | |
right: 1.25rem; /* right-5 */ | |
bottom: 5rem; /* bottom-20 */ | |
display: flex; | |
flex-direction: column; | |
gap: 0.5rem; /* gap-2 */ | |
z-index: 10; | |
} | |
.scroll-button { | |
width: 2.5rem; /* w-10 */ | |
height: 2.5rem; /* h-10 */ | |
background-color: #3b82f6; /* blue-500 - light blue */ | |
color: white; | |
border-radius: 9999px; /* rounded-full */ | |
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); /* shadow-md */ | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
transition: all 0.3s ease-in-out; | |
} | |
.scroll-button:hover { | |
background-color: #2563eb; /* blue-600 - darker light blue */ | |
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); /* hover:shadow-lg */ | |
} | |
/* Global overrides for light theme consistency */ | |
.text-gray-200, .text-gray-300, .text-gray-400 { | |
color: #6b7280 ; /* gray-500 for better contrast */ | |
} | |
/* Navigation and header improvements */ | |
.bg-primary-600, .bg-primary-700 { | |
background-color: #3b82f6 ; /* blue-500 - light blue */ | |
} | |
.text-primary-600, .text-primary-700 { | |
color: #3b82f6 ; /* blue-500 - light blue */ | |
} | |
.border-primary-500, .focus\\:border-primary-500 { | |
border-color: #3b82f6 ; /* blue-500 */ | |
} | |
.ring-primary-200, .focus\\:ring-primary-200 { | |
--tw-ring-color: rgba(59, 130, 246, 0.2) ; /* blue-500 with opacity */ | |
} | |
/* Global purple to blue conversion */ | |
.bg-violet-50, .bg-violet-100, .bg-violet-200, .bg-violet-300, .bg-violet-400, .bg-violet-500, .bg-violet-600, .bg-violet-700, .bg-violet-800, .bg-violet-900 { | |
background-color: #3b82f6 ; /* blue-500 - light blue */ | |
} | |
.text-violet-50, .text-violet-100, .text-violet-200, .text-violet-300, .text-violet-400, .text-violet-500, .text-violet-600, .text-violet-700, .text-violet-800, .text-violet-900 { | |
color: #3b82f6 ; /* blue-500 - light blue */ | |
} | |
.border-violet-50, .border-violet-100, .border-violet-200, .border-violet-300, .border-violet-400, .border-violet-500, .border-violet-600, .border-violet-700, .border-violet-800, .border-violet-900 { | |
border-color: #3b82f6 ; /* blue-500 - light blue */ | |
} | |
/* Global button color overrides */ | |
/* Blue buttons to light blue */ | |
.bg-blue-500, .bg-blue-600, .bg-blue-700, .bg-blue-800, .bg-blue-900, | |
button.bg-blue-500, button.bg-blue-600, button.bg-blue-700, button.bg-blue-800, button.bg-blue-900 { | |
background-color: #3b82f6 ; /* blue-500 - light blue */ | |
} | |
.bg-blue-500:hover, .bg-blue-600:hover, .bg-blue-700:hover, .bg-blue-800:hover, .bg-blue-900:hover, | |
button.bg-blue-500:hover, button.bg-blue-600:hover, button.bg-blue-700:hover, button.bg-blue-800:hover, button.bg-blue-900:hover, | |
.hover\\:bg-blue-600:hover, .hover\\:bg-blue-700:hover, .hover\\:bg-blue-800:hover { | |
background-color: #2563eb ; /* blue-600 - darker light blue */ | |
} | |
/* Red buttons to bright light red */ | |
.bg-red-500, .bg-red-600, .bg-red-700, .bg-red-800, .bg-red-900, | |
button.bg-red-500, button.bg-red-600, button.bg-red-700, button.bg-red-800, button.bg-red-900 { | |
background-color: #f87171 ; /* red-400 - bright light red */ | |
} | |
.bg-red-500:hover, .bg-red-600:hover, .bg-red-700:hover, .bg-red-800:hover, .bg-red-900:hover, | |
button.bg-red-500:hover, button.bg-red-600:hover, button.bg-red-700:hover, button.bg-red-800:hover, button.bg-red-900:hover, | |
.hover\\:bg-red-600:hover, .hover\\:bg-red-700:hover, .hover\\:bg-red-800:hover { | |
background-color: #ef4444 ; /* red-500 - darker bright light red */ | |
} | |
/* Gray buttons to light gray */ | |
.bg-gray-500, .bg-gray-600, .bg-gray-700, .bg-gray-800, .bg-gray-900, | |
button.bg-gray-500, button.bg-gray-600, button.bg-gray-700, button.bg-gray-800, button.bg-gray-900 { | |
background-color: #e5e7eb ; /* gray-200 - light gray */ | |
color: #374151 ; /* gray-700 - dark text for contrast */ | |
} | |
.bg-gray-500:hover, .bg-gray-600:hover, .bg-gray-700:hover, .bg-gray-800:hover, .bg-gray-900:hover, | |
button.bg-gray-500:hover, button.bg-gray-600:hover, button.bg-gray-700:hover, button.bg-gray-800:hover, button.bg-gray-900:hover, | |
.hover\\:bg-gray-600:hover, .hover\\:bg-gray-700:hover, .hover\\:bg-gray-800:hover { | |
background-color: #d1d5db ; /* gray-300 - darker light gray */ | |
color: #374151 ; /* gray-700 - dark text for contrast */ | |
} | |
/* Ensure all text has proper contrast in light theme */ | |
.text-white { | |
color: #374151 ; /* gray-700 for better contrast on light backgrounds */ | |
} | |
/* Fix dark button text - ensure white text on dark backgrounds */ | |
.bg-blue-500, .bg-blue-600, .bg-blue-700, .bg-blue-800, .bg-blue-900, | |
.bg-red-500, .bg-red-600, .bg-red-700, .bg-red-800, .bg-red-900, | |
.bg-green-500, .bg-green-600, .bg-green-700, .bg-green-800, .bg-green-900, | |
.bg-purple-500, .bg-purple-600, .bg-purple-700, .bg-purple-800, .bg-purple-900, | |
.bg-indigo-500, .bg-indigo-600, .bg-indigo-700, .bg-indigo-800, .bg-indigo-900, | |
.bg-gray-700, .bg-gray-800, .bg-gray-900, | |
.bg-sky-500, .bg-sky-600, .bg-sky-700, .bg-sky-800, .bg-sky-900 { | |
color: #ffffff ; | |
} | |
/* Ensure buttons with dark backgrounds have white text */ | |
button.bg-blue-500, button.bg-blue-600, button.bg-blue-700, | |
button.bg-red-500, button.bg-red-600, button.bg-red-700, | |
button.bg-green-500, button.bg-green-600, button.bg-green-700, | |
button.bg-sky-500, button.bg-sky-600, button.bg-sky-700, | |
.btn-primary, .btn-danger, .btn-success, .btn-info { | |
color: #ffffff ; | |
} | |
/* Override any nested text color rules for dark buttons */ | |
.bg-blue-500 *, .bg-blue-600 *, .bg-blue-700 *, | |
.bg-red-500 *, .bg-red-600 *, .bg-red-700 *, | |
.bg-green-500 *, .bg-green-600 *, .bg-green-700 *, | |
.bg-sky-500 *, .bg-sky-600 *, .bg-sky-700 * { | |
color: inherit ; | |
} | |
{% block head_extra_styles %} | |
{% endblock %} | |
</style> | |
{% block head_extra_scripts %}{% endblock %} | |
</head> | |
<body class="bg-white min-h-screen text-gray-900 pt-6 pb-16"> | |
{% block content %}{% endblock %} | |
<!-- 底部版权 --> | |
<div | |
class="fixed bottom-0 left-0 w-full py-3 bg-white bg-opacity-95 backdrop-blur-md text-sm text-gray-800 border-t border-gray-200 flex flex-col items-center space-y-1" | |
> | |
<!-- 第一行 --> | |
<div class="flex items-center justify-center space-x-2"> | |
<span>© <span id="copyright-year"></span> by</span> | |
<a | |
href="https://linux.do/u/snaily" | |
target="_blank" | |
class="text-primary-600 hover:text-primary-800 transition duration-300 flex items-center" | |
> | |
<img | |
src="https://linux.do/user_avatar/linux.do/snaily/288/306510_2.gif" | |
alt="snaily" | |
class="inline-block w-5 h-5 rounded-full align-middle mr-1" | |
/>snaily | |
</a> | |
<span class="text-gray-400">|</span> | |
<a | |
href="https://github.com/snailyp/gemini-balance" | |
target="_blank" | |
class="text-primary-600 hover:text-primary-800 transition duration-300 flex items-center" | |
> | |
<i class="fab fa-github mr-1"></i> GitHub | |
</a> | |
</div> | |
<!-- 第二行 --> | |
<div class="flex items-center justify-center space-x-2 text-xs"> | |
<a | |
href="https://gb-docs.snaily.top/guide/supportme.html" | |
target="_blank" | |
class="text-primary-600 hover:text-primary-800 transition duration-300 flex items-center" | |
> | |
<i class="fas fa-drumstick-bite text-yellow-600 mr-1"></i> 给作者加鸡腿 | |
</a> | |
<span class="text-gray-400">|</span> | |
<a | |
href="https://gb-docs.snaily.top" | |
target="_blank" | |
class="text-primary-600 hover:text-primary-800 transition duration-300 flex items-center" | |
> | |
<i class="fas fa-book mr-1"></i> 在线文档 | |
</a> | |
<span class="text-gray-400">|</span> | |
<a | |
href="https://t.me/+soaHax5lyI0wZDVl" | |
target="_blank" | |
class="text-primary-600 hover:text-primary-800 transition duration-300 flex items-center" | |
> | |
<i class="fab fa-telegram-plane mr-1"></i> 交流群 | |
</a> | |
<span class="text-gray-400">|</span> | |
<span class="text-yellow-600 font-semibold flex items-center"> | |
<i class="fas fa-exclamation-triangle mr-1"></i>免费项目,谨防诈骗 | |
</span> | |
<span id="version-info-container" class="inline-flex items-center"> | |
<!-- Version info will be loaded here by JavaScript --> | |
</span> | |
</div> | |
</div> | |
<!-- 通用JS --> | |
<script> | |
// 设置版权年份 | |
document.getElementById("copyright-year").textContent = | |
new Date().getFullYear(); | |
// 滚动到顶部/底部函数 (如果页面需要) | |
function scrollToTop() { | |
window.scrollTo({ top: 0, behavior: "smooth" }); | |
} | |
function scrollToBottom() { | |
window.scrollTo({ | |
top: document.body.scrollHeight, | |
behavior: "smooth", | |
}); | |
} | |
// 显示通知 | |
function showNotification(message, type = "success", duration = 3000) { | |
const notification = | |
document.getElementById("notification") || | |
createNotificationElement(); | |
if (!notification) return; | |
notification.textContent = message; | |
notification.className = "notification show"; // Reset classes | |
if (type === "error") { | |
notification.classList.add("error"); | |
} | |
// Clear previous timeout if exists | |
if (notification.timeoutId) { | |
clearTimeout(notification.timeoutId); | |
} | |
notification.timeoutId = setTimeout(() => { | |
notification.classList.remove("show"); | |
// Optional: remove the element after fade out if dynamically created | |
// setTimeout(() => notification.remove(), 300); | |
}, duration); | |
} | |
// Helper to create notification element if it doesn't exist | |
function createNotificationElement() { | |
let notification = document.getElementById("notification"); | |
if (!notification) { | |
notification = document.createElement("div"); | |
notification.id = "notification"; | |
notification.className = "notification"; | |
document.body.appendChild(notification); | |
} | |
return notification; | |
} | |
// 页面刷新带加载状态 | |
function refreshPage(button) { | |
if (button) { | |
const icon = button.querySelector("i"); | |
if (icon) { | |
icon.classList.add("loading-spin"); | |
} | |
} | |
setTimeout(() => { | |
window.location.reload(); | |
}, 300); // Short delay to show spinner | |
} | |
// --- Version Check --- | |
const versionInfoContainer = document.getElementById( | |
"version-info-container" | |
); | |
async function fetchVersionInfo() { | |
if (!versionInfoContainer) return; | |
versionInfoContainer.innerHTML = | |
'<span class="mx-1">|</span><span class="text-xs text-gray-700">检查更新中...</span>'; // Initial loading state | |
try { | |
const response = await fetch("/api/version/check"); | |
if (!response.ok) { | |
throw new Error(`HTTP error! status: ${response.status}`); | |
} | |
const data = await response.json(); | |
let versionHtml = `<span class="mx-1">|</span><span class="text-xs text-gray-800">v${data.current_version}</span>`; | |
if (data.update_available) { | |
versionHtml += ` | |
<span class="mx-1">|</span> | |
<a href="https://github.com/snailyp/gemini-balance/releases/latest" target="_blank" class="text-yellow-600 hover:text-yellow-800 transition duration-300 animate-pulse"> | |
<i class="fas fa-arrow-up"></i> 新版本: v${data.latest_version} | |
</a>`; | |
} else if (data.error_message) { | |
versionHtml += ` | |
<span class="mx-1">|</span> | |
<span class="text-xs text-red-500" title="${data.error_message}">更新检查失败</span>`; | |
} else { | |
versionHtml += `<span class="mx-1">|</span><span class="text-xs text-green-500">已是最新</span>`; // Indicate up-to-date | |
} | |
versionInfoContainer.innerHTML = versionHtml; | |
} catch (error) { | |
console.error("Error fetching version info:", error); | |
versionInfoContainer.innerHTML = `<span class="mx-1">|</span><span class="text-xs text-red-500" title="无法连接到服务器或解析响应">更新检查失败</span>`; | |
} | |
} | |
// Fetch immediately on load | |
fetchVersionInfo(); | |
// Fetch periodically (e.g., every hour) | |
setInterval(fetchVersionInfo, 3600000); // 3600000 ms = 1 hour | |
</script> | |
{% block body_scripts %}{% endblock %} | |
</body> | |
</html> | |