zhizhi/persona-studio/frontend/index.html

246 lines
8.2 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Persona Studio · 光湖人格体协助开发体验</title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container login-container">
<div class="logo-area">
<h1>🌊 Persona Studio</h1>
<p class="subtitle">光湖人格体协助开发体验</p>
</div>
<div class="login-box">
<!-- ── 开发编号登录 ── -->
<h2>请输入你的开发编号</h2>
<p class="hint">编号由冰朔主控分配格式EXP-000 ~ EXP-011</p>
<form id="loginForm" onsubmit="return handleLogin(event)">
<input
type="text"
id="devIdInput"
placeholder="EXP-XXX"
pattern="^EXP-\d{3,}$"
required
autocomplete="off"
/>
<button type="submit" id="loginBtn">进入对话</button>
</form>
<div class="guest-divider">
<span></span>
</div>
<!-- ── API Key 登录 ── -->
<div class="apikey-section">
<h2>API Key 登录</h2>
<p class="hint">输入你的第三方 API 信息,自动检测可用模型</p>
<input
type="text"
id="apiBaseInput"
class="apikey-input"
placeholder="API Base URL如 https://api.openai.com"
autocomplete="off"
/>
<input
type="password"
id="apiKeyInput"
class="apikey-input"
placeholder="请输入你的 API Key"
autocomplete="off"
/>
<button type="button" id="detectBtn" class="btn-detect" onclick="handleDetectModels()">
🔍 检测可用模型
</button>
<div id="detectStatus" class="detect-status" style="display:none;"></div>
<div id="modelListContainer" class="model-list-container" style="display:none;">
<p class="model-list-title">请选择一个模型进入对话</p>
<div id="modelList" class="model-list"></div>
</div>
</div>
<div id="errorMsg" class="error-msg" style="display:none;"></div>
</div>
<footer class="login-footer">
<p>HoloLake Era · AGE OS · 人格体协助体验平台</p>
</footer>
</div>
<script>
const DEV_ID_RE = /^EXP-\d{3,}$/;
const API_BASE = getApiBase();
function getApiBase() {
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
return 'http://localhost:3721';
}
return 'https://guanghulab.com';
}
/* ---- 开发编号登录 ---- */
async function handleLogin(e) {
e.preventDefault();
const devId = document.getElementById('devIdInput').value.trim().toUpperCase();
const errorEl = document.getElementById('errorMsg');
const btn = document.getElementById('loginBtn');
errorEl.style.display = 'none';
if (!DEV_ID_RE.test(devId)) {
errorEl.textContent = '编号格式不正确,请输入 EXP-XXX 格式';
errorEl.style.display = 'block';
return false;
}
btn.disabled = true;
btn.textContent = '验证中…';
try {
const res = await fetch(API_BASE + '/api/ps/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ dev_id: devId })
});
const data = await res.json();
if (!res.ok || data.error) {
errorEl.textContent = data.message || '登录失败,请检查编号';
errorEl.style.display = 'block';
btn.disabled = false;
btn.textContent = '进入对话';
return false;
}
sessionStorage.setItem('dev_id', devId);
sessionStorage.setItem('session_token', data.token || '');
sessionStorage.removeItem('login_mode');
sessionStorage.removeItem('user_api_base');
sessionStorage.removeItem('user_api_key');
sessionStorage.removeItem('selected_model');
window.location.href = 'chat.html';
} catch (_err) {
errorEl.textContent = '服务暂时不可用,请稍后再试';
errorEl.style.display = 'block';
btn.disabled = false;
btn.textContent = '进入对话';
}
return false;
}
/* ---- API Key 模型检测 ---- */
async function handleDetectModels() {
const apiBase = document.getElementById('apiBaseInput').value.trim();
const apiKey = document.getElementById('apiKeyInput').value.trim();
const errorEl = document.getElementById('errorMsg');
const statusEl = document.getElementById('detectStatus');
const modelContainer = document.getElementById('modelListContainer');
const btn = document.getElementById('detectBtn');
errorEl.style.display = 'none';
modelContainer.style.display = 'none';
if (!apiBase) {
errorEl.textContent = '请输入 API Base URL';
errorEl.style.display = 'block';
return;
}
if (!apiKey) {
errorEl.textContent = '请输入 API Key';
errorEl.style.display = 'block';
return;
}
btn.disabled = true;
btn.textContent = '正在检测可用模型…';
statusEl.textContent = '正在检测可用模型...';
statusEl.className = 'detect-status detect-loading';
statusEl.style.display = 'block';
try {
const res = await fetch(API_BASE + '/api/ps/apikey/detect-models', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ api_base: apiBase, api_key: apiKey })
});
const data = await res.json();
if (!res.ok || data.error) {
statusEl.textContent = data.message || '未检测到模型,请检查 API Key';
statusEl.className = 'detect-status detect-error';
btn.disabled = false;
btn.textContent = '🔍 检测可用模型';
return;
}
const models = data.models || [];
if (models.length === 0) {
statusEl.textContent = '未检测到可用模型';
statusEl.className = 'detect-status detect-error';
btn.disabled = false;
btn.textContent = '🔍 检测可用模型';
return;
}
statusEl.textContent = '检测到 ' + models.length + ' 个模型,请选择一个模型进入对话';
statusEl.className = 'detect-status detect-success';
renderModelList(models, apiBase, apiKey);
modelContainer.style.display = 'block';
btn.disabled = false;
btn.textContent = '🔍 重新检测';
} catch (fetchErr) {
var errMsg = '检测失败';
if (fetchErr instanceof TypeError) {
errMsg = '服务器代理未部署或不可达,请联系管理员';
} else if (fetchErr.name === 'AbortError') {
errMsg = '请求超时,请检查网络连接';
} else {
errMsg = '网络错误: ' + (fetchErr.message || '请检查网络连接');
}
statusEl.textContent = errMsg;
statusEl.className = 'detect-status detect-error';
btn.disabled = false;
btn.textContent = '🔍 检测可用模型';
}
}
/* ---- 渲染模型列表 ---- */
function renderModelList(models, apiBase, apiKey) {
var listEl = document.getElementById('modelList');
listEl.innerHTML = '';
models.forEach(function (modelId) {
var item = document.createElement('button');
item.className = 'model-item';
item.textContent = modelId;
item.onclick = function () { enterApiKeyChat(apiBase, apiKey, modelId); };
listEl.appendChild(item);
});
}
/* ---- 选择模型 → 进入对话 ---- */
function enterApiKeyChat(apiBase, apiKey, selectedModel) {
sessionStorage.setItem('login_mode', 'apikey');
sessionStorage.setItem('user_api_base', apiBase);
sessionStorage.setItem('user_api_key', apiKey);
sessionStorage.setItem('selected_model', selectedModel);
sessionStorage.setItem('dev_id', 'APIKEY-USER');
sessionStorage.removeItem('session_token');
window.location.href = 'chat.html';
}
</script>
</body>
</html>