登录认证系统:先登录再使用功能 + 用户身份区分
This commit is contained in:
parent
6a1db24f6b
commit
f89e3226a8
@ -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,'<').replace(/>/g,'>')+'</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,'<').replace(/>/g,'>').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,'<').replace(/>/g,'>')+'</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,'<').replace(/>/g,'>').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,'<').replace(/>/g,'>')+'</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,'<').replace(/>/g,'>').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,'<').replace(/>/g,'>')+'</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,'<').replace(/>/g,'>').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>
|
||||
|
||||
@ -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"}
|
||||
@ -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)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user