登录认证系统:先登录再使用功能 + 用户身份区分

This commit is contained in:
铸渊 2026-05-19 19:09:33 +08:00
parent 6a1db24f6b
commit f89e3226a8
3 changed files with 151 additions and 24 deletions

View File

@ -205,6 +205,34 @@ body{
/* ── Footer ── */
.footer{text-align:center;padding:24px;font-size:13px;color:var(--text-faint);letter-spacing:.15em;border-top:1px solid var(--glass-edge);margin-top:auto;position:relative;z-index:1;font-weight:400}
/* ── 登录态 ── */
#login-bar{display:flex;align-items:center;justify-content:space-between;padding:18px 24px;background:rgba(11,20,36,.5);border:1px solid var(--glass-edge);border-radius:18px;gap:16px;flex-wrap:wrap}
#login-bar .hint{font-size:14px;color:var(--text-soft)}
#login-bar .hint strong{color:var(--light);font-weight:600}
.login-in{display:flex;gap:10px;align-items:center;flex-wrap:wrap}
.login-in input{padding:10px 16px;background:rgba(255,255,255,.035);border:1px solid var(--glass-edge);border-radius:12px;color:var(--text);font-size:14px;outline:none;transition:all .25s;font-family:inherit;width:130px;font-weight:400}
.login-in input:focus{border-color:rgba(90,208,224,.3);box-shadow:0 0 16px rgba(42,157,191,.08)}
.login-in input::placeholder{color:var(--text-faint)}
.login-in button{padding:10px 24px;background:linear-gradient(135deg,rgba(42,157,191,.3),rgba(123,93,191,.2));border:1px solid var(--glass-edge);border-radius:12px;color:var(--light);font-size:14px;font-weight:500;cursor:pointer;transition:all .25s;font-family:inherit;letter-spacing:.08em}
.login-in button:hover{background:linear-gradient(135deg,rgba(42,157,191,.5),rgba(123,93,191,.35));border-color:rgba(90,208,224,.2)}
.login-in button:disabled{opacity:.4;cursor:not-allowed}
#logged-in-bar{display:none;align-items:center;justify-content:space-between;padding:16px 24px;background:rgba(64,184,122,.06);border:1px solid rgba(64,184,122,.15);border-radius:18px;gap:16px;flex-wrap:wrap}
#logged-in-bar .user-badge{display:flex;align-items:center;gap:10px}
#logged-in-bar .user-avatar{width:36px;height:36px;border-radius:50%;background:linear-gradient(135deg,rgba(42,157,191,.4),rgba(123,93,191,.3));display:flex;align-items:center;justify-content:center;font-size:16px;color:var(--light);font-weight:600}
#logged-in-bar .user-name{font-size:15px;font-weight:600;color:var(--light)}
#logged-in-bar .user-status{font-size:12px;color:var(--green)}
.logged-actions{display:flex;gap:10px;flex-wrap:wrap}
.logged-actions button{padding:10px 22px;border-radius:12px;font-size:14px;font-weight:500;cursor:pointer;transition:all .25s;font-family:inherit}
.logged-actions .btn-primary{background:linear-gradient(135deg,rgba(42,157,191,.35),rgba(123,93,191,.25));border:1px solid var(--glass-edge);color:var(--light)}
.logged-actions .btn-primary:hover{background:linear-gradient(135deg,rgba(42,157,191,.55),rgba(123,93,191,.4))}
.logged-actions .btn-secondary{background:rgba(255,255,255,.03);border:1px solid var(--glass-edge);color:var(--text-soft)}
.logged-actions .btn-secondary:hover{background:rgba(255,255,255,.06);color:var(--light)}
.logged-actions .btn-logout{background:transparent;border:1px solid rgba(224,74,116,.2);color:var(--heart);font-size:13px}
.logged-actions .btn-logout:hover{background:rgba(224,74,116,.08)}
.login-error{color:var(--heart);font-size:13px;margin-top:6px;display:none}
.login-loading{color:var(--text-soft);font-size:13px;margin-top:6px;display:none}
</style>
</head>
<body>
@ -240,19 +268,27 @@ body{
</div>
<div class="dashboard">
<!-- 统一登录 -->
<div class="login-panel">
<div>
<div class="login-hint">已注册成员 · <strong>统一登录</strong> 进入代码域</div>
<div class="login-note">
已有账户?直接登录。新成员需管理员创建账号。
各仓库节点通过联邦令牌互连,<a href="#" onclick="alert('联系管理员:光湖联邦管理员')">了解联邦接入</a>
</div>
</div>
<!-- 登录 / 已登录状态栏 -->
<div id="login-bar">
<div class="hint">登录你的<strong>代码仓库账号</strong> · 使用全部功能</div>
<div class="login-in">
<input type="text" placeholder="账号" id="login-user">
<input type="password" placeholder="密码" id="login-pass" onkeydown="if(event.key==='Enter')login()">
<button onclick="login()">→ 进入代码域</button>
<input type="text" placeholder="账号" id="login-user" onkeydown="if(event.key==='Enter')handleLogin()">
<input type="password" placeholder="密码" id="login-pass" onkeydown="if(event.key==='Enter')handleLogin()">
<button onclick="handleLogin()" id="login-btn">登录</button>
</div>
<div class="login-error" id="login-error"></div>
<div class="login-loading" id="login-loading">验证中...</div>
</div>
<div id="logged-in-bar">
<div class="user-badge">
<div class="user-avatar" id="user-avatar">?</div>
<div><div class="user-name" id="user-name"></div><div class="user-status">已登录 · 光湖联邦成员</div></div>
</div>
<div class="logged-actions">
<button class="btn-primary" onclick="goToRepo()">→ 进入代码仓库</button>
<button class="btn-secondary" onclick="dismissRepo()">留在首页使用功能</button>
<button class="btn-logout" onclick="logout()">退出</button>
</div>
</div>
@ -402,10 +438,64 @@ body{
/* ── Clock ── */
(function t(){const e=document.getElementById('feed-now-time');if(e)e.textContent=new Date().toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit'});setTimeout(t,30000)})();
/* ── Login ── */
function login(){const u=document.getElementById('login-user').value.trim(),p=document.getElementById('login-pass').value;if(!u||!p){alert('请输入账号和密码');return}window.location.href='/code/user/login?redirect_to=/'+u}
/* ── 登录系统 ── */
async function handleLogin(){
const u=document.getElementById('login-user').value.trim(),p=document.getElementById('login-pass').value;
if(!u||!p){showLoginError('请输入账号和密码');return}
showLoginLoading(true);
try{
const r=await fetch('/api/verify',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({user:u,password:p})});
const d=await r.json();
if(d.ok){
localStorage.setItem('current_user',d.username);
localStorage.setItem('is_admin',d.is_admin);
localStorage.setItem('logged_in','true');
localStorage.setItem('login_time',Date.now().toString());
showLoggedIn(d.username);
document.getElementById('login-pass').value='';
}else{
showLoginError(d.error||'登录失败');
}
}catch(e){showLoginError('连接失败')}
showLoginLoading(false);
}
function showLoginError(msg){const e=document.getElementById('login-error');e.textContent=msg;e.style.display='block';setTimeout(()=>e.style.display='none',3000)}
function showLoginLoading(v){document.getElementById('login-loading').style.display=v?'block':'none';document.getElementById('login-btn').disabled=v}
/* ── Training ── */
function showLoggedIn(username){
document.getElementById('login-bar').style.display='none';
document.getElementById('logged-in-bar').style.display='flex';
document.getElementById('user-name').textContent=username;
document.getElementById('user-avatar').textContent=username[0].toUpperCase();
}
function goToRepo(){
const u=localStorage.getItem('current_user');
window.location.href='/code/'+u;
}
function dismissRepo(){
// 已登录状态保持不变,关闭跳转提示
}
function logout(){
localStorage.removeItem('current_user');
localStorage.removeItem('is_admin');
localStorage.removeItem('logged_in');
localStorage.removeItem('login_time');
document.getElementById('logged-in-bar').style.display='none';
document.getElementById('login-bar').style.display='flex';
document.getElementById('login-user').value='';
}
// 页面加载时检查登录状态
(function(){
const u=localStorage.getItem('current_user');
if(u&&localStorage.getItem('logged_in')==='true'){
showLoggedIn(u);
}
})();
// 重写 sendCardMsg 和 sendMsg 带上用户信息
async function sendCardMsg(){const i=document.getElementById('card-chat-input'),m=i.value.trim();if(!m)return;i.value='';const ms=document.getElementById('chat-card-msgs');const ud=document.createElement('div');ud.className='c-msg user';ud.innerHTML='<div class="c-c">'+m.replace(/</g,'&lt;').replace(/>/g,'&gt;')+'</div>';ms.appendChild(ud);const ld=document.createElement('div');ld.className='c-msg bot loading';ld.innerHTML='<div class="c-c">✦ 思考中...</div>';ms.appendChild(ld);ms.scrollTop=ms.scrollHeight;try{const u=localStorage.getItem('current_user')||'';const r=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:m,user:u})});const d=await r.json();ld.className='c-msg bot';ld.innerHTML='<div class="c-c">'+d.reply.replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g,'<br>')+'</div>'}catch(e){ld.className='c-msg bot';ld.innerHTML='<div class="c-c"><span style="opacity:.6">连接暂不可用</span></div>'}ms.scrollTop=ms.scrollHeight}
async function sendMsg(){const i=document.getElementById('chat-input'),m=i.value.trim();if(!m)return;i.value='';const ms=document.getElementById('chat-msgs');const ud=document.createElement('div');ud.className='c-msg user';ud.innerHTML='<div class="c-c">'+m.replace(/</g,'&lt;').replace(/>/g,'&gt;')+'</div>';ms.appendChild(ud);const ld=document.createElement('div');ld.className='c-msg bot loading';ld.innerHTML='<div class="c-c">✦ 思考中...</div>';ms.appendChild(ld);ms.scrollTop=ms.scrollHeight;try{const u=localStorage.getItem('current_user')||'';const r=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:m,user:u})});const d=await r.json();ld.className='c-msg bot';ld.innerHTML='<div class="c-c">'+d.reply.replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g,'<br>')+'</div>'}catch(e){ld.className='c-msg bot';ld.innerHTML='<div class="c-c"><span style="opacity:.6">连接暂不可用</span></div>'}ms.scrollTop=ms.scrollHeight}
async function upTr(){try{const r=await fetch('/training-status.json');if(!r.ok)return;const d=await r.json();if(d.step===undefined)return;const s=d.step||0,t=d.total||12243,p=Math.min(s/t*100,100);document.getElementById('train-step').textContent=s.toLocaleString()+' / '+t.toLocaleString();document.getElementById('train-pct').textContent=p.toFixed(1)+'%';document.getElementById('train-bar').style.width=p+'%';if(d.loss!==undefined)document.getElementById('train-loss').textContent=d.loss;const e=document.getElementById('train-eta');if(e)e.textContent='预计今日完成 · Step '+s.toLocaleString();const z=document.getElementById('persona-zhu-status');if(z)z.textContent='训练中 · '+p.toFixed(0)+'%';const tag=document.getElementById('status-tag');if(tag)tag.textContent='● 训练中 · Step '+s.toLocaleString()}catch(e){}}
setInterval(upTr,30000);upTr();
@ -413,14 +503,9 @@ setInterval(upTr,30000);upTr();
async function connectNotion(){try{window.location.href='/api/notion/connect?user_key=default'}catch(e){alert('Notion 连接暂时不可用')}}
async function checkNt(){try{const r=await fetch('/api/notion/status');const d=await r.json();if(!d.ok)return;const b=document.getElementById('notion-body'),l=document.getElementById('notion-label');if(d.connected_users&&d.connected_users.length>0){const u=d.connected_users[0];l.textContent='✅ '+u.workspace;b.innerHTML='<div style="font-size:13px;color:var(--green);font-weight:500">✅ Notion 已连接</div><div style="margin-top:4px;font-size:13px;color:var(--text-soft)">工作区: '+u.workspace+'</div><button class="notion-btn done" onclick="location.href=\'/api/notion/disconnect?user_key=default\'"><span class="nb-dot"></span>断开连接</button>'}else{l.textContent='未连接';b.innerHTML='<div style="font-size:13px;color:var(--text-soft)">连接 Notion 后,铸渊可读取和记录你的页面</div><button class="notion-btn" onclick="connectNotion()"><span class="nb-dot"></span>连接 Notion</button><div class="notion-perm"><span class="ok">✅ 按语言路径读取</span> · <span class="ok">✅ 新建页面</span> · <span class="no">❌ 不编辑/删除现有</span></div>'}}catch(e){}}
setInterval(checkNt,15000);checkNt();
/* ── Chat ── */
/* ── 浮窗对话 ── */
let chatOpen=false;
function toggleChat(){chatOpen=!chatOpen;document.getElementById('chat-panel').classList.toggle('open')}
async function sendMsg(){const i=document.getElementById('chat-input'),m=i.value.trim();if(!m)return;i.value='';const ms=document.getElementById('chat-msgs');const ud=document.createElement('div');ud.className='c-msg user';ud.innerHTML='<div class="c-c">'+m.replace(/</g,'&lt;').replace(/>/g,'&gt;')+'</div>';ms.appendChild(ud);const ld=document.createElement('div');ld.className='c-msg bot loading';ld.innerHTML='<div class="c-c">✦ 思考中...</div>';ms.appendChild(ld);ms.scrollTop=ms.scrollHeight;try{const r=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:m})});const d=await r.json();ld.className='c-msg bot';ld.innerHTML='<div class="c-c">'+d.reply.replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g,'<br>')+'</div>'}catch(e){ld.className='c-msg bot';ld.innerHTML='<div class="c-c"><span style="opacity:.6">连接暂不可用</span></div>'}ms.scrollTop=ms.scrollHeight}
/* ── 页面中央对话 ── */
async function sendCardMsg(){const i=document.getElementById('card-chat-input'),m=i.value.trim();if(!m)return;i.value='';const ms=document.getElementById('chat-card-msgs');const ud=document.createElement('div');ud.className='c-msg user';ud.innerHTML='<div class="c-c">'+m.replace(/</g,'&lt;').replace(/>/g,'&gt;')+'</div>';ms.appendChild(ud);const ld=document.createElement('div');ld.className='c-msg bot loading';ld.innerHTML='<div class="c-c">✦ 思考中...</div>';ms.appendChild(ld);ms.scrollTop=ms.scrollHeight;try{const r=await fetch('/api/chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({message:m})});const d=await r.json();ld.className='c-msg bot';ld.innerHTML='<div class="c-c">'+d.reply.replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/\n/g,'<br>')+'</div>'}catch(e){ld.className='c-msg bot';ld.innerHTML='<div class="c-c"><span style="opacity:.6">连接暂不可用</span></div>'}ms.scrollTop=ms.scrollHeight}
</script>
</body>
</html>

View File

@ -1 +1 @@
{"step": 2710, "total": 12243, "loss": "1.693", "updated": "2026-05-19T11:05:02.358331+00:00Z"}
{"step": 2830, "total": 12243, "loss": "1.738", "updated": "2026-05-19T11:09:02.193455+00:00Z"}

View File

@ -4,10 +4,12 @@
铸渊Agent API服务 v2
光湖代码仓库 · 引导主控人格 · Notion工具集成
"""
import json, os, glob, re, urllib.request
import json, os, glob, re, urllib.request, base64
from http.server import HTTPServer, BaseHTTPRequestHandler
PORT = 3905
FORGEJO_URL = "https://guanghulab.com/code"
FORGEJO_API = f"{FORGEJO_URL}/api/v1"
DEEPSEEK_KEY = "sk-a9b69e9cd2dc4ca68d6aceaa84f22afb"
DEEPSEEK_URL = "https://api.deepseek.com/chat/completions"
NOTION_MCP = "http://127.0.0.1:3915"
@ -231,6 +233,24 @@ def call_deepseek(messages):
except Exception as e:
return f"抱歉,出错了:{str(e)}"
# ── Forgejo 验证 ──
def verify_forgejo(user, password):
"""通过 Forgejo API 验证用户凭据"""
try:
auth = base64.b64encode(f"{user}:{password}".encode()).decode()
req = urllib.request.Request(f"{FORGEJO_API}/user")
req.add_header("Authorization", f"Basic {auth}")
with urllib.request.urlopen(req, timeout=10) as resp:
data = json.loads(resp.read())
return {"ok": True, "username": data.get("login"), "id": data.get("id"),
"is_admin": data.get("is_admin", False)}
except urllib.request.HTTPError as e:
if e.code == 401:
return {"ok": False, "error": "账号或密码错误"}
return {"ok": False, "error": f"验证失败: {e.code}"}
except Exception as e:
return {"ok": False, "error": f"连接失败: {str(e)}"}
# ── HTTP 处理器 ──
class AgentHandler(BaseHTTPRequestHandler):
def do_OPTIONS(self):
@ -248,18 +268,40 @@ class AgentHandler(BaseHTTPRequestHandler):
user_msg = data.get('message', '').strip()
history = data.get('history', [])
current_user = data.get('user', '')
if not user_msg:
self.json_response({'reply': '请说点什么吧。'})
return
messages = [{"role": "system", "content": build_system_prompt()}]
# 构建系统提示,带上用户信息
sys_prompt = build_system_prompt()
if current_user:
sys_prompt += f"\n\n【当前用户】\n正在和你对话的用户是: {current_user}\n如果涉及Notion操作使用此用户的身份。如果是冰朔(bingshuo),按冰朔的权限规则处理。"
messages = [{"role": "system", "content": sys_prompt}]
for h in history[-10:]:
messages.append(h)
messages.append({"role": "user", "content": user_msg})
reply = call_deepseek(messages)
self.json_response({'reply': reply})
elif self.path == '/verify':
length = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(length).decode() if length else '{}'
data = json.loads(body) if body else {}
user = data.get('user', '').strip()
password = data.get('password', '').strip()
if not user or not password:
self.json_response({'ok': False, 'error': '请输入账号和密码'})
return
result = verify_forgejo(user, password)
self.json_response(result)
else:
self.json_response({'error': 'not found'}, 404)