monica-proxy / index.html
asemxin
Add cookie refresh guide with step-by-step instructions
2eef5c3
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Monica Proxy - 控制面板</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-primary: #0a0a0f;
--bg-secondary: #12121a;
--bg-card: rgba(255, 255, 255, 0.03);
--border-color: rgba(255, 255, 255, 0.08);
--text-primary: #f5f5f7;
--text-secondary: #8e8e93;
--accent-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--success-color: #30d158;
--error-color: #ff453a;
--warning-color: #ffd60a;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
background: var(--bg-primary);
color: var(--text-primary);
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 20px;
background-image:
radial-gradient(ellipse 80% 50% at 50% -20%, rgba(102, 126, 234, 0.15), transparent),
radial-gradient(ellipse 60% 40% at 100% 100%, rgba(118, 75, 162, 0.1), transparent);
}
.container {
width: 100%;
max-width: 600px;
}
.card {
background: var(--bg-card);
border: 1px solid var(--border-color);
border-radius: 20px;
padding: 40px;
backdrop-filter: blur(20px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
margin-bottom: 20px;
}
.header {
text-align: center;
margin-bottom: 36px;
}
.logo {
font-size: 48px;
margin-bottom: 16px;
display: block;
}
h1 {
font-size: 24px;
font-weight: 600;
background: var(--accent-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-bottom: 8px;
}
.subtitle {
color: var(--text-secondary);
font-size: 14px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
font-size: 13px;
font-weight: 500;
color: var(--text-secondary);
margin-bottom: 8px;
}
input,
textarea {
width: 100%;
padding: 14px 16px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 12px;
color: var(--text-primary);
font-size: 14px;
font-family: inherit;
transition: all 0.2s ease;
}
textarea {
resize: vertical;
min-height: 80px;
font-family: 'SF Mono', 'Consolas', monospace;
font-size: 12px;
}
input:focus,
textarea:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.15);
}
input::placeholder,
textarea::placeholder {
color: var(--text-secondary);
opacity: 0.6;
}
.btn {
width: 100%;
padding: 16px;
background: var(--accent-gradient);
border: none;
border-radius: 12px;
color: white;
font-size: 15px;
font-weight: 600;
font-family: inherit;
cursor: pointer;
transition: all 0.2s ease;
margin-top: 8px;
}
.btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(102, 126, 234, 0.35);
}
.btn:active {
transform: translateY(0);
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
transform: none;
}
.btn-secondary {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
}
.btn-secondary:hover {
background: rgba(255, 255, 255, 0.1);
box-shadow: none;
}
.btn-danger {
background: linear-gradient(135deg, #ff453a 0%, #d63031 100%);
}
.status-panel {
margin-top: 28px;
padding: 20px;
background: var(--bg-secondary);
border-radius: 14px;
border: 1px solid var(--border-color);
}
.status-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
}
.status-label {
font-size: 13px;
font-weight: 500;
color: var(--text-secondary);
}
.status-badge {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
}
.status-badge.idle {
background: rgba(142, 142, 147, 0.15);
color: var(--text-secondary);
}
.status-badge.loading {
background: rgba(255, 214, 10, 0.15);
color: var(--warning-color);
}
.status-badge.success {
background: rgba(48, 209, 88, 0.15);
color: var(--success-color);
}
.status-badge.error {
background: rgba(255, 69, 58, 0.15);
color: var(--error-color);
}
.status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: currentColor;
}
.status-badge.loading .status-dot {
animation: pulse 1.5s ease-in-out infinite;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.4;
}
}
.status-details {
font-size: 13px;
color: var(--text-secondary);
line-height: 1.6;
}
.status-details pre {
background: rgba(0, 0, 0, 0.3);
padding: 12px;
border-radius: 8px;
overflow-x: auto;
font-family: 'SF Mono', 'Consolas', monospace;
font-size: 12px;
margin-top: 12px;
white-space: pre-wrap;
word-break: break-all;
}
.model-list {
margin-top: 16px;
max-height: 400px;
overflow-y: auto;
}
.model-item {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 14px;
background: rgba(255, 255, 255, 0.03);
border-radius: 10px;
margin-bottom: 8px;
border: 1px solid transparent;
transition: all 0.2s ease;
}
.model-item:hover {
border-color: var(--border-color);
background: rgba(255, 255, 255, 0.05);
}
.model-icon {
font-size: 18px;
}
.model-name {
flex: 1;
font-family: 'SF Mono', 'Consolas', monospace;
font-size: 13px;
}
.model-count {
background: var(--accent-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 600;
font-size: 14px;
}
.section-title {
font-size: 16px;
font-weight: 600;
margin-bottom: 20px;
color: var(--text-primary);
display: flex;
align-items: center;
gap: 10px;
}
.section-title .icon {
font-size: 20px;
}
.tabs {
display: flex;
gap: 8px;
margin-bottom: 24px;
}
.tab {
flex: 1;
padding: 12px;
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 10px;
color: var(--text-secondary);
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
text-align: center;
}
.tab:hover {
background: rgba(255, 255, 255, 0.05);
}
.tab.active {
background: var(--accent-gradient);
color: white;
border-color: transparent;
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
.footer {
text-align: center;
margin-top: 24px;
font-size: 12px;
color: var(--text-secondary);
}
.footer a {
color: #667eea;
text-decoration: none;
}
.footer a:hover {
text-decoration: underline;
}
.alert {
padding: 14px 16px;
border-radius: 10px;
font-size: 13px;
margin-bottom: 16px;
display: none;
}
.alert.success {
background: rgba(48, 209, 88, 0.15);
border: 1px solid rgba(48, 209, 88, 0.3);
color: var(--success-color);
display: block;
}
.alert.error {
background: rgba(255, 69, 58, 0.15);
border: 1px solid rgba(255, 69, 58, 0.3);
color: var(--error-color);
display: block;
}
.info-box {
background: rgba(102, 126, 234, 0.1);
border: 1px solid rgba(102, 126, 234, 0.2);
border-radius: 10px;
padding: 14px 16px;
font-size: 12px;
color: var(--text-secondary);
margin-bottom: 20px;
}
.info-box code {
background: rgba(0, 0, 0, 0.3);
padding: 2px 6px;
border-radius: 4px;
font-family: 'SF Mono', 'Consolas', monospace;
}
.guide-box {
background: var(--bg-secondary);
border: 1px solid var(--border-color);
border-radius: 12px;
margin-bottom: 20px;
overflow: hidden;
}
.guide-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 16px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: background 0.2s ease;
}
.guide-header:hover {
background: rgba(255, 255, 255, 0.03);
}
.guide-toggle {
font-size: 12px;
color: var(--text-secondary);
transition: transform 0.2s ease;
}
.guide-toggle.open {
transform: rotate(180deg);
}
.guide-content {
display: none;
padding: 0 16px 16px;
border-top: 1px solid var(--border-color);
}
.guide-content.open {
display: block;
}
.guide-steps {
padding-top: 16px;
}
.guide-step {
display: flex;
gap: 14px;
margin-bottom: 16px;
}
.guide-step:last-child {
margin-bottom: 0;
}
.step-number {
width: 28px;
height: 28px;
background: var(--accent-gradient);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: 600;
flex-shrink: 0;
}
.step-content {
flex: 1;
}
.step-content strong {
display: block;
font-size: 13px;
margin-bottom: 4px;
color: var(--text-primary);
}
.step-content p {
font-size: 12px;
color: var(--text-secondary);
line-height: 1.5;
}
.step-content a {
color: #667eea;
text-decoration: none;
}
.step-content a:hover {
text-decoration: underline;
}
.step-content code {
background: rgba(0, 0, 0, 0.3);
padding: 2px 6px;
border-radius: 4px;
font-family: 'SF Mono', 'Consolas', monospace;
font-size: 11px;
}
.guide-note {
margin-top: 16px;
padding: 12px 14px;
background: rgba(255, 214, 10, 0.08);
border: 1px solid rgba(255, 214, 10, 0.15);
border-radius: 8px;
font-size: 12px;
color: var(--text-secondary);
line-height: 1.6;
}
.guide-note a {
color: #667eea;
text-decoration: none;
}
.guide-note a:hover {
text-decoration: underline;
}
.guide-note code {
background: rgba(0, 0, 0, 0.3);
padding: 2px 6px;
border-radius: 4px;
font-family: 'SF Mono', 'Consolas', monospace;
font-size: 11px;
}
</style>
</head>
<body>
<div class="container">
<div class="card">
<div class="header">
<span class="logo">🤖</span>
<h1>Monica Proxy</h1>
<p class="subtitle">控制面板</p>
</div>
<div class="tabs">
<div class="tab active" onclick="switchTab('status')">📊 状态检测</div>
<div class="tab" onclick="switchTab('cookie')">🔑 Cookie 管理</div>
</div>
<!-- Status Tab -->
<div id="status-tab" class="tab-content active">
<div class="form-group">
<label for="apiUrl">API 地址</label>
<input type="text" id="apiUrl" value="https://asem12345-monica-proxy.hf.space"
placeholder="输入 API 地址">
</div>
<div class="form-group">
<label for="bearerToken">Bearer Token</label>
<input type="password" id="bearerToken" placeholder="输入你的 Bearer Token">
</div>
<button class="btn" id="checkBtn" onclick="checkStatus()">
检测 Cookie 状态
</button>
<div class="status-panel">
<div class="status-header">
<span class="status-label">状态</span>
<div class="status-badge idle" id="statusBadge">
<span class="status-dot"></span>
<span id="statusText">等待检测</span>
</div>
</div>
<div class="status-details" id="statusDetails">
点击上方按钮检测 Monica Proxy 的 Cookie 是否可用。
</div>
</div>
</div>
<!-- Cookie Management Tab -->
<div id="cookie-tab" class="tab-content">
<!-- How to get cookie guide -->
<div class="guide-box">
<div class="guide-header" onclick="toggleGuide()">
<span>📖 如何获取 Cookie?</span>
<span class="guide-toggle" id="guideToggle"></span>
</div>
<div class="guide-content" id="guideContent">
<div class="guide-steps">
<div class="guide-step">
<span class="step-number">1</span>
<div class="step-content">
<strong>打开 Monica 官网并登录</strong>
<p>访问 <a href="https://monica.im" target="_blank">https://monica.im</a> 并登录你的账号</p>
</div>
</div>
<div class="guide-step">
<span class="step-number">2</span>
<div class="step-content">
<strong>打开开发者工具</strong>
<p><code>F12</code> 打开开发者工具</p>
</div>
</div>
<div class="guide-step">
<span class="step-number">3</span>
<div class="step-content">
<strong>找到 Cookie</strong>
<p>切换到 <code>Application</code><code>Cookies</code><code>monica.im</code></p>
</div>
</div>
<div class="guide-step">
<span class="step-number">4</span>
<div class="step-content">
<strong>复制 session_id</strong>
<p>找到 <code>session_id</code>,复制其值</p>
</div>
</div>
<div class="guide-step">
<span class="step-number">5</span>
<div class="step-content">
<strong>更新 Cookie</strong>
<p>将复制的值粘贴到下方输入框,点击更新按钮</p>
</div>
</div>
</div>
<div class="guide-note">
💡 <strong>提示:</strong>Cookie 通常有效 7-30 天,过期后按上述流程重新获取。
<br><br>
🔗 也可以直接在 <a href="https://huggingface.co/spaces/ASEM12345/monica-proxy/settings"
target="_blank">HF Space Settings</a> 中更新 <code>MONICA_COOKIE</code> Secret。
</div>
</div>
</div>
<div class="info-box">
⚠️ 更新 Cookie 需要提供 Bearer Token 进行身份验证。Cookie 更新后,代理服务将自动重启以应用新配置。
</div>
<div id="cookieAlert" class="alert"></div>
<div class="form-group">
<label for="cookieApiUrl">API 地址</label>
<input type="text" id="cookieApiUrl" value="https://asem12345-monica-proxy.hf.space"
placeholder="输入 API 地址">
</div>
<div class="form-group">
<label for="cookieBearerToken">Bearer Token(身份验证)</label>
<input type="password" id="cookieBearerToken" placeholder="输入你的 Bearer Token">
</div>
<div class="form-group">
<label for="newCookie">新的 MONICA_COOKIE</label>
<textarea id="newCookie" placeholder="粘贴新的 Monica Cookie 值..."></textarea>
</div>
<button class="btn btn-danger" id="updateCookieBtn" onclick="updateCookie()">
🔄 更新 Cookie
</button>
</div>
</div>
<div class="footer">
Powered by <a href="https://github.com/ycvk/monica-proxy" target="_blank">ycvk/monica-proxy</a>
</div>
</div>
<script>
function switchTab(tabName) {
// Update tab buttons
document.querySelectorAll('.tab').forEach(tab => tab.classList.remove('active'));
event.target.classList.add('active');
// Update tab content
document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));
document.getElementById(tabName + '-tab').classList.add('active');
}
function toggleGuide() {
const content = document.getElementById('guideContent');
const toggle = document.getElementById('guideToggle');
content.classList.toggle('open');
toggle.classList.toggle('open');
}
async function checkStatus() {
const apiUrl = document.getElementById('apiUrl').value.trim().replace(/\/$/, '');
const bearerToken = document.getElementById('bearerToken').value.trim();
const btn = document.getElementById('checkBtn');
const statusBadge = document.getElementById('statusBadge');
const statusText = document.getElementById('statusText');
const statusDetails = document.getElementById('statusDetails');
if (!apiUrl) {
alert('请输入 API 地址');
return;
}
if (!bearerToken) {
alert('请输入 Bearer Token');
return;
}
// Loading state
btn.disabled = true;
btn.textContent = '检测中...';
statusBadge.className = 'status-badge loading';
statusText.textContent = '检测中';
statusDetails.innerHTML = '正在连接到 Monica Proxy...';
try {
const response = await fetch(`${apiUrl}/v1/models`, {
method: 'GET',
headers: {
'Authorization': `Bearer ${bearerToken}`,
'Content-Type': 'application/json'
}
});
const data = await response.json();
if (response.ok && data.data && data.data.length > 0) {
// Success
statusBadge.className = 'status-badge success';
statusText.textContent = 'Cookie 可用';
let html = `
<p>✅ Cookie 工作正常!</p>
<div style="margin-top: 16px; display: flex; align-items: center; justify-content: space-between;">
<span class="section-title" style="margin: 0;"><span class="icon">🧠</span> 可用模型</span>
<span class="model-count">${data.data.length} 个</span>
</div>
<div class="model-list">
`;
data.data.forEach(model => {
const icon = getModelIcon(model.id);
html += `<div class="model-item"><span class="model-icon">${icon}</span><span class="model-name">${model.id}</span></div>`;
});
html += '</div>';
statusDetails.innerHTML = html;
} else if (response.status === 401) {
// Unauthorized
statusBadge.className = 'status-badge error';
statusText.textContent = 'Token 错误';
statusDetails.innerHTML = `<p>❌ Bearer Token 验证失败</p><pre>${JSON.stringify(data, null, 2)}</pre>`;
} else {
// Other error - possibly cookie issue
statusBadge.className = 'status-badge error';
statusText.textContent = 'Cookie 失效';
statusDetails.innerHTML = `
<p>❌ Cookie 可能已失效或存在其他问题</p>
<pre>${JSON.stringify(data, null, 2)}</pre>
<p style="margin-top: 16px;">👉 请切换到 <strong>Cookie 管理</strong> 标签页更新 Cookie</p>
`;
}
} catch (error) {
statusBadge.className = 'status-badge error';
statusText.textContent = '连接失败';
statusDetails.innerHTML = `<p>❌ 无法连接到 API</p><pre>${error.message}</pre><p style="margin-top: 12px; color: var(--text-secondary);">可能原因:网络问题、CORS 限制、或服务不可用</p>`;
} finally {
btn.disabled = false;
btn.textContent = '检测 Cookie 状态';
}
}
function getModelIcon(modelId) {
const id = modelId.toLowerCase();
if (id.includes('gpt')) return '🟢';
if (id.includes('claude')) return '🟠';
if (id.includes('gemini')) return '🔵';
if (id.includes('grok')) return '⚫';
if (id.includes('llama')) return '🦙';
if (id.includes('qwen')) return '🟣';
if (id.includes('deepseek')) return '🔷';
if (id.includes('sonar')) return '📡';
if (id.includes('o1') || id.includes('o3')) return '🧪';
return '🤖';
}
async function updateCookie() {
const apiUrl = document.getElementById('cookieApiUrl').value.trim().replace(/\/$/, '');
const bearerToken = document.getElementById('cookieBearerToken').value.trim();
const newCookie = document.getElementById('newCookie').value.trim();
const btn = document.getElementById('updateCookieBtn');
const alertBox = document.getElementById('cookieAlert');
if (!apiUrl || !bearerToken || !newCookie) {
showAlert('error', '请填写所有字段');
return;
}
btn.disabled = true;
btn.textContent = '更新中...';
alertBox.className = 'alert';
try {
const response = await fetch(`${apiUrl}/admin/update-cookie`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${bearerToken}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ cookie: newCookie })
});
const data = await response.json();
if (response.ok) {
showAlert('success', '✅ Cookie 更新成功!服务将在几秒后重启。');
document.getElementById('newCookie').value = '';
} else {
showAlert('error', `❌ 更新失败: ${data.error || data.message || '未知错误'}`);
}
} catch (error) {
showAlert('error', `❌ 请求失败: ${error.message}`);
} finally {
btn.disabled = false;
btn.textContent = '🔄 更新 Cookie';
}
}
function showAlert(type, message) {
const alertBox = document.getElementById('cookieAlert');
alertBox.className = `alert ${type}`;
alertBox.textContent = message;
}
// Allow Enter key to trigger check
document.getElementById('bearerToken').addEventListener('keypress', function (e) {
if (e.key === 'Enter') {
checkStatus();
}
});
</script>
</body>
</html>