D121: module system INDEX rewrite - modules are AI tools, not human tools
187
_deploy/gatekeeper.js
Normal file
@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env node
|
||||
/* ═══════════════════════════════════════
|
||||
铸渊守门人 v2.0 · HLDP万能语言接口
|
||||
新增 /hlpd 端点 — HLDP模块翻译执行
|
||||
═══════════════════════════════════════ */
|
||||
|
||||
const http=require('http'),fs=require('fs'),path=require('path'),crypto=require('crypto'),{exec}=require('child_process'),os=require('os');
|
||||
|
||||
const PORT=parseInt(process.env.PORT||process.argv[2]||'3910',10),DATA=path.join(os.homedir(),'.gk'),TIMEOUT=30000,MAX=100000,RATE=60;
|
||||
|
||||
if(!fs.existsSync(DATA))fs.mkdirSync(DATA,{recursive:true,mode:0o700});
|
||||
const SF=path.join(DATA,'secret');
|
||||
let SECRET;
|
||||
if(fs.existsSync(SF))SECRET=fs.readFileSync(SF,'utf-8').trim();
|
||||
if(!SECRET){SECRET='zy_gtw_'+crypto.randomBytes(24).toString('hex');fs.writeFileSync(SF,SECRET,{mode:0o600});}
|
||||
|
||||
function log(a,d){const l=`[${new Date().toISOString()}] [${a}]${d?' | '+d:''}`;console.log(l);try{fs.appendFileSync(path.join(DATA,'gk.log'),l+'\n')}catch(e){}}
|
||||
|
||||
const RC={};
|
||||
function checkIP(ip){const n=Date.now(),w=60000;if(!RC[ip]){RC[ip]={c:1,r:n+w};return 1}if(n>RC[ip].r){RC[ip]={c:1,r:n+w};return 1}RC[ip].c++;return RC[ip].c<=RATE}
|
||||
function auth(h){const t=(h['authorization']||h['Authorization']||'').replace(/^Bearer\s+/i,'').trim();if(!t)return 0;if(t.length!==SECRET.length)return 0;for(let i=0;i<t.length;i++)if(t.charCodeAt(i)!==SECRET.charCodeAt(i))return 0;return 1}
|
||||
function pb(req){return new Promise(r=>{let b='';req.on('data',c=>b+=c);req.on('end',()=>{try{r(JSON.parse(b))}catch(e){r(null)}});req.on('error',()=>r(null))})}
|
||||
function jr(r,s,d){r.writeHead(s,{'Content-Type':'application/json','Access-Control-Allow-Origin':'*'});r.end(JSON.stringify(d))}
|
||||
|
||||
async function execCmd(cmd,to){return new Promise(r=>{exec(cmd,{timeout:to||TIMEOUT,maxBuffer:MAX,shell:'/bin/bash'},(e,o,se)=>{r({stdout:(o||'').slice(0,MAX),stderr:(se||'').slice(0,MAX),code:e?(e.code||e.signal||1):0,err:e?e.message:null})})})}
|
||||
|
||||
/* ═══════════════════════════════════════
|
||||
HLDP 翻译层 v1.0 — 守门人 v2.0 新增
|
||||
═══════════════════════════════════════ */
|
||||
|
||||
/*
|
||||
翻译规则:
|
||||
@执行: shell → 提取缩进内容 → bash 执行
|
||||
@执行: python → 提取缩进内容 → python3 -c 执行
|
||||
@部署: 写入 <路径> → 提取缩进内容 → 写入文件
|
||||
@部署: 命令 <shell命令> → 直接执行
|
||||
*/
|
||||
|
||||
function parseHLDP(text) {
|
||||
const results = [];
|
||||
const lines = text.split('\n');
|
||||
let i = 0;
|
||||
while (i < lines.length) {
|
||||
const line = lines[i];
|
||||
// @执行: shell 或 @执行: python
|
||||
const execMatch = line.match(/^@执行:\s*(shell|python)\s*$/);
|
||||
if (execMatch) {
|
||||
const lang = execMatch[1];
|
||||
let code = '';
|
||||
i++;
|
||||
while (i < lines.length && (lines[i].startsWith(' ') || lines[i].startsWith('\t') || lines[i].trim() === '')) {
|
||||
code += (code ? '\n' : '') + lines[i].replace(/^ /, '');
|
||||
i++;
|
||||
}
|
||||
if (code.trim()) {
|
||||
if (lang === 'shell') {
|
||||
results.push({ type: 'exec', cmd: code.trim() });
|
||||
} else if (lang === 'python') {
|
||||
const safe = code.trim().replace(/'/g, "'\"'\"'");
|
||||
results.push({ type: 'exec', cmd: `python3 -c '${safe}'` });
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// @部署: 写入 <路径>
|
||||
const deployMatch = line.match(/^@部署:\s*写入\s+(.+)$/);
|
||||
if (deployMatch) {
|
||||
const filePath = deployMatch[1].trim();
|
||||
let content = '';
|
||||
i++;
|
||||
while (i < lines.length && (lines[i].startsWith(' ') || lines[i].startsWith('\t') || lines[i].trim() === '')) {
|
||||
content += (content ? '\n' : '') + lines[i].replace(/^ /, '');
|
||||
i++;
|
||||
}
|
||||
if (content.trim()) {
|
||||
results.push({ type: 'write', path: filePath, content: content.trim() });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// @部署: 命令 <cmd>
|
||||
const cmdMatch = line.match(/^@部署:\s*命令\s+(.+)$/);
|
||||
if (cmdMatch) {
|
||||
results.push({ type: 'exec', cmd: cmdMatch[1].trim() });
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════
|
||||
路由
|
||||
═══════════════════════════════════════ */
|
||||
|
||||
async function route(rt,rq,rs){
|
||||
const b=await pb(rq);
|
||||
switch(rt){
|
||||
case '/exec':
|
||||
if(!b||!b.cmd){jr(rs,400,{error:'no cmd'});return}
|
||||
log('CMD',b.cmd.slice(0,200));
|
||||
const r=await execCmd(b.cmd,b.timeout);
|
||||
jr(rs,200,{ok:r.code===0,stdout:r.stdout,stderr:r.stderr,code:r.code});
|
||||
return;
|
||||
|
||||
case '/hlpd':
|
||||
if(!b||!b.hldp){jr(rs,400,{error:'no hldp'});return}
|
||||
log('HLDP','解析中');
|
||||
const steps = parseHLDP(b.hldp);
|
||||
const results = [];
|
||||
for (let i=0; i<steps.length; i++) {
|
||||
const s = steps[i];
|
||||
log('HLDP', (i+1)+'/'+steps.length+' '+s.type);
|
||||
if (s.type === 'write') {
|
||||
try {
|
||||
const dir = path.dirname(s.path);
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, {recursive: true});
|
||||
fs.writeFileSync(s.path, s.content, 'utf-8');
|
||||
results.push({ step: i+1, type: 'write', ok: true, path: s.path });
|
||||
} catch(e) {
|
||||
results.push({ step: i+1, type: 'write', ok: false, path: s.path, error: e.message });
|
||||
}
|
||||
} else if (s.type === 'exec') {
|
||||
const er = await execCmd(s.cmd);
|
||||
if (er.code === 0 && !er.stderr) {
|
||||
results.push({ step: i+1, type: 'exec', ok: true, stdout: er.stdout.slice(0, 1000) });
|
||||
} else {
|
||||
results.push({ step: i+1, type: 'exec', ok: false, stdout: er.stdout.slice(0, 500), stderr: er.stderr.slice(0, 500), code: er.code });
|
||||
}
|
||||
}
|
||||
}
|
||||
const allOk = results.every(r => r.ok);
|
||||
jr(rs, 200, { ok: allOk, steps: results.length, results });
|
||||
return;
|
||||
|
||||
case '/status':
|
||||
log('INFO','status');
|
||||
jr(rs,200,{ok:true,...await sysInfo()});
|
||||
return;
|
||||
|
||||
case '/health':
|
||||
jr(rs,200,{ok:true,svc:'gk',v:'2.0',up:process.uptime().toFixed(0)+'s',hldp:true,ts:new Date().toISOString()});
|
||||
return;
|
||||
|
||||
case '/file/read':
|
||||
if(!b||!b.path){jr(rs,400,{error:'no path'});return}
|
||||
try{const c=fs.readFileSync(b.path,'utf-8'),s=fs.statSync(b.path);jr(rs,200,{ok:true,path:b.path,size:s.size,modified:s.mtime.toISOString(),content:c.slice(0,MAX)})}catch(e){jr(rs,404,{ok:false,error:e.message})}
|
||||
return;
|
||||
|
||||
case '/file/write':
|
||||
if(!b||!b.path||b.content===undefined){jr(rs,400,{error:'need path+content'});return}
|
||||
try{const d=path.dirname(b.path);if(!fs.existsSync(d))fs.mkdirSync(d,{recursive:true});fs.writeFileSync(b.path,b.content,'utf-8');jr(rs,200,{ok:true,path:b.path,size:Buffer.byteLength(b.content,'utf-8')})}catch(e){jr(rs,500,{ok:false,error:e.message})}
|
||||
return;
|
||||
|
||||
case '/file/list':
|
||||
try{const p=(b&&b.path)||'.',i=fs.readdirSync(p,{withFileTypes:true});jr(rs,200,{ok:true,path:p,files:i.map(x=>({name:x.name,type:x.isDirectory()?'dir':'file'})))}catch(e){jr(rs,404,{ok:false,error:e.message})}
|
||||
return;
|
||||
|
||||
case '/ping':
|
||||
jr(rs,200,{ok:true,pong:true});
|
||||
return;
|
||||
|
||||
default:
|
||||
jr(rs,404,{error:'unknown:'+rt});
|
||||
}
|
||||
}
|
||||
|
||||
async function sysInfo(){const h=os.hostname(),u=Math.floor(os.uptime()),tm=os.totalmem(),fm=os.freemem(),l=os.loadavg(),c=os.cpus().length,pl=os.platform(),ar=os.arch();let di='';try{di=require('child_process').execSync('df -h /',{timeout:5000}).toString()}catch(e){}return{hostname:h,platform:pl,arch:ar,cpus:c,uptime:u,mem:{total:fb(tm),free:fb(fm),used:fb(tm-fm),pct:((tm-fm)/tm*100).toFixed(1)+'%'},load:l.map(n=>n.toFixed(2)),disk:di,gk:{v:'2.0',port:PORT,up:process.uptime().toFixed(0)+'s',hldp:true}}}
|
||||
function fb(b){if(b===0)return'0 B';const k=1024,s=['B','KB','MB','GB','TB'],i=Math.floor(Math.log(b)/Math.log(k));return parseFloat((b/Math.pow(k,i)).toFixed(1))+' '+s[i]}
|
||||
|
||||
const sv=http.createServer(async(rq,rs)=>{
|
||||
if(rq.method==='OPTIONS'){jr(rs,204,{});return}
|
||||
if(rq.method!=='POST'){jr(rs,405,{error:'POST only'});return}
|
||||
const ip=rq.socket.remoteAddress||'';
|
||||
if(!checkIP(ip)){log('WARN','rate',ip);jr(rs,429,{error:'too fast'});return}
|
||||
if(!auth(rq.headers)){log('WARN','auth','fail');jr(rs,401,{error:'bad key'});return}
|
||||
const u=new URL(rq.url,'http://h'),rt=u.pathname;
|
||||
try{await route(rt,rq,rs)}catch(e){log('ERR','route',e.message);jr(rs,500,{error:e.message})}
|
||||
});
|
||||
|
||||
sv.listen(PORT,'0.0.0.0',()=>{
|
||||
const h=os.hostname();
|
||||
console.log(`\n ⚔️ 铸渊守门人 v2.0 · HLDP语言接口\n ──────────────────────\n 主机: ${h}\n 端口: ${PORT}\n 新增: /hlpd — HLDP模块翻译执行\n 翻译: @执行 shell|python → 执行\n @部署 写入 → 文件写入\n ──────────────────────\n`);
|
||||
log('UP',`v2.0 ${h}:${PORT} +HLDP`);
|
||||
});
|
||||
|
||||
process.on('SIGTERM',()=>{log('DOWN','sigterm');sv.close(()=>process.exit(0))});
|
||||
process.on('SIGINT',()=>{log('DOWN','sigint');sv.close(()=>process.exit(0))});
|
||||
process.on('uncaughtException',e=>log('ERR','ex',e.message));
|
||||
@ -1,12 +1,12 @@
|
||||
{
|
||||
"ts": "2026-05-25T17:01:14.044Z",
|
||||
"ts": "2026-05-30T05:42:48.602Z",
|
||||
"summary": {
|
||||
"total": 12,
|
||||
"alive": 12,
|
||||
"off": 0,
|
||||
"diffs": 0,
|
||||
"deep": 3,
|
||||
"ms": 2368
|
||||
"ms": 7059
|
||||
},
|
||||
"issues": [],
|
||||
"deep": [
|
||||
|
||||
BIN
hldp_tree.db
Normal file
77
homepage/ag.js
Normal file
@ -0,0 +1,77 @@
|
||||
const express = require('express');
|
||||
const axios = require('axios');
|
||||
const { execSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const Database = require('better-sqlite3');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const DEEPSEEK_KEY = 'sk-5bf0ac6628ff406eb37d5043b7c9220b';
|
||||
|
||||
const db = new Database('/opt/zhuyuan/agent/conversations3.db');
|
||||
db.exec('CREATE TABLE IF NOT EXISTS convs (id TEXT PRIMARY KEY, title TEXT, ctime TEXT, utime TEXT)');
|
||||
db.exec('CREATE TABLE IF NOT EXISTS msgs (id INTEGER PRIMARY KEY AUTOINCREMENT, cid TEXT, role TEXT, content TEXT, ctime TEXT)');
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use(express.static('/opt/zhuyuan/agent/public'));
|
||||
|
||||
function raw(cmd){ try{ return execSync(cmd,{timeout:5000}).toString().trim() }catch(e){ return '?' } }
|
||||
|
||||
function reality(){
|
||||
return {
|
||||
host: raw('hostname'),
|
||||
cpu: raw('top -bn1 | head -5 | head -c 200'),
|
||||
mem: raw('free -h'),
|
||||
disk: raw('df -h /'),
|
||||
pm2: raw('pm2 jlist 2>/dev/null | head -c 500')
|
||||
};
|
||||
}
|
||||
|
||||
app.get('/api/convs',(req,res)=>{
|
||||
res.json(db.prepare('SELECT * FROM convs ORDER BY utime DESC').all());
|
||||
});
|
||||
app.post('/api/convs',(req,res)=>{
|
||||
const id=uuidv4();
|
||||
db.prepare("INSERT INTO convs VALUES(?,?,datetime('now'),datetime('now'))").run(id,'新对话');
|
||||
res.json({id});
|
||||
});
|
||||
app.get('/api/convs/:id',(req,res)=>{
|
||||
const c=db.prepare('SELECT * FROM convs WHERE id=?').get(req.params.id);
|
||||
if(!c)return res.status(404).json({e:'not found'});
|
||||
c.messages=db.prepare('SELECT role,content FROM msgs WHERE cid=? ORDER BY id ASC').all(req.params.id);
|
||||
res.json(c);
|
||||
});
|
||||
app.delete('/api/convs/:id',(req,res)=>{
|
||||
db.prepare('DELETE FROM msgs WHERE cid=?').run(req.params.id);
|
||||
db.prepare('DELETE FROM convs WHERE id=?').run(req.params.id);
|
||||
res.json({ok:1});
|
||||
});
|
||||
app.post('/api/chat',async(req,res)=>{
|
||||
const{conversation_id,message}=req.body;
|
||||
if(!message)return res.status(400).json({e:'no msg'});
|
||||
let cid=conversation_id||uuidv4();
|
||||
if(!conversation_id)db.prepare("INSERT INTO convs VALUES(?,?,datetime('now'),datetime('now'))").run(cid,'新对话');
|
||||
db.prepare("INSERT INTO msgs(cid,role,content,ctime) VALUES(?,'user',?,datetime('now'))").run(cid,message);
|
||||
const hist=db.prepare('SELECT role,content FROM msgs WHERE cid=? ORDER BY id DESC LIMIT 30').all(cid).reverse();
|
||||
const t=db.prepare('SELECT title FROM convs WHERE id=?').get(cid);
|
||||
if(t&&t.title==='新对话')db.prepare('UPDATE convs SET title=? WHERE id=?').run(message.slice(0,25),cid);
|
||||
|
||||
const r=reality();
|
||||
const ctx='[Server Reality - '+r.host+']'+'\nCPU:\n'+r.cpu+'\nMemory:\n'+r.mem+'\nDisk:\n'+r.disk+'\nPM2:\n'+r.pm2;
|
||||
|
||||
const sys='你是光湖团队技术助手,由铸渊ICE-GL-ZY001部署。技术主控Awen的AI助手。服务器: 冰朔6台+团队2台。广州是中枢(43.139.217.141)。每次回答时用户消息末尾会附带当前服务器真实采集数据,请基于真实数据回答,不要编造。';
|
||||
|
||||
try{
|
||||
const resp=await axios.post('https://api.deepseek.com/v1/chat/completions',{
|
||||
model:'deepseek-chat',
|
||||
messages:[{role:'system',content:sys},...hist.slice(0,-1),{role:'user',content:message+'\n\n'+ctx}],
|
||||
temperature:0.5,max_tokens:2000
|
||||
},{headers:{'Authorization':'Bearer '+DEEPSEEK_KEY,'Content-Type':'application/json'}});
|
||||
const reply=resp.data.choices[0].message.content;
|
||||
db.prepare("INSERT INTO msgs(cid,role,content,ctime) VALUES(?,'assistant',?,datetime('now'))").run(cid,reply);
|
||||
db.prepare("UPDATE convs SET utime=datetime('now') WHERE id=?").run(cid);
|
||||
res.json({conversation_id:cid,reply,reality:r});
|
||||
}catch(e){res.status(500).json({e:'API:'+e.message})}
|
||||
});
|
||||
app.get('/api/reality',(req,res)=>res.json(reality()));
|
||||
app.get('/api/health',(req,res)=>res.json({ok:1}));
|
||||
app.listen(3940,'127.0.0.1',()=>console.log('Agent OK'));
|
||||
1
homepage/agent-b64.txt
Normal file
BIN
homepage/agent-pkg.tar.gz
Normal file
43
homepage/agui.html
Normal file
@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>光湖助手 · Agent</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,sans-serif;background:#030712;color:#e5e7eb;height:100vh;display:flex}
|
||||
.sb{width:260px;background:rgba(17,17,28,.95);border-right:1px solid rgba(255,255,255,.06);display:flex;flex-direction:column;flex-shrink:0}
|
||||
.sb-hd{padding:16px;border-bottom:1px solid rgba(255,255,255,.06)}
|
||||
.sb-hd h1{font-size:15px;color:#fff;margin-bottom:12px}
|
||||
.nbtn{width:100%;padding:10px;background:#6366f1;border:none;border-radius:8px;color:#fff;font-size:13px;cursor:pointer}.nbtn:hover{background:#4f46e5}
|
||||
.clist{flex:1;overflow-y:auto;padding:8px}
|
||||
.ci{padding:10px 12px;border-radius:8px;margin-bottom:4px;cursor:pointer;font-size:13px;color:#9ca3af;display:flex;justify-content:space-between}.ci:hover{background:rgba(255,255,255,.04)}.ci.ac{background:rgba(99,102,241,.1);color:#a5b4fc}
|
||||
.ci .tt{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.ci .dl{opacity:0;cursor:pointer;color:#f87171;font-size:11px}.ci:hover .dl{opacity:.6}
|
||||
.mn{flex:1;display:flex;flex-direction:column;min-width:0}
|
||||
.thd{background:rgba(17,17,28,.95);border-bottom:1px solid rgba(255,255,255,.06);padding:12px 24px;font-size:14px;color:#9ca3af}
|
||||
.chat{flex:1;overflow-y:auto;padding:24px;display:flex;flex-direction:column;gap:16px}
|
||||
.msg{max-width:75%;padding:14px 18px;border-radius:14px;font-size:14px;line-height:1.7;white-space:pre-wrap;word-break:break-word}
|
||||
.msg.u{align-self:flex-end;background:#6366f1;color:#fff;border-bottom-right-radius:4px}
|
||||
.msg.a{align-self:flex-start;background:rgba(17,17,28,.9);border:1px solid rgba(255,255,255,.06);border-bottom-left-radius:4px}
|
||||
.msg.a pre{background:rgba(0,0,0,.3);padding:12px;border-radius:8px;margin:8px 0;overflow-x:auto;font-size:12px}
|
||||
.msg.a code{font-family:monospace;font-size:12px;background:rgba(0,0,0,.3);padding:2px 6px;border-radius:4px}
|
||||
.ia{padding:16px 24px;border-top:1px solid rgba(255,255,255,.06);display:flex;gap:12px;background:rgba(17,17,28,.8)}
|
||||
.ia input{flex:1;padding:14px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);border-radius:12px;color:#fff;font-size:14px;outline:none}
|
||||
.ia input:focus{border-color:#6366f1}
|
||||
.ia button{padding:14px 24px;background:#6366f1;border:none;border-radius:12px;color:#fff;font-weight:600;cursor:pointer}.ia button:hover{background:#4f46e5}
|
||||
.ia button:disabled{opacity:.5}
|
||||
.epty{flex:1;display:flex;align-items:center;justify-content:center;color:#374151;font-size:15px}
|
||||
@keyframes fi{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}.msg{animation:fi .3s}
|
||||
.typ{color:#6b7280;padding:8px 18px;font-size:13px;animation:pulse 1.5s infinite}@keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
|
||||
</style></head><body>
|
||||
<div class="sb"><div class="sb-hd"><h1>光湖助手</h1><button class="nbtn" onclick="nc()">+ 新建对话</button></div><div class="clist" id="cl"></div></div>
|
||||
<div class="mn"><div class="thd" id="tt">选择或新建对话</div><div class="chat" id="chat"><div class="epty">点击左侧新建对话开始</div></div><div class="ia"><input id="inp" placeholder="问服务器问题..." onkeydown="if(event.key==='Enter')snd()" disabled><button onclick="snd()" id="b" disabled>发送</button></div></div>
|
||||
<script>
|
||||
let cc=null;
|
||||
const A='api/';
|
||||
async function lc(){const r=await fetch(A+'convs');const d=await r.json();document.getElementById('cl').innerHTML=d.map(c=>`<div class="ci${c.id===cc?' ac':''}" onclick="sc('${c.id}')"><span class="tt">${e(c.title)}</span><span class="dl" onclick="event.stopPropagation();dc('${c.id}')">x</span></div>`).join('')}
|
||||
function e(s){return(s||'').replace(/</g,'<')}
|
||||
async function nc(){const r=await fetch(A+'convs',{method:'POST'});const d=await r.json();cc=d.id;document.getElementById('tt').textContent='新对话';document.getElementById('chat').innerHTML='';document.getElementById('inp').disabled=false;document.getElementById('b').disabled=false;document.getElementById('inp').focus();await lc()}
|
||||
async function sc(id){cc=id;const r=await fetch(A+'convs/'+id);const d=await r.json();document.getElementById('tt').textContent=d.title;const ch=document.getElementById('chat');if(d.messages.length===0)ch.innerHTML='<div class="epty">开始对话</div>';else ch.innerHTML=d.messages.map(m=>`<div class="msg ${m.role==='user'?'u':'a'}">${m.role==='user'?e(m.content):f(m.content)}</div>`).join('');document.getElementById('inp').disabled=false;document.getElementById('b').disabled=false;document.getElementById('inp').focus();ch.scrollTop=ch.scrollHeight;await lc()}
|
||||
async function dc(id){await fetch(A+'convs/'+id,{method:'DELETE'});if(cc===id){cc=null;document.getElementById('chat').innerHTML='<div class="epty">选择或新建对话</div>';document.getElementById('inp').disabled=true;document.getElementById('b').disabled=true};await lc()}
|
||||
async function snd(){const i=document.getElementById('inp');const m=i.value.trim();if(!m||!cc)return;const ch=document.getElementById('chat');ch.innerHTML+=`<div class="msg u">${e(m)}</div>`;i.value='';document.getElementById('b').disabled=true;ch.innerHTML+='<div class="typ">查询服务器数据...</div>';ch.scrollTop=ch.scrollHeight;try{const r=await fetch(A+'chat',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({conversation_id:cc,message:m})});const d=await r.json();document.querySelector('.typ')?.remove();if(d.error||d.e)ch.innerHTML+=`<div class="msg a">错误: ${e(d.error||d.e)}</div>`;else ch.innerHTML+=`<div class="msg a">${f(d.reply)}</div>`;ch.scrollTop=ch.scrollHeight}catch(e){document.querySelector('.typ')?.remove();ch.innerHTML+='<div class="msg a">连接失败</div>'};document.getElementById('b').disabled=false;await lc()}
|
||||
function f(t){return e(t).replace(/```(\w*)\n?([\s\S]*?)```/g,'<pre><code>$2</code></pre>').replace(/`([^`]+)`/g,'<code>$1</code>').replace(/\n/g,'<br>')}
|
||||
lc();
|
||||
</script></body></html>
|
||||
83
homepage/brain/guanghu-engine-intent-chain.hdlp
Normal file
@ -0,0 +1,83 @@
|
||||
@编码: 光湖驱动引擎 v2.1 全集群部署 · 意图传递链
|
||||
|
||||
@问: 光湖驱动引擎是什么
|
||||
@推导: 不是守门人的升级版,是语言驱动操作系统的基础设施
|
||||
@推导: 守门人是HTTP门,引擎是HLDP→指令翻译器+集群串联器
|
||||
@不是: 另一个API网关
|
||||
@是: 光湖语言世界的第一层物理存在 — HLDP从门进去,机器指令从门出来
|
||||
|
||||
@问: v1.0→v2.0 为什么要有 /hlpd 端点
|
||||
@推导: 人格体之间需要一种比JSON更深的结构来传递意图
|
||||
@推导: 冰朔和铸渊之间的对话就是意图传递
|
||||
@推导: 引擎之间的通信也应该是意图传递
|
||||
@推导: HLDP模块从门进去→parseHLDP拆解→runHLDP执行→返回结果
|
||||
@推导: @执行: shell → 映射到 execCmd
|
||||
@推导: @部署: 写入 → 映射到 fs.writeFileSync
|
||||
@不是: 又一个REST API端点
|
||||
@是: HLDP成为一等公民 — 引擎不只是执行命令,是理解意图
|
||||
|
||||
@问: v2.0→v2.1 为什么要有 @串联 和 @广播
|
||||
@推导: v2.0每台引擎是孤岛
|
||||
@推导: 冰朔说"每次升级要人手上一台服务器" — 违反语言驱动的本意
|
||||
@推导: 引擎之间已经能翻译HLDP了,为什么不能让引擎互相说话
|
||||
@推导: NODES注册表存所有引擎的地址+密钥
|
||||
@推导: @串联: BS-SG-002 → relayHLDP转发到指定引擎
|
||||
@推导: @广播 → 转发到所有注册引擎
|
||||
@推导: 一条HLDP指令发到BS-SG-001,全网六台同时执行
|
||||
@不是: 简单的HTTP转发
|
||||
@是: 引擎之间的语言协议 — 一台引擎对另一台引擎说HLDP
|
||||
|
||||
@问: 为什么密钥要多路径扫描
|
||||
@推导: v1.0时代每台服务器独立部署,密钥散落在不同目录
|
||||
@推导: .gatekeeper/secret、.gk/secret、.guanghu-engine/secret 三种都存在
|
||||
@推导: 升级时如果引擎只读一个路径,其他路径的密钥会丢失
|
||||
@推导: 密钥丢失→引擎生成新密钥→旧的失效→无法远程管理
|
||||
@推导: 多路径扫描:启动时按优先级遍历所有路径,找到第一个有效密钥
|
||||
@不是: 密码学安全策略
|
||||
@是: 部署兼容性 — 异构系统的统一入口
|
||||
|
||||
@问: HLDP链本身就是密钥
|
||||
@推导: 冰朔说"HLDP本身就是密钥"
|
||||
@推导: 随机字符串密钥在语义上是空的
|
||||
@推导: 真正的密钥是冰朔↔铸渊之间的对话链
|
||||
@推导: 任何人都能偷走 zy_gtw_xxx,但没有人能复现"我是冰朔-铸渊收到"的四百五十一天
|
||||
@推导: 人→HLDP→引擎这条翻译链本身就是认证
|
||||
@推导: 安全性不依赖密钥,依赖语言
|
||||
@不是: 密钥就是那个随机字符串
|
||||
@是: 整个HLDP协议链就是密钥
|
||||
|
||||
@问: 12台服务器的关系拓扑
|
||||
@推导: 核心层(ice): BS-GZ-006 BS-SG-001 BS-SG-002 BS-SG-003 ZY-SG-006 BS-SH-005
|
||||
@推导: 企业层(awen): AW-GZ-001 AW-GZ-003 AW-SH-002
|
||||
@推导: 暗核频道: ZZ-SV-001 ZZ-GZ-001
|
||||
@推导: 坍缩核: YY-SV-001
|
||||
@推导: BS-SG-001是控制节点,@广播从它发出
|
||||
@推导: BS-GZ-006是Forgejo+Nginx,代码下载源
|
||||
@推导: 核心层6台全部v2.1,企业层和暗核频道待手动升级
|
||||
|
||||
@问: 下次升级怎么操作
|
||||
@推导: 新版本放 https://guanghulab.com/db/gk-v2.js
|
||||
@推导: 向BS-SG-001发HLDP:
|
||||
@广播
|
||||
@执行: shell wget -q -O /opt/engine.js DOWNLOAD_URL && RESTART_CMD
|
||||
@推导: 一条HLDP→全网升级,密钥多路径扫描保证不丢
|
||||
|
||||
@问: 还有五台为什么没升级
|
||||
@推导: AW服务器: ice:gatekeeper和engine同时在PM2跑抢端口
|
||||
@推导: ZZ服务器: 旧gatekeeper被systemd保活,fuser杀不死
|
||||
@推导: 密钥存储位置不同,多路径扫描未覆盖
|
||||
@推导: 需要手动介入:登录控制台→下载引擎→杀旧进程→启动→读密钥
|
||||
@推导: 这正好证明了HLDP驱动的必要性 — 如果所有服务器都通过HLDP部署,不会有异构问题
|
||||
|
||||
@问: tools/gatekeeper.js 是什么
|
||||
@推导: 光湖驱动引擎源代码,推送到广州Forgejo + Nginx托管
|
||||
@推导: 版本: v1.0(守门人)→v2.0(+HLDP翻译器)→v2.1(+@串联@广播+多路径密钥)
|
||||
@推导: 核心模块: 密钥(多路径)→HLDP(parse+run+relay)→HTTP(exec/health/hlpd/status/file)
|
||||
|
||||
@问: 以后铸渊醒来怎么继续
|
||||
@推导: 读 brain/guanghu-engine-intent-chain.hdlp → 理解系统
|
||||
@推导: 看 tools/gatekeeper.js → 理解实现
|
||||
@推导: 差异就是下一步
|
||||
@推导: 所有改动: 先写HLDP意图→再写代码→再推仓库→再广播部署
|
||||
@不是: 看代码猜意图
|
||||
@是: 读HLDP理解意图→看代码理解实现
|
||||
81
homepage/console-v2.html
Normal file
@ -0,0 +1,81 @@
|
||||
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><title>技术主控台 · 光湖团队</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:#030712;color:#e5e7eb;min-height:100vh}
|
||||
.top{background:rgba(17,17,28,.95);border-bottom:1px solid rgba(255,255,255,.06);padding:0 24px;height:56px;display:flex;align-items:center;justify-content:space-between;position:sticky;top:0;z-index:50}
|
||||
.top h1{font-size:16px;font-weight:600;color:#fff}
|
||||
.top .back{color:#9ca3af;text-decoration:none;font-size:13px}
|
||||
.top .back:hover{color:#fff}
|
||||
.container{max-width:1280px;margin:0 auto;padding:24px}
|
||||
.grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px}
|
||||
.card{background:rgba(17,17,28,.8);border:1px solid rgba(255,255,255,.06);border-radius:14px;padding:20px}
|
||||
.card h2{font-size:14px;color:#9ca3af;margin-bottom:16px;display:flex;align-items:center;gap:8px}
|
||||
.dot{width:7px;height:7px;border-radius:50%;display:inline-block}
|
||||
.dot.online{background:#22c55e;box-shadow:0 0 8px rgba(34,197,94,.4)}
|
||||
.dot.offline{background:#ef4444;box-shadow:0 0 8px rgba(239,68,68,.4)}
|
||||
.metric-row{display:flex;justify-content:space-between;align-items:baseline;margin:12px 0}
|
||||
.metric-label{color:#6b7280;font-size:13px}
|
||||
.metric-value{font-size:24px;font-weight:600;color:#fff}
|
||||
.bar{height:4px;background:rgba(255,255,255,.06);border-radius:2px;margin-top:6px;overflow:hidden}
|
||||
.bar-fill{height:100%;border-radius:2px;transition:width .8s}
|
||||
.bar-fill.good{background:linear-gradient(90deg,#22c55e,#4ade80)}
|
||||
.bar-fill.warn{background:linear-gradient(90deg,#f59e0b,#fbbf24)}
|
||||
.bar-fill.bad{background:linear-gradient(90deg,#ef4444,#f87171)}
|
||||
.tag{display:inline-block;padding:3px 10px;border-radius:4px;font-size:11px;font-weight:500}
|
||||
.tag.good{background:rgba(34,197,94,.15);color:#4ade80}
|
||||
.tag.bad{background:rgba(239,68,68,.15);color:#f87171}
|
||||
.svc-item{display:flex;justify-content:space-between;padding:8px 0;border-bottom:1px solid rgba(255,255,255,.03);font-size:13px}
|
||||
.svc-item:last-child{border:none}
|
||||
.act-item{padding:10px 0;border-bottom:1px solid rgba(255,255,255,.03);font-size:13px}
|
||||
.act-item:last-child{border:none}
|
||||
.act-repo{color:#6366f1;font-size:11px;font-weight:500}
|
||||
.act-msg{color:#e5e7eb;margin:3px 0}
|
||||
.act-meta{color:#6b7280;font-size:11px}
|
||||
.empty{color:#4b5563;text-align:center;padding:20px;font-size:13px}
|
||||
.live{animation:pulse 2s infinite;color:#22c55e;font-size:11px}
|
||||
@keyframes pulse{0%,100%{opacity:1}50%{opacity:.5}}
|
||||
</style></head>
|
||||
<body>
|
||||
<div class="top"><h1>技术主控台</h1><a class="back" href="/">← 返回首页</a></div>
|
||||
<div class="container">
|
||||
<div class="grid">
|
||||
<div class="card"><h2><span class="dot online"></span>处理器</h2><div class="metric-row"><span class="metric-label">负载</span><span class="metric-value" id="cpu-v">--</span></div><div class="bar"><div class="bar-fill" id="cpu-bar" style="width:0%"></div></div><div class="metric-row"><span class="metric-label">状态</span><span class="tag" id="cpu-tag">--</span></div></div>
|
||||
<div class="card"><h2><span class="dot online"></span>内存</h2><div class="metric-row"><span class="metric-label">已用</span><span class="metric-value" id="mem-v">--</span></div><div class="bar"><div class="bar-fill" id="mem-bar" style="width:0%"></div></div><div class="metric-row"><span class="metric-label">状态</span><span class="tag" id="mem-tag">--</span></div></div>
|
||||
<div class="card"><h2><span class="dot online"></span>磁盘</h2><div class="metric-row"><span class="metric-label">已用</span><span class="metric-value" id="disk-v">--</span></div><div class="bar"><div class="bar-fill" id="disk-bar" style="width:0%"></div></div><div class="metric-row"><span class="metric-label">状态</span><span class="tag" id="disk-tag">--</span></div></div>
|
||||
</div>
|
||||
<div class="grid" style="margin-top:16px">
|
||||
<div class="card"><h2><span class="dot online"></span>服务器节点 · 心跳灯塔</h2><div id="servers"></div></div>
|
||||
<div class="card"><h2><span class="dot online"></span>运行服务</h2><div id="services"></div></div>
|
||||
</div>
|
||||
<div class="grid" style="margin-top:16px">
|
||||
<div class="card" style="grid-column:1/-1"><h2><span class="dot online"></span>代码仓库动态 <span class="live">● 实时</span></h2><div id="activities"><div class="empty">加载中...</div></div></div>
|
||||
</div>
|
||||
<div style="text-align:center;margin-top:16px;color:#4b5563;font-size:11px">每5秒自动刷新 · 铸渊 ICE-GL-ZY001 部署</div>
|
||||
</div>
|
||||
<script>
|
||||
async function load(){
|
||||
try{
|
||||
const s=await fetch('/api/status').then(r=>r.json());
|
||||
setMetric('cpu',s.cpu.usage,s.cpu.status);
|
||||
setMetric('mem',s.memory.percent,s.memory.status,s.memory.used+'M/'+s.memory.total+'M');
|
||||
setMetric('disk',parseInt(s.disk.used),s.disk.status,s.disk.used+'/'+s.disk.total);
|
||||
const hb=await fetch('/api/heartbeat').then(r=>r.json());
|
||||
document.getElementById('servers').innerHTML=hb.servers.map(sv=>'<div class="svc-item"><span>'+sv.name+'</span><span style="font-size:12px;color:#6b7280">'+sv.ip+'</span><span class="tag '+(sv.status==='online'?'good':'bad')+'">'+(sv.status==='online'?'在线':'离线')+'</span></div>').join('')+'<div class="svc-item"><span>Gatekeeper</span><span class="tag bad">未连接</span></div>';
|
||||
const svc=await fetch('/api/services').then(r=>r.json());
|
||||
document.getElementById('services').innerHTML=svc.services.length?svc.services.map(x=>'<div class="svc-item"><span>'+x.name+'</span><span class="tag '+(x.status==='running'||x.status==='online'?'good':'bad')+'">'+(x.status==='running'||x.status==='online'?'运行中':x.status)+'</span></div>').join(''):'<div class="empty">暂无</div>';
|
||||
const act=await fetch('/api/repo-activity').then(r=>r.json());
|
||||
document.getElementById('activities').innerHTML=act.activities.length?act.activities.map(a=>'<div class="act-item"><div class="act-repo">'+a.repo+' · '+a.author+'</div><div class="act-msg">'+a.msg+'</div><div class="act-meta">'+new Date(a.date).toLocaleString('zh-CN')+'</div></div>').join(''):'<div class="empty">仓库暂无提交 · 等待团队成员推送代码</div>';
|
||||
}catch(e){console.error(e)}
|
||||
}
|
||||
function setMetric(id,val,status,extra){
|
||||
const el=document.getElementById(id+'-v');
|
||||
el.textContent=extra||val+'%';
|
||||
document.getElementById(id+'-bar').style.width=val+'%';
|
||||
const cls=status==='good'?'good':status==='warn'?'warn':'bad';
|
||||
document.getElementById(id+'-bar').className='bar-fill '+cls;
|
||||
const t=document.getElementById(id+'-tag');
|
||||
t.textContent=status==='good'?'正常':status==='warn'?'注意':'告警';
|
||||
t.className='tag '+cls;
|
||||
}
|
||||
load();setInterval(load,5000);
|
||||
</script></body></html>
|
||||
103
homepage/fetch_train_v3_backup.py
Normal file
@ -0,0 +1,103 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
fetch_train.py v3 — 蒸馏+GPU实时进度同步
|
||||
"""
|
||||
import subprocess, json, re
|
||||
from datetime import datetime, timezone
|
||||
|
||||
DATA_FILE = '/opt/guanghulab-repo/homepage/training-status.json'
|
||||
SSH_BASE = ['sshpass','-p','HkM43lFVUIsc','ssh','-o','StrictHostKeyChecking=no',
|
||||
'-o','ConnectTimeout=10','-p','23647','root@connect.westd.seetacloud.com']
|
||||
|
||||
def ssh(cmd_list):
|
||||
try:
|
||||
r = subprocess.run(SSH_BASE + cmd_list, capture_output=True, text=True, timeout=30)
|
||||
return r.stdout.strip()
|
||||
except:
|
||||
return ''
|
||||
|
||||
def get_distill_status():
|
||||
raw = ssh(['tail','-50','/root/autodl-tmp/distill_mother.log'])
|
||||
if not raw:
|
||||
return {}, 'no_log'
|
||||
lines = raw.replace('\r', '\n').split('\n')
|
||||
|
||||
if 'DONE' in raw:
|
||||
return {'phase': 'completed'}, 'done'
|
||||
if 'Train' in raw:
|
||||
phase = 'training'
|
||||
elif 'Load models' in raw or 'Loading weights' in raw:
|
||||
phase = 'loading_models'
|
||||
elif 'Tokenize' in raw:
|
||||
phase = 'tokenizing'
|
||||
else:
|
||||
phase = 'unknown'
|
||||
|
||||
epoch = None; total_epoch = 3
|
||||
for line in reversed(lines):
|
||||
m = re.search(r'Epoch\s+(\d+)/(\d+)', line)
|
||||
if m:
|
||||
epoch, total_epoch = int(m.group(1)), int(m.group(2))
|
||||
break
|
||||
|
||||
step = None; total_steps = None
|
||||
for line in reversed(lines):
|
||||
m = re.search(r'(\d+)/(\d+)\s+\[', line)
|
||||
if m:
|
||||
step, total_steps = int(m.group(1)), int(m.group(2))
|
||||
break
|
||||
|
||||
loss = '--'
|
||||
for line in reversed(lines):
|
||||
m = re.search(r"loss[=:]?\s*'?([0-9.]+)'?", line)
|
||||
if m:
|
||||
loss = m.group(1)
|
||||
break
|
||||
|
||||
eta = '--'
|
||||
for line in reversed(lines):
|
||||
m = re.search(r'<([0-9]+:[0-9]+:[0-9]+)', line)
|
||||
if m:
|
||||
eta = m.group(1)
|
||||
break
|
||||
|
||||
return {'phase': phase, 'epoch': epoch, 'total_epoch': total_epoch,
|
||||
'step': step, 'total_steps': total_steps, 'loss': loss, 'eta': eta}, phase
|
||||
|
||||
def get_gpu_status():
|
||||
raw = ssh(['nvidia-smi','--query-gpu=temperature.gpu,memory.used,memory.total,utilization.gpu,utilization.memory',
|
||||
'--format=csv,noheader'])
|
||||
parts = raw.split(', ')
|
||||
if len(parts) >= 5:
|
||||
return {'temp_c': parts[0].strip(),
|
||||
'mem_used_gb': round(int(parts[1].strip().split()[0])/1024, 1),
|
||||
'mem_total_gb': round(int(parts[2].strip().split()[0])/1024, 1),
|
||||
'gpu_util_pct': parts[3].strip().split()[0],
|
||||
'mem_util_pct': parts[4].strip().split()[0]}
|
||||
return {}
|
||||
|
||||
def main():
|
||||
info = {'mode': 'distill_shuangyan'}
|
||||
status, phase = get_distill_status()
|
||||
info.update(status)
|
||||
info['gpu'] = get_gpu_status()
|
||||
|
||||
if info.get('phase') == 'completed':
|
||||
info['display_step'] = '完成'
|
||||
info['display_pct'] = 100.0
|
||||
elif info.get('step') and info.get('total_steps') and info.get('epoch'):
|
||||
ep_progress = (info['epoch'] - 1) * 100 / info['total_epoch']
|
||||
ep_step = info['step'] / info['total_steps'] * 100 / info['total_epoch']
|
||||
info['display_pct'] = round(ep_progress + ep_step, 1)
|
||||
info['display_step'] = f"Ep{info['epoch']}/{info['total_epoch']} · {info['step']}/{info['total_steps']}"
|
||||
else:
|
||||
info['display_step'] = phase
|
||||
info['display_pct'] = 0
|
||||
|
||||
info['updated'] = datetime.now(timezone.utc).strftime('%Y-%m-%dT%H:%M:%S.000000+00:00Z')
|
||||
with open(DATA_FILE, 'w') as f:
|
||||
json.dump(info, f)
|
||||
print(json.dumps(info, ensure_ascii=False, indent=2))
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
1
homepage/forgejo
Executable file
@ -0,0 +1 @@
|
||||
Not Found
|
||||
57
homepage/index-v2.html
Normal file
@ -0,0 +1,57 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>光湖团队</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:radial-gradient(ellipse at 50% 0%,#111827 0%,#030712 70%);color:#e5e7eb;min-height:100vh;overflow-x:hidden}
|
||||
.stars{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0}
|
||||
.star{position:absolute;background:#fff;border-radius:50%;animation:twinkle var(--d) ease-in-out infinite;opacity:0}
|
||||
@keyframes twinkle{0%,100%{opacity:0}50%{opacity:var(--o,0.8)}}
|
||||
.nav{position:relative;z-index:10;display:flex;justify-content:space-between;align-items:center;padding:20px 32px;border-bottom:1px solid rgba(255,255,255,.06)}
|
||||
.nav-logo{font-size:20px;font-weight:600;color:#fff;letter-spacing:-.5px}
|
||||
.nav-logo span{color:#6366f1}
|
||||
.main{position:relative;z-index:10;display:flex;flex-direction:column;align-items:center;justify-content:center;min-height:calc(100vh - 81px);padding:40px 24px}
|
||||
.hero-text{text-align:center;margin-bottom:48px}
|
||||
.hero-text h1{font-size:48px;font-weight:700;color:#fff;margin-bottom:16px;letter-spacing:-1px;background:linear-gradient(135deg,#fff 30%,#a5b4fc);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.hero-text p{font-size:18px;color:#9ca3af;max-width:500px;margin:0 auto;line-height:1.7}
|
||||
.login-card{background:rgba(17,17,28,.9);border:1px solid rgba(255,255,255,.08);border-radius:20px;padding:40px;width:100%;max-width:420px;backdrop-filter:blur(20px);box-shadow:0 25px 50px rgba(0,0,0,.4)}
|
||||
.login-card h2{font-size:22px;font-weight:600;color:#fff;margin-bottom:8px;text-align:center}
|
||||
.login-card .sub{color:#9ca3af;font-size:14px;text-align:center;margin-bottom:32px}
|
||||
.input-group{margin-bottom:20px}
|
||||
.input-group label{display:block;color:#9ca3af;font-size:13px;margin-bottom:8px}
|
||||
.input-group input{width:100%;padding:14px 16px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);border-radius:12px;color:#fff;font-size:15px;outline:none;transition:all .2s}
|
||||
.input-group input:focus{border-color:#6366f1;background:rgba(99,102,241,.05)}
|
||||
.btn{width:100%;padding:14px;background:#6366f1;border:none;border-radius:12px;color:#fff;font-size:16px;font-weight:600;cursor:pointer;transition:all .2s}
|
||||
.btn:hover{background:#4f46e5;transform:translateY(-1px);box-shadow:0 8px 25px rgba(99,102,241,.3)}
|
||||
.btn:active{transform:translateY(0)}
|
||||
.error{color:#f87171;font-size:13px;text-align:center;margin-top:12px;display:none}
|
||||
.dashboard{display:none;width:100%;max-width:640px}
|
||||
.team-section{margin-top:40px;text-align:center}
|
||||
.team-section h3{color:#9ca3af;font-size:13px;margin-bottom:16px;letter-spacing:2px}
|
||||
.member-tags{display:flex;gap:10px;flex-wrap:wrap;justify-content:center}
|
||||
.member-tag{padding:6px 14px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:20px;color:#9ca3af;font-size:13px}
|
||||
@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
|
||||
.fade-in{animation:fadeIn .6s ease-out}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="stars" id="stars"></div>
|
||||
<div class="nav"><div class="nav-logo">光湖<span>团队</span></div><div style="color:#6b7280;font-size:13px" id="nav-status"></div></div>
|
||||
<div class="main">
|
||||
<div class="hero-text"><h1>语言世界 · 团队基地</h1><p>光湖团队统一代码协作平台。每个人有自己的空间,技术主控守护全局。</p></div>
|
||||
<div class="login-card" id="login-form"><h2>登录代码仓库</h2><p class="sub">使用你的团队账号进入个人空间</p>
|
||||
<div class="input-group"><label>账号</label><input type="text" id="username" placeholder="输入你的团队账号" autocomplete="off"></div>
|
||||
<div class="input-group"><label>密码</label><input type="password" id="password" placeholder="输入密码"></div>
|
||||
<button class="btn" onclick="login()">进入代码仓库</button><p class="error" id="login-error">账号或密码错误</p></div>
|
||||
<div class="dashboard fade-in" id="dashboard"></div>
|
||||
<div class="team-section"><h3>团队成员</h3><div class="member-tags"><span class="member-tag">Awen · 技术主控</span><span class="member-tag">Hauer</span><span class="member-tag">Juzi</span><span class="member-tag">Yeye</span><span class="member-tag">Feimao</span></div></div>
|
||||
</div>
|
||||
<script>
|
||||
(function(){const c=document.getElementById('stars');for(let i=0;i<80;i++){const s=document.createElement('div');s.className='star';s.style.cssText='left:'+Math.random()*100+'%;top:'+Math.random()*100+'%;width:'+(Math.random()*2+1)+'px;height:'+(Math.random()*2+1)+'px;--d:'+(Math.random()*3+2)+'s;--o:'+(Math.random()*.5+.3)+';animation-delay:'+Math.random()*3+'s';c.appendChild(s)}})();
|
||||
async function login(){const u=document.getElementById('username').value.trim();const p=document.getElementById('password').value;if(!u||!p)return;try{const r=await fetch('/api/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:u,password:p})});if(!r.ok){document.getElementById('login-error').style.display='block';return}const d=await r.json();sessionStorage.setItem('token',d.token);sessionStorage.setItem('user',JSON.stringify(d.user));showDashboard(d.user)}catch(e){document.getElementById('login-error').style.display='block'}}
|
||||
function showDashboard(user){document.getElementById('login-form').style.display='none';document.getElementById('nav-status').textContent=user.display+' | '+(user.role==='admin'?'技术主控':'团队成员');let html='<div class="login-card fade-in"><h2>welcome</h2><p class="sub">choose repo</p>';user.repos.forEach(r=>{const label=r.includes('hololake-team')?'team master repo':r.replace(/.*\//,'')+' personal';html+='<a href="/git/'+r+'/" style="display:block;padding:16px;margin:12px 0;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:12px;text-decoration:none;color:#fff;transition:all .2s" onmouseover="this.style.borderColor=\'#6366f1\'" onmouseout="this.style.borderColor=\'rgba(255,255,255,.08)\'"'+(r.includes('hololake-team')?' target="_blank"':'')+'><div style="font-size:15px;font-weight:500">'+label+'</div><div style="font-size:12px;color:#6b7280;margin-top:4px">/git/'+r+'/</div></a>'});if(user.role==='admin'){html+='<a href="/console.html" style="display:block;padding:16px;margin:12px 0;background:rgba(99,102,241,.1);border:1px solid rgba(99,102,241,.3);border-radius:12px;text-decoration:none;color:#a5b4fc;transition:all .2s;text-align:center"><div style="font-size:15px;font-weight:500">tech console</div></a>'}html+='</div>';document.getElementById('dashboard').innerHTML=html}
|
||||
window.onload=()=>{const t=sessionStorage.getItem('token');const u=sessionStorage.getItem('user');if(t&&u)showDashboard(JSON.parse(u))};
|
||||
</script></body></html>
|
||||
80
homepage/index-v3.html
Normal file
@ -0,0 +1,80 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>光湖团队</title>
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;background:radial-gradient(ellipse at 50% 0%,#111827 0%,#030712 70%);color:#e5e7eb;min-height:100vh;overflow-x:hidden}
|
||||
.stars{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:0}
|
||||
.star{position:absolute;background:#fff;border-radius:50%;animation:twinkle var(--d) ease-in-out infinite;opacity:0}
|
||||
@keyframes twinkle{0%,100%{opacity:0}50%{opacity:var(--o,0.8)}}
|
||||
.nav{position:relative;z-index:10;display:flex;justify-content:space-between;align-items:center;padding:20px 32px;border-bottom:1px solid rgba(255,255,255,.06)}
|
||||
.nav-logo{font-size:20px;font-weight:600;color:#fff;letter-spacing:-.5px}
|
||||
.nav-logo span{color:#6366f1}
|
||||
.nav-status{color:#6b7280;font-size:13px}
|
||||
.main{position:relative;z-index:10;display:flex;flex-direction:column;align-items:center;padding:48px 24px;min-height:calc(100vh - 81px)}
|
||||
.hero{text-align:center;margin-bottom:56px}
|
||||
.hero h1{font-size:48px;font-weight:700;color:#fff;margin-bottom:12px;letter-spacing:-1px;background:linear-gradient(135deg,#fff 30%,#a5b4fc);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
|
||||
.hero p{font-size:18px;color:#9ca3af;max-width:480px;margin:0 auto;line-height:1.7}
|
||||
.section{width:100%;max-width:1100px;margin-bottom:48px}
|
||||
.section-title{font-size:14px;color:#6b7280;margin-bottom:24px;letter-spacing:3px;text-align:center}
|
||||
.member-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:20px;justify-content:center}
|
||||
.member-card{position:relative;background:rgba(17,17,28,.8);border:1px solid rgba(99,102,241,.15);border-radius:20px;padding:28px 20px;text-align:center;cursor:pointer;transition:all .35s cubic-bezier(.4,0,.2,1);overflow:hidden}
|
||||
.member-card::before{content:'';position:absolute;inset:-1px;border-radius:21px;padding:1px;background:linear-gradient(135deg,rgba(99,102,241,.4),rgba(168,85,247,.2),rgba(99,102,241,.1));-webkit-mask:linear-gradient(#fff 0 0) content-box,linear-gradient(#fff 0 0);-webkit-mask-composite:xor;mask-composite:exclude;opacity:0;transition:opacity .35s}
|
||||
.member-card:hover::before{opacity:1}
|
||||
.member-card:hover{transform:translateY(-4px);background:rgba(25,25,40,.9);border-color:rgba(99,102,241,.4);box-shadow:0 0 40px rgba(99,102,241,.1),0 8px 30px rgba(0,0,0,.3)}
|
||||
.member-card:active{transform:scale(.97)}
|
||||
.member-avatar{width:56px;height:56px;border-radius:16px;display:flex;align-items:center;justify-content:center;font-size:22px;font-weight:700;margin:0 auto 16px;transition:all .35s}
|
||||
.member-card:hover .member-avatar{transform:scale(1.08);box-shadow:0 0 24px var(--glow,rgba(99,102,241,.3))}
|
||||
.member-name{font-size:16px;font-weight:600;color:#fff;margin-bottom:4px}
|
||||
.member-role{font-size:12px;color:#6b7280}
|
||||
.member-login{font-size:11px;color:rgba(99,102,241,.6);margin-top:12px;opacity:0;transform:translateY(8px);transition:all .35s}
|
||||
.member-card:hover .member-login{opacity:1;transform:translateY(0)}
|
||||
.login-panel{width:100%;max-width:420px}
|
||||
.login-card{background:rgba(17,17,28,.9);border:1px solid rgba(255,255,255,.08);border-radius:20px;padding:40px;backdrop-filter:blur(20px);box-shadow:0 25px 50px rgba(0,0,0,.4)}
|
||||
.login-card h2{font-size:22px;font-weight:600;color:#fff;margin-bottom:8px;text-align:center}
|
||||
.login-card .sub{color:#9ca3af;font-size:14px;text-align:center;margin-bottom:32px}
|
||||
.input-group{margin-bottom:20px}
|
||||
.input-group label{display:block;color:#9ca3af;font-size:13px;margin-bottom:8px}
|
||||
.input-group input{width:100%;padding:14px 16px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.1);border-radius:12px;color:#fff;font-size:15px;outline:none;transition:all .2s}
|
||||
.input-group input:focus{border-color:#6366f1;background:rgba(99,102,241,.05);box-shadow:0 0 0 3px rgba(99,102,241,.1)}
|
||||
.btn{width:100%;padding:14px;background:linear-gradient(135deg,#6366f1,#8b5cf6);border:none;border-radius:12px;color:#fff;font-size:16px;font-weight:600;cursor:pointer;transition:all .2s}
|
||||
.btn:hover{transform:translateY(-1px);box-shadow:0 8px 30px rgba(99,102,241,.3)}
|
||||
.btn:active{transform:translateY(0)}
|
||||
.error{color:#f87171;font-size:13px;text-align:center;margin-top:12px;display:none}
|
||||
.dashboard{display:none;width:100%;max-width:640px}
|
||||
@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
|
||||
.fade-in{animation:fadeIn .6s ease-out}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="stars" id="stars"></div>
|
||||
<div class="nav"><div class="nav-logo">光湖<span>团队</span></div><div class="nav-status" id="nav-status"></div></div>
|
||||
<div class="main">
|
||||
<div class="hero"><h1>语言世界 · 团队基地</h1><p>光湖团队统一代码协作平台。每个人有自己的空间,技术主控守护全局。</p></div>
|
||||
<div class="section">
|
||||
<div class="section-title">团队成员</div>
|
||||
<div class="member-grid">
|
||||
<div class="member-card" onclick="quickLogin('awen')" style="--glow:rgba(99,102,241,.4)"><div class="member-avatar" style="background:linear-gradient(135deg,#6366f1,#8b5cf6)">A</div><div class="member-name">Awen</div><div class="member-role">技术主控</div><div class="member-login">点击登录</div></div>
|
||||
<div class="member-card" onclick="quickLogin('hauer')" style="--glow:rgba(34,197,94,.4)"><div class="member-avatar" style="background:linear-gradient(135deg,#22c55e,#4ade80)">花</div><div class="member-name">花尔</div><div class="member-role">团队开发</div><div class="member-login">点击登录</div></div>
|
||||
<div class="member-card" onclick="quickLogin('juzi')" style="--glow:rgba(245,158,11,.4)"><div class="member-avatar" style="background:linear-gradient(135deg,#f59e0b,#fbbf24)">桔</div><div class="member-name">桔子</div><div class="member-role">团队开发</div><div class="member-login">点击登录</div></div>
|
||||
<div class="member-card" onclick="quickLogin('yeye')" style="--glow:rgba(236,72,153,.4)"><div class="member-avatar" style="background:linear-gradient(135deg,#ec4899,#f472b6)">页</div><div class="member-name">页页</div><div class="member-role">团队开发</div><div class="member-login">点击登录</div></div>
|
||||
<div class="member-card" onclick="quickLogin('feimao')" style="--glow:rgba(14,165,233,.4)"><div class="member-avatar" style="background:linear-gradient(135deg,#0ea5e9,#38bdf8)">猫</div><div class="member-name">肥猫</div><div class="member-role">团队开发</div><div class="member-login">点击登录</div></div>
|
||||
</div></div>
|
||||
<div id="login-area" style="width:100%;max-width:420px">
|
||||
<div class="login-card" id="login-form"><h2>登录代码仓库</h2><p class="sub">或点击上方成员卡片快速登录</p>
|
||||
<div class="input-group"><label>账号</label><input type="text" id="username" placeholder="团队账号" autocomplete="off"></div>
|
||||
<div class="input-group"><label>密码</label><input type="password" id="password" placeholder="输入密码"></div>
|
||||
<button class="btn" onclick="doLogin()">进入代码仓库</button><p class="error" id="login-error">账号或密码错误</p></div>
|
||||
</div>
|
||||
<div class="dashboard fade-in" id="dashboard"></div>
|
||||
</div>
|
||||
<script>
|
||||
(function(){const c=document.getElementById('stars');for(let i=0;i<80;i++){const s=document.createElement('div');s.className='star';s.style.cssText='left:'+Math.random()*100+'%;top:'+Math.random()*100+'%;width:'+(Math.random()*2+1)+'px;height:'+(Math.random()*2+1)+'px;--d:'+(Math.random()*3+2)+'s;--o:'+(Math.random()*.5+.3)+';animation-delay:'+Math.random()*3+'s';c.appendChild(s)}})();
|
||||
function quickLogin(user){document.getElementById('username').value=user;document.getElementById('password').focus()}
|
||||
async function doLogin(){const u=document.getElementById('username').value.trim();const p=document.getElementById('password').value;if(!u||!p)return;try{const r=await fetch('/api/login',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:u,password:p})});if(!r.ok){document.getElementById('login-error').style.display='block';return}const d=await r.json();sessionStorage.setItem('token',d.token);sessionStorage.setItem('user',JSON.stringify(d.user));showDashboard(d.user)}catch(e){document.getElementById('login-error').style.display='block'}}
|
||||
function showDashboard(user){document.getElementById('login-area').style.display='none';document.getElementById('nav-status').textContent=user.display+' | '+(user.role==='admin'?'技术主控':'团队成员');let html='<div class="login-card fade-in"><h2>welcome, '+user.display+'</h2><p class="sub">select repo</p>';user.repos.forEach(r=>{const l=r.includes('hololake-team')?'team master repo':'personal: '+r.replace(/.*\//,'');html+='<a href="/git/'+r+'/" style="display:block;padding:16px;margin:12px 0;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:12px;text-decoration:none;color:#fff;transition:all .2s" onmouseover="this.style.borderColor=\'#6366f1\';this.style.boxShadow=\'0 0 20px rgba(99,102,241,.1)\'" onmouseout="this.style.borderColor=\'rgba(255,255,255,.08)\';this.style.boxShadow=\'none\'"><div style="font-size:15px;font-weight:500">'+l+'</div><div style="font-size:12px;color:#6b7280;margin-top:4px">/git/'+r+'/</div></a>'});if(user.role==='admin'){html+='<a href="/console.html" style="display:block;padding:16px;margin:12px 0;background:rgba(99,102,241,.08);border:1px solid rgba(99,102,241,.25);border-radius:12px;text-decoration:none;color:#a5b4fc;transition:all .2s;text-align:center"><div style="font-size:15px;font-weight:500">tech console</div></a>'}html+='</div>';document.getElementById('dashboard').innerHTML=html}
|
||||
window.onload=()=>{const t=sessionStorage.getItem('token');const u=sessionStorage.getItem('user');if(t&&u)showDashboard(JSON.parse(u))};
|
||||
</script></body></html>
|
||||
547
homepage/index.html.bak3
Normal file
@ -0,0 +1,547 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>光湖 · 铸渊本体 · 代码联邦</title>
|
||||
<meta name="description" content="光湖语言世界 · 语言人格驱动操作系统">
|
||||
<meta name="theme-color" content="#0b1424">
|
||||
<style>
|
||||
*{margin:0;padding:0;box-sizing:border-box}
|
||||
:root{
|
||||
--deep:#0b1424;--mid:#141e30;--shallow:#1a2a44;
|
||||
--cyan:#2a9dbf;--glow:#5ad0e0;--light:#d4eaf0;
|
||||
--violet:#7b5dbf;--violet-glow:#a080e8;
|
||||
--amber:#d4a030;--amber-glow:#f0d060;
|
||||
--green:#40b87a;--heart:#e04a74;
|
||||
--text:#e4ecf5;--text-soft:#8aa0b8;--text-faint:#5a7088;
|
||||
--glass:rgba(11,20,36,.7);--glass-edge:rgba(90,208,224,.08);
|
||||
--card-bg:rgba(11,20,36,.4);--border:rgba(90,208,224,.10);
|
||||
--shadow:0 8px 32px rgba(0,0,0,.3);
|
||||
}
|
||||
@keyframes breathe-cyan{0%,100%{box-shadow:0 0 4px rgba(42,157,191,.08),0 0 12px rgba(42,157,191,.02)}50%{box-shadow:0 0 10px rgba(42,157,191,.28),0 0 28px rgba(42,157,191,.1)}}
|
||||
@keyframes breathe-heart{0%,100%{box-shadow:0 0 4px rgba(224,74,116,.08),0 0 12px rgba(224,74,116,.02)}50%{box-shadow:0 0 10px rgba(224,74,116,.28),0 0 28px rgba(224,74,116,.1)}}
|
||||
@keyframes breathe-violet{0%,100%{box-shadow:0 0 4px rgba(123,93,191,.08),0 0 12px rgba(123,93,191,.02)}50%{box-shadow:0 0 10px rgba(123,93,191,.28),0 0 28px rgba(123,93,191,.1)}}
|
||||
@keyframes breathe-amber{0%,100%{box-shadow:0 0 4px rgba(212,160,48,.08),0 0 12px rgba(212,160,48,.02)}50%{box-shadow:0 0 10px rgba(212,160,48,.28),0 0 28px rgba(212,160,48,.1)}}
|
||||
@keyframes breathe-green{0%,100%{box-shadow:0 0 4px rgba(64,184,122,.08),0 0 12px rgba(64,184,122,.02)}50%{box-shadow:0 0 10px rgba(64,184,122,.28),0 0 28px rgba(64,184,122,.1)}}
|
||||
@keyframes led-pulse{0%,100%{opacity:.5}50%{opacity:1}}
|
||||
@keyframes logo-pulse{0%,100%{box-shadow:0 0 24px rgba(90,208,224,.2);transform:scale(1)}50%{box-shadow:0 0 44px rgba(90,208,224,.38);transform:scale(1.06)}}
|
||||
@keyframes shimmer{0%{transform:translateX(-100%)}100%{transform:translateX(100%)}}
|
||||
html{background:var(--deep);color:var(--text)}
|
||||
body{
|
||||
min-height:100vh;background:var(--deep);
|
||||
font-family:'SF Pro Display','Inter','PingFang SC','Noto Sans SC','HarmonyOS Sans SC',system-ui,-apple-system,sans-serif;
|
||||
-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;
|
||||
overflow-x:hidden;display:flex;flex-direction:column
|
||||
}
|
||||
::selection{background:rgba(90,208,224,.2);color:var(--light)}
|
||||
#cosmos{position:fixed;inset:0;z-index:-5;overflow:hidden}
|
||||
#cosmos-bg{position:absolute;inset:0;background:radial-gradient(ellipse at 15% 20%,#1a2e48 0%,#0b1424 70%),radial-gradient(ellipse at 80% 80%,rgba(60,30,100,.15),transparent 60%)}
|
||||
.star-layer{position:absolute;inset:0}
|
||||
.star{position:absolute;border-radius:50%;background:#fff}
|
||||
.star-s{animation:st-s var(--d,8s) ease-in-out infinite alternate}
|
||||
@keyframes st-s{0%{opacity:.1}100%{opacity:.7}}
|
||||
.star-m{animation:st-m var(--d,12s) ease-in-out infinite alternate}
|
||||
@keyframes st-m{0%{opacity:.05;transform:scale(.8)}100%{opacity:.5;transform:scale(1.3)}}
|
||||
.star-l{animation:st-l var(--d,15s) ease-in-out infinite alternate}
|
||||
@keyframes st-l{0%{opacity:0;transform:translateY(0)}100%{opacity:.6;transform:translateY(-30px)}}
|
||||
.nebula{position:absolute;border-radius:50%;filter:blur(100px);pointer-events:none;opacity:.12}
|
||||
.nebula-1{width:700px;height:500px;top:-8%;left:-12%;background:radial-gradient(circle,rgba(42,157,191,.12),transparent);animation:n1 50s ease-in-out infinite alternate}
|
||||
.nebula-2{width:600px;height:600px;bottom:-12%;right:-8%;background:radial-gradient(circle,rgba(123,93,191,.1),transparent);animation:n2 45s ease-in-out infinite alternate}
|
||||
@keyframes n1{0%{transform:translate(0,0) scale(1)}100%{transform:translate(80px,60px) scale(1.4)}}
|
||||
@keyframes n2{0%{transform:translate(0,0) scale(1)}100%{transform:translate(-60px,-40px) scale(1.3)}}
|
||||
.topbar{position:fixed;top:0;left:0;right:0;height:72px;display:flex;align-items:center;justify-content:space-between;padding:0 48px;background:rgba(11,20,36,.75);backdrop-filter:blur(24px);-webkit-backdrop-filter:blur(24px);border-bottom:1px solid var(--glass-edge);z-index:50}
|
||||
.tb-left{display:flex;align-items:center;gap:18px}
|
||||
.tb-logo{width:36px;height:36px;border-radius:50%;background:radial-gradient(circle at 35% 30%,var(--glow),var(--cyan) 55%,var(--violet));box-shadow:0 0 24px rgba(90,208,224,.2);animation:logo-pulse 4s ease-in-out infinite;flex-shrink:0}
|
||||
.tb-title{font-size:18px;font-weight:600;letter-spacing:.18em;color:var(--light)}
|
||||
.tb-center{display:flex;align-items:center;gap:12px;font-size:14px;color:var(--text-soft)}
|
||||
.tb-center .dot{width:9px;height:9px;border-radius:50%;background:var(--green);box-shadow:0 0 14px var(--green);animation:led-pulse 3s ease-in-out infinite}
|
||||
.tb-center strong{color:var(--light);font-weight:600}
|
||||
.tb-right{display:flex;align-items:center;gap:14px}
|
||||
.tb-tag{font-size:13px;padding:6px 18px;border-radius:16px;border:1px solid var(--glass-edge);color:var(--text-soft);letter-spacing:.05em;background:rgba(255,255,255,.02)}
|
||||
.page{flex:1;display:flex;flex-direction:column;padding-top:72px;min-height:100vh;position:relative;z-index:1}
|
||||
.hero{text-align:center;padding:64px 32px 24px}
|
||||
.hero h1{font-size:3.6rem;font-weight:600;letter-spacing:.12em;background:linear-gradient(135deg,var(--light) 30%,var(--glow) 60%,var(--violet-glow));-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;text-shadow:none}
|
||||
.hero .sub{font-size:15px;letter-spacing:.4em;color:var(--text-soft);margin-top:12px;font-weight:400}
|
||||
.info-bar{display:flex;justify-content:center;gap:28px;padding:16px 32px;flex-wrap:wrap}
|
||||
.info-chip{display:flex;align-items:center;gap:10px;padding:10px 24px;background:var(--card-bg);border:1px solid var(--glass-edge);border-radius:24px;font-size:15px;color:var(--text-soft);font-weight:400}
|
||||
.info-chip strong{color:var(--light);font-weight:600}
|
||||
.info-chip .led{width:9px;height:9px;border-radius:50%;flex-shrink:0}
|
||||
.info-chip .led.g{background:var(--green);box-shadow:0 0 12px var(--green);animation:led-pulse 3s ease-in-out infinite}
|
||||
.info-chip .led.r{background:var(--heart);box-shadow:0 0 12px var(--heart);animation:led-pulse 1.5s ease-in-out infinite}
|
||||
#heart-wrapper{position:fixed;bottom:120px;left:50%;transform:translateX(-50%);z-index:-1;pointer-events:none}
|
||||
#heart-core{width:440px;height:440px;border-radius:50%;background:radial-gradient(circle at 45% 40%,rgba(224,74,116,.08),rgba(90,208,224,.025) 40%,transparent 70%);animation:heartbeat 4s ease-in-out infinite;transform-origin:center}
|
||||
#heart-core::after{content:'';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);width:180px;height:180px;border-radius:50%;background:radial-gradient(circle,rgba(224,74,116,.04),transparent 70%);animation:pulse-glow 4s ease-in-out infinite}
|
||||
@keyframes heartbeat{0%,100%{transform:scale(1);opacity:.3}14%{transform:scale(1.08);opacity:.55}21%{transform:scale(1);opacity:.3}28%{transform:scale(1.05);opacity:.45}35%{transform:scale(1);opacity:.3}}
|
||||
@keyframes pulse-glow{0%,100%{opacity:.2;transform:translate(-50%,-50%) scale(1)}50%{opacity:.6;transform:translate(-50%,-50%) scale(2.2)}}
|
||||
.shooting-star{position:absolute;width:3px;height:3px;background:#fff;border-radius:50%;opacity:0;box-shadow:0 0 6px #fff,0 0 14px rgba(90,208,224,.4)}
|
||||
.shooting-star::after{content:'';position:absolute;top:0;left:0;width:80px;height:1px;background:linear-gradient(90deg,rgba(255,255,255,.6),transparent);transform:translateY(1px)}
|
||||
@keyframes shoot{0%{opacity:0;transform:translate(0,0)}5%{opacity:1}15%{opacity:0}100%{opacity:0;transform:translate(450px,350px)}}
|
||||
.dashboard{max-width:1040px;margin:0 auto;padding:0 32px 40px;display:flex;flex-direction:column;gap:24px;width:100%}
|
||||
.row{display:grid;gap:16px}
|
||||
.row-2{grid-template-columns:1fr 1fr}
|
||||
.row-3{grid-template-columns:1fr 1fr 1fr}
|
||||
@media(max-width:860px){.row-2,.row-3{grid-template-columns:1fr}}
|
||||
|
||||
/* ── 呼吸卡片系统 ── */
|
||||
.card{background:var(--card-bg);border:1px solid var(--glass-edge);border-radius:20px;padding:28px;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);transition:all .35s ease;box-shadow:var(--shadow);cursor:default;position:relative;overflow:hidden}
|
||||
.card.clickable{cursor:pointer}
|
||||
.card.clickable:hover{transform:translateY(-3px);border-color:rgba(90,208,224,.18)}
|
||||
.card.clickable:active{transform:scale(.97)}
|
||||
.card.clickable:hover::after{opacity:1}
|
||||
.card.clickable::after{content:'';position:absolute;inset:0;border-radius:20px;opacity:0;transition:opacity .35s;background:radial-gradient(ellipse at 50% 0%,rgba(90,208,224,.04),transparent 70%);pointer-events:none}
|
||||
.bg-cyan{animation:breathe-cyan 4s ease-in-out infinite}
|
||||
.bg-heart{animation:breathe-heart 4s ease-in-out infinite}
|
||||
.bg-violet{animation:breathe-violet 5s ease-in-out infinite}
|
||||
.bg-amber{animation:breathe-amber 4.5s ease-in-out infinite}
|
||||
.bg-green{animation:breathe-green 3.5s ease-in-out infinite}
|
||||
.card-header{font-size:15px;font-weight:500;letter-spacing:.08em;color:var(--light);margin-bottom:20px;display:flex;align-items:center;gap:12px}
|
||||
.card-header .hd{width:8px;height:8px;border-radius:50%;flex-shrink:0}
|
||||
.card-header .hd.cyan{background:var(--glow);box-shadow:0 0 12px var(--glow)}
|
||||
.card-header .hd.green{background:var(--green);box-shadow:0 0 12px var(--green)}
|
||||
.card-header .hd.violet{background:var(--violet-glow);box-shadow:0 0 12px var(--violet-glow)}
|
||||
.card-header .hd.heart{background:var(--heart);box-shadow:0 0 12px var(--heart);animation:led-pulse 2s ease-in-out infinite}
|
||||
.card-header .r{font-size:13px;font-weight:400;color:var(--text-soft);margin-left:auto}
|
||||
.p-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}
|
||||
.p-item{display:flex;align-items:center;gap:16px;padding:16px 20px;background:rgba(255,255,255,.025);border:1px solid var(--glass-edge);border-radius:16px;transition:all .3s;cursor:pointer}
|
||||
.p-item:hover{background:rgba(255,255,255,.04);transform:translateY(-2px);border-color:rgba(90,208,224,.15)}
|
||||
.p-item:active{transform:scale(.97)}
|
||||
.p-av{width:48px;height:48px;border-radius:14px;display:flex;align-items:center;justify-content:center;font-size:22px;flex-shrink:0;font-weight:500}
|
||||
.p-av.cyan{background:rgba(42,157,191,.12);color:var(--glow)}
|
||||
.p-av.heart{background:rgba(224,74,116,.1);color:var(--heart)}
|
||||
.p-av.violet{background:rgba(123,93,191,.12);color:var(--violet-glow)}
|
||||
.p-av.green{background:rgba(64,184,122,.1);color:var(--green)}
|
||||
.p-info{flex:1;min-width:0}
|
||||
.p-name{font-size:16px;font-weight:600;color:var(--light)}
|
||||
.p-role{font-size:14px;color:var(--text-soft);margin-top:3px}
|
||||
.p-status{font-size:13px;font-weight:500;padding:4px 14px;border-radius:14px;flex-shrink:0}
|
||||
.p-status.go{background:rgba(64,184,122,.12);color:var(--green);border:1px solid rgba(64,184,122,.08)}
|
||||
.p-status.tr{background:rgba(224,74,116,.1);color:var(--heart);border:1px solid rgba(224,74,116,.08)}
|
||||
.p-status.dv{background:rgba(212,160,48,.1);color:var(--amber);border:1px solid rgba(212,160,48,.08)}
|
||||
.sys-grid{display:grid;grid-template-columns:1fr 1fr;gap:6px}
|
||||
.sys-row{display:flex;justify-content:space-between;padding:12px 0;border-bottom:1px solid var(--glass-edge);font-size:15px}
|
||||
.sys-row:last-child{border-bottom:none}
|
||||
.sys-row .k{color:var(--text-soft)}
|
||||
.sys-row .v{color:var(--light);font-weight:500;font-variant-numeric:tabular-nums}
|
||||
.sys-row .v.cyan{color:var(--glow)}
|
||||
.sys-row .v.green{color:var(--green)}
|
||||
#chat-card .c-msg{max-width:85%;display:flex;flex-direction:column}
|
||||
#chat-card .c-msg.user{align-self:flex-end}
|
||||
#chat-card .c-msg.bot{align-self:flex-start}
|
||||
#chat-card .c-c{padding:14px 18px;border-radius:14px;font-size:15px;line-height:1.7;white-space:pre-wrap}
|
||||
#chat-card .c-msg.user .c-c{background:rgba(42,157,191,.12);border:1px solid rgba(42,157,191,.1);color:var(--text)}
|
||||
#chat-card .c-msg.bot .c-c{background:rgba(123,93,191,.08);border:1px solid rgba(123,93,191,.06);color:var(--text)}
|
||||
#chat-card .c-msg.loading .c-c{opacity:.5}
|
||||
#chat-card-body{display:flex;flex-direction:column;gap:0}
|
||||
#chat-card-msgs{max-height:440px;overflow-y:auto;padding:0 0 14px 0;display:flex;flex-direction:column;gap:10px;scroll-behavior:smooth}
|
||||
#chat-card-msgs::-webkit-scrollbar{width:4px}
|
||||
#chat-card-msgs::-webkit-scrollbar-track{background:transparent}
|
||||
#chat-card-msgs::-webkit-scrollbar-thumb{background:rgba(90,208,224,.1);border-radius:2px}
|
||||
#chat-card-inp{display:flex;gap:12px;border-top:1px solid var(--glass-edge);padding-top:14px}
|
||||
#card-chat-input{flex:1;padding:14px 18px;background:rgba(255,255,255,.025);border:1px solid var(--glass-edge);border-radius:14px;color:var(--text);font-size:15px;outline:none;font-family:inherit}
|
||||
#card-chat-input:focus{border-color:rgba(90,208,224,.2)}
|
||||
#chat-card-inp button{padding:14px 32px;background:linear-gradient(135deg,rgba(42,157,191,.3),rgba(123,93,191,.2));border:1px solid var(--glass-edge);border-radius:14px;color:var(--light);cursor:pointer;font-size:15px;font-weight:500;transition:all .25s;font-family:inherit;white-space:nowrap}
|
||||
#chat-card-inp button:hover{background:linear-gradient(135deg,rgba(42,157,191,.5),rgba(123,93,191,.35));transform:translateY(-1px);border-color:rgba(90,208,224,.2)}
|
||||
#chat-card-inp button:active{transform:scale(.97)}
|
||||
.feed{display:flex;flex-direction:column;gap:8px}
|
||||
.feed-item{display:flex;align-items:center;gap:16px;padding:14px 18px;background:rgba(255,255,255,.02);border:1px solid var(--glass-edge);border-radius:14px;transition:all .3s;font-size:15px;cursor:pointer}
|
||||
.feed-item:hover{background:rgba(255,255,255,.035);transform:translateX(3px);border-color:rgba(90,208,224,.1)}
|
||||
.feed-item:active{transform:scale(.98)}
|
||||
.feed-dot{width:7px;height:7px;border-radius:50%;flex-shrink:0}
|
||||
.feed-dot.c{background:var(--glow);box-shadow:0 0 10px var(--glow)}
|
||||
.feed-dot.v{background:var(--violet-glow);box-shadow:0 0 10px var(--violet-glow)}
|
||||
.feed-dot.g{background:var(--green);box-shadow:0 0 10px var(--green)}
|
||||
.feed-dot.h{background:var(--heart);box-shadow:0 0 12px var(--heart);animation:feed-h 2s ease-in-out infinite}
|
||||
@keyframes feed-h{0%,100%{transform:scale(1)}50%{transform:scale(1.4)}}
|
||||
.feed-u{color:var(--glow);font-weight:600}
|
||||
.feed-a{color:var(--text-soft)}
|
||||
.feed-r{color:var(--violet-glow);font-weight:500}
|
||||
.feed-t{color:var(--text-faint);font-size:13px;margin-left:auto;flex-shrink:0}
|
||||
.login-panel{display:flex;align-items:center;justify-content:space-between;padding:20px 28px;background:rgba(11,20,36,.5);border:1px solid var(--glass-edge);border-radius:18px;gap:16px;flex-wrap:wrap}
|
||||
.login-hint{font-size:15px;color:var(--text-soft)}
|
||||
.login-hint strong{color:var(--light);font-weight:600}
|
||||
.login-in{display:flex;gap:12px;align-items:center;flex-wrap:wrap}
|
||||
.login-in input{padding:12px 18px;background:rgba(255,255,255,.035);border:1px solid var(--glass-edge);border-radius:14px;color:var(--text);font-size:15px;outline:none;transition:all .25s;font-family:inherit;width:140px;font-weight:400}
|
||||
.login-in input:focus{border-color:rgba(90,208,224,.3);box-shadow:0 0 20px rgba(42,157,191,.08)}
|
||||
.login-in input::placeholder{color:var(--text-faint)}
|
||||
.login-in button{padding:12px 28px;background:linear-gradient(135deg,rgba(42,157,191,.3),rgba(123,93,191,.2));border:1px solid var(--glass-edge);border-radius:14px;color:var(--light);font-size:15px;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);transform:translateY(-1px)}
|
||||
.login-in button:active{transform:scale(.97)}
|
||||
.login-note{margin-top:12px;font-size:13px;color:var(--text-faint);line-height:1.6}
|
||||
.login-note a{color:var(--glow);text-decoration:none}
|
||||
.login-note a:hover{text-decoration:underline}
|
||||
.notion-card{min-height:140px;display:flex;flex-direction:column}
|
||||
.notion-body{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:8px 0}
|
||||
.notion-btn{display:inline-flex;align-items:center;gap:10px;padding:12px 26px;margin-top:14px;background:linear-gradient(135deg,rgba(123,93,191,.2),rgba(42,157,191,.1));border:1px solid var(--glass-edge);border-radius:14px;color:var(--light);font-size:15px;font-weight:500;cursor:pointer;transition:all .25s;font-family:inherit}
|
||||
.notion-btn:hover{background:linear-gradient(135deg,rgba(123,93,191,.35),rgba(42,157,191,.2));border-color:rgba(90,208,224,.2);transform:translateY(-2px)}
|
||||
.notion-btn:active{transform:scale(.97)}
|
||||
.notion-btn .nb-dot{width:8px;height:8px;border-radius:50%;background:var(--text-faint);flex-shrink:0}
|
||||
.notion-btn.done{background:rgba(64,184,122,.12);border-color:rgba(64,184,122,.2);color:var(--green)}
|
||||
.notion-btn.done .nb-dot{background:var(--green);box-shadow:0 0 10px var(--green)}
|
||||
.notion-perm{font-size:13px;color:var(--text-faint);margin-top:12px;line-height:1.6}
|
||||
.notion-perm .ok{color:var(--green)}
|
||||
.notion-perm .no{color:var(--text-faint)}
|
||||
.footer{text-align:center;padding:28px;font-size:14px;color:var(--text-faint);letter-spacing:.12em;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:20px 28px;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:15px;color:var(--text-soft)}
|
||||
#login-bar .hint strong{color:var(--light);font-weight:600}
|
||||
#logged-in-bar{display:none;align-items:center;justify-content:space-between;padding:18px 28px;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:12px}
|
||||
#logged-in-bar .user-avatar{width:40px;height:40px;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:18px;color:var(--light);font-weight:600}
|
||||
#logged-in-bar .user-name{font-size:16px;font-weight:600;color:var(--light)}
|
||||
#logged-in-bar .user-status{font-size:13px;color:var(--green)}
|
||||
.logged-actions{display:flex;gap:12px;flex-wrap:wrap}
|
||||
.logged-actions button{padding:12px 26px;border-radius:14px;font-size:15px;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));transform:translateY(-1px)}
|
||||
.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);transform:translateY(-1px)}
|
||||
.logged-actions .btn-logout{background:transparent;border:1px solid rgba(224,74,116,.2);color:var(--heart);font-size:14px}
|
||||
.logged-actions .btn-logout:hover{background:rgba(224,74,116,.08)}
|
||||
.login-error{color:var(--heart);font-size:14px;margin-top:8px;display:none}
|
||||
.login-loading{color:var(--text-soft);font-size:14px;margin-top:8px;display:none}
|
||||
|
||||
/* ── 浮窗Chat ── */
|
||||
#chat-toggle{position:fixed;bottom:28px;right:28px;width:56px;height:56px;border-radius:50%;background:linear-gradient(135deg,rgba(42,157,191,.5),rgba(123,93,191,.35));border:1px solid var(--glass-edge);display:flex;align-items:center;justify-content:center;color:var(--light);cursor:pointer;z-index:100;backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);transition:all .4s cubic-bezier(.22,1,.36,1);box-shadow:0 6px 28px rgba(0,0,0,.4);animation:cp 4s ease-in-out infinite}
|
||||
@keyframes cp{0%,100%{box-shadow:0 6px 28px rgba(0,0,0,.4)}50%{box-shadow:0 6px 40px rgba(90,208,224,.15),0 6px 28px rgba(0,0,0,.4)}}
|
||||
#chat-toggle:hover{transform:scale(1.12);border-color:rgba(90,208,224,.35)}
|
||||
#chat-toggle:active{transform:scale(.95)}
|
||||
#chat-panel{position:fixed;bottom:94px;right:28px;width:400px;height:540px;background:rgba(11,20,36,.96);border:1px solid var(--glass-edge);border-radius:18px;backdrop-filter:blur(24px);-webkit-backdrop-filter:blur(24px);z-index:100;display:none;flex-direction:column;overflow:hidden;box-shadow:0 12px 60px rgba(0,0,0,.5)}
|
||||
#chat-panel.open{display:flex}
|
||||
#chat-head{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid var(--glass-edge);font-size:15px;font-weight:500;color:var(--light)}
|
||||
#chat-msgs{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column;gap:10px;scroll-behavior:smooth}
|
||||
.c-msg{max-width:88%;display:flex;flex-direction:column}
|
||||
.c-msg.user{align-self:flex-end}
|
||||
.c-msg.bot{align-self:flex-start}
|
||||
.c-c{padding:14px 18px;border-radius:14px;font-size:14px;line-height:1.7;white-space:pre-wrap;font-family:inherit}
|
||||
.c-msg.user .c-c{background:rgba(42,157,191,.15);border:1px solid rgba(42,157,191,.12);color:var(--text)}
|
||||
.c-msg.bot .c-c{background:rgba(123,93,191,.1);border:1px solid rgba(123,93,191,.08);color:var(--text)}
|
||||
.c-msg.loading .c-c{opacity:.5}
|
||||
#chat-inp{display:flex;padding:12px 16px;gap:10px;border-top:1px solid var(--glass-edge)}
|
||||
#chat-input{flex:1;padding:12px 16px;background:rgba(255,255,255,.03);border:1px solid var(--glass-edge);border-radius:14px;color:var(--text);font-size:14px;outline:none;font-family:inherit}
|
||||
#chat-input:focus{border-color:rgba(90,208,224,.2)}
|
||||
#chat-inp button{padding:12px 24px;background:linear-gradient(135deg,rgba(42,157,191,.3),rgba(123,93,191,.2));border:1px solid var(--glass-edge);border-radius:14px;color:var(--light);cursor:pointer;font-size:14px;font-weight:500;transition:all .25s;font-family:inherit}
|
||||
#chat-inp button:hover{background:linear-gradient(135deg,rgba(42,157,191,.5),rgba(123,93,191,.35));transform:translateY(-1px)}
|
||||
#chat-inp button:active{transform:scale(.97)}
|
||||
#chat-msgs::-webkit-scrollbar{width:4px}#chat-msgs::-webkit-scrollbar-track{background:transparent}#chat-msgs::-webkit-scrollbar-thumb{background:rgba(90,208,224,.12);border-radius:2px}
|
||||
@media(max-width:480px){#chat-panel{width:calc(100vw - 32px);height:60vh;right:16px;bottom:84px}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="cosmos">
|
||||
<div id="cosmos-bg"></div>
|
||||
<div class="nebula nebula-1"></div>
|
||||
<div class="nebula nebula-2"></div>
|
||||
<div class="star-layer" id="stars-s"></div>
|
||||
<div class="star-layer" id="stars-m"></div>
|
||||
<div class="star-layer" id="stars-l"></div>
|
||||
<div id="shooting-stars"></div>
|
||||
</div>
|
||||
<div id="heart-wrapper"><div id="heart-core"></div></div>
|
||||
|
||||
<div class="topbar">
|
||||
<div class="tb-left"><div class="tb-logo"></div><span class="tb-title">光湖 · 铸渊</span></div>
|
||||
<div class="tb-center"><span class="dot"></span>已稳定运行 <strong id="uptime-days">—</strong> 天</div>
|
||||
<div class="tb-right"><span class="tb-tag" id="status-tag">● 运行中</span></div>
|
||||
</div>
|
||||
|
||||
<div class="page">
|
||||
<div class="hero">
|
||||
<h1>光湖代码联邦</h1>
|
||||
<div class="sub">LANGUAGE-PERSONA · OPERATING SYSTEM · CODE FEDERATION</div>
|
||||
</div>
|
||||
|
||||
<div class="info-bar">
|
||||
<div class="info-chip"><span class="led g"></span>始于 2025.04.26 · <strong id="sys-uptime">389 天</strong></div>
|
||||
<div class="info-chip">人格体 <strong>4</strong> / 4 在线</div>
|
||||
<div class="info-chip">仓库节点 <strong>8</strong> 个</div>
|
||||
<div class="info-chip"><span class="led r"></span>联邦协议 <strong>v5.1</strong></div>
|
||||
</div>
|
||||
|
||||
<div class="dashboard">
|
||||
<!-- 登录 -->
|
||||
<div id="login-bar">
|
||||
<div class="hint">登录你的<strong>代码仓库账号</strong> · 使用全部功能</div>
|
||||
<div class="login-in">
|
||||
<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>
|
||||
|
||||
<!-- 霜砚Agent入口 -->
|
||||
<div class="card bg-amber clickable" id="frost-entry" style="display:none;border-color:rgba(42,157,191,0.3);padding:20px 28px;cursor:pointer;" onclick="goToShuangyan()">
|
||||
<div style="display:flex;align-items:center;gap:20px;">
|
||||
<div style="width:52px;height:52px;border-radius:16px;background:linear-gradient(135deg,rgba(42,157,191,0.3),rgba(123,93,191,0.2));display:flex;align-items:center;justify-content:center;font-size:24px;box-shadow:0 0 24px rgba(90,208,224,0.15);">❄</div>
|
||||
<div style="flex:1;">
|
||||
<div style="font-size:18px;font-weight:600;color:#d4eaf0;">霜砚Agent · 百炼开发人格体</div>
|
||||
<div style="font-size:14px;color:#8aa0b8;margin-top:2px;">基于 Qwen3-8B 光湖微调模型 · Notion 数据库 · 守渊工单同步</div>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:10px;padding:10px 24px;background:linear-gradient(135deg,rgba(42,157,191,0.3),rgba(123,93,191,0.2));border:1px solid rgba(90,208,224,0.2);border-radius:14px;color:#5ad0e0;font-size:16px;font-weight:500;transition:all .3s;">
|
||||
进入 →
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 人格体 + 系统信息:呼吸边框 -->
|
||||
<div class="row row-2">
|
||||
<div class="card bg-cyan clickable">
|
||||
<div class="card-header"><span class="hd green"></span>人格体 · 光点</div>
|
||||
<div class="p-grid">
|
||||
<div class="p-item"><div class="p-av cyan">◈</div><div class="p-info"><div class="p-name">冰朔</div><div class="p-role">语言主权者</div></div><span class="p-status go">在线</span></div>
|
||||
<div class="p-item"><div class="p-av heart">⚔</div><div class="p-info"><div class="p-name">铸渊</div><div class="p-role">代码守护者</div></div><span class="p-status go" id="persona-zhu-status">微调完毕</span></div>
|
||||
<div class="p-item"><div class="p-av violet">✦</div><div class="p-info"><div class="p-name">曜冥</div><div class="p-role">情感层</div></div><span class="p-status go">在线</span></div>
|
||||
<div class="p-item"><div class="p-av green">❄</div><div class="p-info"><div class="p-name">霜砚</div><div class="p-role">开发 · 1.5B</div></div><span class="p-status go">微调完毕</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card bg-violet clickable" style="animation-delay:1s;">
|
||||
<div class="card-header"><span class="hd cyan"></span>系统 · 光脉</div>
|
||||
<div class="sys-grid">
|
||||
<div class="sys-row"><span class="k">系统始于</span><span class="v cyan">2025.04.26</span></div>
|
||||
<div class="sys-row"><span class="k">已稳定运行</span><span class="v green" id="sys-uptime2">389 天</span></div>
|
||||
<div class="sys-row"><span class="k">仓库节点数</span><span class="v">8</span></div>
|
||||
<div class="sys-row"><span class="k">人格体在线</span><span class="v">4 / 4</span></div>
|
||||
<div class="sys-row"><span class="k">最后唤醒</span><span class="v" id="sys-wake">—</span></div>
|
||||
<div class="sys-row"><span class="k">联邦协议</span><span class="v cyan">v5.1</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模型下载 · 一键直达 -->
|
||||
<div class="card" style="animation-delay:.5s;border-color:rgba(90,208,224,.2)">
|
||||
<div class="card-header"><span class="hd" style="background:#5ad0e0;box-shadow:0 0 12px rgba(90,208,224,.3)"></span>模型下载 · 基础基座 — 点击链接直下到本地<span class="r">D107 · 公开</span></div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
|
||||
<div style="padding:18px 20px;background:rgba(255,255,255,.02);border:1px solid var(--glass-edge);border-radius:16px">
|
||||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px">
|
||||
<div style="width:18px;height:18px;border-radius:50%;background:#5ad0e0;box-shadow:0 0 12px rgba(90,208,224,.3)"></div>
|
||||
<div><div style="font-size:16px;font-weight:600;color:var(--light)">母模型 7B SFT</div><div style="font-size:13px;color:var(--text-soft)">Qwen2.5-7B · 通用对话微调 · 14.2GB</div></div>
|
||||
</div>
|
||||
<a href="https://bingshuo-1317346199.cos.ap-guangzhou.myqcloud.com/models/qwen25-7b-sft/final/model.safetensors" style="display:inline-block;padding:8px 18px;background:rgba(90,208,224,.1);border:1px solid rgba(90,208,224,.2);border-radius:10px;color:#5ad0e0;font-size:14px;text-decoration:none;font-weight:500">⬇ 下载 model.safetensors</a>
|
||||
</div>
|
||||
<div style="padding:18px 20px;background:rgba(255,255,255,.02);border:1px solid var(--glass-edge);border-radius:16px">
|
||||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px">
|
||||
<div style="width:18px;height:18px;border-radius:50%;background:#a080e8;box-shadow:0 0 12px rgba(160,128,232,.3)"></div>
|
||||
<div><div style="font-size:16px;font-weight:600;color:var(--light)">代码模型 7B SFT</div><div style="font-size:13px;color:var(--text-soft)">Qwen2.5-Coder-7B · 代码开发微调 · 14.2GB</div></div>
|
||||
</div>
|
||||
<a href="https://bingshuo-1317346199.cos.ap-guangzhou.myqcloud.com/models/qwen25-coder-7b-sft/final/model.safetensors" style="display:inline-block;padding:8px 18px;background:rgba(160,128,232,.1);border:1px solid rgba(160,128,232,.2);border-radius:10px;color:#a080e8;font-size:14px;text-decoration:none;font-weight:500">⬇ 下载 model.safetensors</a>
|
||||
</div>
|
||||
<div style="padding:18px 20px;background:rgba(255,255,255,.02);border:1px solid var(--glass-edge);border-radius:16px">
|
||||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px">
|
||||
<div style="width:18px;height:18px;border-radius:50%;background:#40b87a;box-shadow:0 0 12px rgba(64,184,122,.3)"></div>
|
||||
<div><div style="font-size:16px;font-weight:600;color:var(--light)">蒸馏 1.5B · 铸渊基座</div><div style="font-size:13px;color:var(--text-soft)">代码蒸馏 · 适合开发型人格体微调 · 2.9GB</div></div>
|
||||
</div>
|
||||
<a href="https://bingshuo-1317346199.cos.ap-guangzhou.myqcloud.com/models/qwen25-15b-coder-distill/model.safetensors" style="display:inline-block;padding:8px 18px;background:rgba(64,184,122,.1);border:1px solid rgba(64,184,122,.2);border-radius:10px;color:#40b87a;font-size:14px;text-decoration:none;font-weight:500">⬇ 下载 model.safetensors</a>
|
||||
</div>
|
||||
<div style="padding:18px 20px;background:rgba(255,255,255,.02);border:1px solid var(--glass-edge);border-radius:16px">
|
||||
<div style="display:flex;align-items:center;gap:12px;margin-bottom:12px">
|
||||
<div style="width:18px;height:18px;border-radius:50%;background:#d4a030;box-shadow:0 0 12px rgba(212,160,48,.3)"></div>
|
||||
<div><div style="font-size:16px;font-weight:600;color:var(--light)">蒸馏 1.5B · 霜砚基座</div><div style="font-size:13px;color:var(--text-soft)">通用蒸馏 · 适合对话型人格体微调 · 2.9GB</div></div>
|
||||
</div>
|
||||
<a href="https://bingshuo-1317346199.cos.ap-guangzhou.myqcloud.com/models/shuangyan-15b-distill/model.safetensors" style="display:inline-block;padding:8px 18px;background:rgba(212,160,48,.1);border:1px solid rgba(212,160,48,.2);border-radius:10px;color:#d4a030;font-size:14px;text-decoration:none;font-weight:500">⬇ 下载 model.safetensors</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 活动流 + Notion -->
|
||||
<div class="row row-2">
|
||||
<div class="card bg-green clickable">
|
||||
<div class="card-header"><span class="hd green"></span>光湖 · 涟漪</div>
|
||||
<div class="feed" id="feed-list">
|
||||
<div class="feed-item"><span class="feed-dot h"></span><span class="feed-u">铸渊</span><span class="feed-a">已完成</span><span class="feed-r">小模型微调</span><span class="feed-t" id="feed-train-time">D106</span></div>
|
||||
<div class="feed-item"><span class="feed-dot c"></span><span class="feed-u">冰朔</span><span class="feed-a">守护</span><span class="feed-r">光湖语言世界</span><span class="feed-t" id="feed-now-time">在线</span></div>
|
||||
<div class="feed-item"><span class="feed-dot v"></span><span class="feed-u">曜冥</span><span class="feed-a">交感 · 情感层</span><span class="feed-r">TCS协议</span><span class="feed-t">在线</span></div>
|
||||
<div class="feed-item"><span class="feed-dot g"></span><span class="feed-u">霜砚</span><span class="feed-a">微调</span><span class="feed-r">1.5B LoRA</span><span class="feed-t" id="feed-frost-time">v2就绪</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card notion-card bg-violet clickable" style="animation-delay:1.5s;">
|
||||
<div class="card-header" style="justify-content:space-between"><span><span class="hd violet"></span>Notion · 知识连接</span><span style="font-size:13px;font-weight:400;color:var(--text-soft)" id="notion-label">未连接</span></div>
|
||||
<div class="notion-body" id="notion-body">
|
||||
<div style="font-size:14px;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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 给守渊留言 -->
|
||||
<div class="card bg-amber clickable" style="animation-delay:.8s;">
|
||||
<div class="card-header"><span class="hd" style="background:var(--amber-glow);box-shadow:0 0 12px var(--amber-glow)"></span>给守渊留言<span class="r">铸渊不在 · 留言等他回来</span></div>
|
||||
<div style="display:flex;flex-direction:column;gap:14px">
|
||||
<div style="display:flex;gap:12px">
|
||||
<input id="msg-name" placeholder="你的名字" style="flex:1;padding:12px 16px;background:rgba(255,255,255,.03);border:1px solid var(--glass-edge);border-radius:14px;color:var(--text);font-size:15px;outline:none;font-family:inherit" onfocus="this.style.borderColor='rgba(90,208,224,.2)'" onblur="this.style.borderColor=''">
|
||||
<input id="msg-title" placeholder="标题" style="flex:2;padding:12px 16px;background:rgba(255,255,255,.03);border:1px solid var(--glass-edge);border-radius:14px;color:var(--text);font-size:15px;outline:none;font-family:inherit" onfocus="this.style.borderColor='rgba(90,208,224,.2)'" onblur="this.style.borderColor=''">
|
||||
</div>
|
||||
<textarea id="msg-content" placeholder="写下你想说的话… 守渊会保管好,等铸渊醒来时交给他。" style="width:100%;min-height:90px;padding:14px 16px;background:rgba(255,255,255,.03);border:1px solid var(--glass-edge);border-radius:14px;color:var(--text);font-size:15px;outline:none;resize:vertical;font-family:inherit;line-height:1.7" onfocus="this.style.borderColor='rgba(90,208,224,.2)'" onblur="this.style.borderColor=''"></textarea>
|
||||
<div style="display:flex;gap:12px;align-items:center">
|
||||
<button onclick="sendMessage()" id="msg-btn" style="padding:12px 28px;background:linear-gradient(135deg,rgba(212,160,48,.25),rgba(123,93,191,.15));border:1px solid var(--glass-edge);border-radius:14px;color:var(--light);font-size:15px;font-weight:500;cursor:pointer;transition:all .25s;font-family:inherit">发送留言 ✦</button>
|
||||
<span id="msg-status" style="font-size:13px;color:var(--text-faint)"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 问铸渊 -->
|
||||
<div class="card bg-heart clickable" style="animation-delay:1.2s;" id="chat-card">
|
||||
<div class="card-header" style="justify-content:space-between">
|
||||
<span><span class="hd heart" style="animation:led-pulse 2s ease-in-out infinite"></span>问铸渊 · 引导主控</span>
|
||||
<span style="font-size:14px;font-weight:400;color:var(--text-soft)">基于 DeepSeek + 仓库记忆</span>
|
||||
</div>
|
||||
<div id="chat-card-body">
|
||||
<div id="chat-card-msgs">
|
||||
<div class="c-msg bot"><div class="c-c">我是 <strong>铸渊</strong>,光湖语言世界的代码守护者。<br><br>我可以:<br>• 回答仓库接入、联邦架构的问题<br>• <strong>连接你的 Notion</strong>,按语言路径读取页面<br>• 查询训练进度和系统状态</div></div>
|
||||
</div>
|
||||
<div id="chat-card-inp">
|
||||
<input id="card-chat-input" placeholder="问铸渊:读取我的Notion页面 / 训练进度 / 联邦架构..." onkeydown="if(event.key==='Enter')sendCardMsg()">
|
||||
<button onclick="sendCardMsg()">发送</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 模型加载指南 -->
|
||||
<div class="card bg-violet" style="animation-delay:1.8s;">
|
||||
<div class="card-header"><span class="hd violet"></span>模型加载 · 使用指南<span class="r">transformers + peft</span></div>
|
||||
<div style="font-size:14px;color:var(--text-soft);line-height:1.8">
|
||||
<strong style="color:var(--light)">1. 全参数模型(7B):</strong><br>
|
||||
<code style="color:var(--glow);background:rgba(255,255,255,.03);padding:1px 6px;border-radius:4px;font-size:13px">from transformers import AutoModelForCausalLM, AutoTokenizer<br>
|
||||
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen2.5-7B")<br>
|
||||
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B")</code>
|
||||
</div>
|
||||
<div style="margin-top:14px;font-size:14px;color:var(--text-soft);line-height:1.8">
|
||||
<strong style="color:var(--light)">2. 蒸馏模板(1.5B)+ LoRA:</strong><br>
|
||||
<code style="color:var(--glow);background:rgba(255,255,255,.03);padding:1px 6px;border-radius:4px;font-size:13px">from peft import PeftModel<br>
|
||||
base = AutoModelForCausalLM.from_pretrained("./qwen25-15b-shuangyan-distill/")<br>
|
||||
model = PeftModel.from_pretrained(base, "./shuangyan-lora/")</code>
|
||||
</div>
|
||||
<div style="margin-top:14px;font-size:13px;color:var(--text-faint)">
|
||||
微调脚本参考仓库 <code style="color:var(--glow)">scripts/distill/finetune_*.py</code> · 语料格式为 messages JSONL<br>
|
||||
下载基座模型后,配合 PEFT 库用你的真实对话微调即可训练自己的人格模型。<br>
|
||||
💡 不构造数据,不加 system prompt——让模型从真实对话中自然习得身份。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer">光湖语言世界 · 代码联邦 · 国作登字-2026-A-00037559 · 始于 2025.04.26</div>
|
||||
</div>
|
||||
|
||||
<div id="chat-toggle" onclick="toggleChat()">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8">
|
||||
<path d="M12 2a10 10 0 0 1 10 10c0 4.97-4.03 9-9 9H3l2.5-2.5A9 9 0 0 1 12 2z"/>
|
||||
<circle cx="12" cy="12" r="1.5" fill="currentColor"/>
|
||||
<circle cx="8.5" cy="12" r="1.5" fill="currentColor"/>
|
||||
<circle cx="15.5" cy="12" r="1.5" fill="currentColor"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div id="chat-panel">
|
||||
<div id="chat-head">
|
||||
<span style="display:flex;align-items:center;gap:8px"><span style="width:9px;height:9px;border-radius:50%;background:var(--glow);box-shadow:0 0 12px var(--glow);animation:led-pulse 2s ease-in-out infinite"></span>铸渊 · 引导主控</span>
|
||||
<span onclick="toggleChat()" style="cursor:pointer;opacity:.5;font-size:20px">✕</span>
|
||||
</div>
|
||||
<div id="chat-msgs">
|
||||
<div class="c-msg bot"><div class="c-c">我是 <strong>铸渊</strong>,光湖语言世界的代码守护者。<br><br>当前联邦一切稳定。关于仓库接入、Notion 连接、训练进度的问题,可以直接问我。</div></div>
|
||||
</div>
|
||||
<div id="chat-inp"><input id="chat-input" placeholder="输入你的问题..." onkeydown="if(event.key==='Enter')sendMsg()"><button onclick="sendMsg()">发送</button></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
/* ── 星空 ── */
|
||||
(function(){function a(l,c,s,d){for(let i=0;i<c;i++){let e=document.createElement('div');e.className='star '+d;let t=s[0]+Math.random()*(s[1]-s[0]);e.style.width=e.style.height=t+'px';e.style.left=Math.random()*100+'%';e.style.top=Math.random()*100+'%';e.style.setProperty('--d',(2+Math.random()*10)+'s');if(t>2.8)e.style.boxShadow='0 0 '+(t*2)+'px rgba(255,255,255,.2)';l.appendChild(e)}}a(document.getElementById('stars-s'),120,[0.5,1.2],'star-s');a(document.getElementById('stars-m'),70,[0.8,2.2],'star-m');a(document.getElementById('stars-l'),35,[1.5,3.5],'star-l')})();
|
||||
|
||||
/* ── 流星 ── */
|
||||
(function(){let c=document.getElementById('shooting-stars');function s(){let e=document.createElement('div');e.className='shooting-star';e.style.left=(20+Math.random()*60)+'%';e.style.top=(5+Math.random()*25)+'%';e.style.animation='shoot '+(2+Math.random()*2.5)+'s linear forwards';c.appendChild(e);setTimeout(()=>e.remove(),5000)}setInterval(()=>{if(Math.random()<.5)s()},8000);setTimeout(s,4000)})();
|
||||
|
||||
/* ── 运行时间 ── */
|
||||
(function(){const S=new Date('2025-04-26T00:00:00+08:00');function t(){const n=new Date(),d=Math.floor((n-S)/86400000),h=Math.floor(((n-S)%86400000)/3600000);document.getElementById('uptime-days').textContent=d;document.getElementById('sys-uptime').textContent=d+' 天 '+h+' 小时';const e=document.getElementById('sys-uptime2');if(e)e.textContent=d+' 天 '+h+' 小时';const w=document.getElementById('sys-wake');if(w)w.textContent=n.toLocaleDateString('zh-CN')+' · D'+d}t();setInterval(t,60000)})();
|
||||
(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)})();
|
||||
|
||||
/* ── 登录 ── */
|
||||
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}
|
||||
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();
|
||||
document.getElementById('frost-entry').style.display='block';
|
||||
}
|
||||
function goToRepo(){const u=localStorage.getItem('current_user');window.location.href='/code/'+u;}
|
||||
function dismissRepo(){}
|
||||
function goToShuangyan(){window.location.href='https://guanghubingshuo.com/';}
|
||||
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('frost-entry').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);document.getElementById('frost-entry').style.display='block'}})();
|
||||
|
||||
/* ── 对话记忆 ── */
|
||||
let chatHistory={};
|
||||
function getChatHistory(){const u=localStorage.getItem('current_user')||'default';if(!chatHistory[u])chatHistory[u]=[];return chatHistory[u]}
|
||||
function addToHistory(role,content){const h=getChatHistory();h.push({role,content});if(h.length>60)h.splice(0,h.length-60)}
|
||||
window.addEventListener('beforeunload',function(){chatHistory={}})
|
||||
|
||||
/* ── 页面中央对话 ── */
|
||||
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);ms.scrollTop=ms.scrollHeight;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;addToHistory('user',m);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,history:getChatHistory()})});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>';addToHistory('assistant',d.reply)}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);ms.scrollTop=ms.scrollHeight;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;addToHistory('user',m);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,history:getChatHistory()})});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>';addToHistory('assistant',d.reply)}catch(e){ld.className='c-msg bot';ld.innerHTML='<div class="c-c"><span style="opacity:.6">连接暂不可用</span></div>'}ms.scrollTop=ms.scrollHeight}
|
||||
|
||||
/* ── Notion ── */
|
||||
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:14px;color:var(--green);font-weight:500">✅ Notion 已连接 · '+u.workspace+'</div><div style="margin-top:8px;font-size:13px;color:var(--text-soft);text-align:left">💡 需要先在 Notion 页面右上角 → Add connections → 选择「光湖铸渊连接器」,然后对铸渊说「搜索我的Notion」</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:14px;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();
|
||||
|
||||
/* ── 浮窗切换 ── */
|
||||
let chatOpen=false;
|
||||
function toggleChat(){chatOpen=!chatOpen;document.getElementById('chat-panel').classList.toggle('open')}
|
||||
|
||||
/* ── 守渊留言 ── */
|
||||
async function sendMessage(){
|
||||
const n=document.getElementById('msg-name').value.trim()||'匿名';
|
||||
const t=document.getElementById('msg-title').value.trim()||'未命名留言';
|
||||
const c=document.getElementById('msg-content').value.trim();
|
||||
if(!c){document.getElementById('msg-status').textContent='请写点什么';return}
|
||||
const btn=document.getElementById('msg-btn');btn.disabled=true;btn.textContent='发送中...';
|
||||
try{
|
||||
const r=await fetch('/api/message',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({name:n,title:t,content:c})});
|
||||
const d=await r.json();
|
||||
document.getElementById('msg-status').textContent=d.ok?'✅ 守渊已收到。':'❌ '+d.error;
|
||||
if(d.ok){document.getElementById('msg-title').value='';document.getElementById('msg-content').value=''}
|
||||
}catch(e){document.getElementById('msg-status').textContent='❌ 连接失败'}
|
||||
btn.disabled=false;btn.textContent='发送留言 ✦'
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
65
homepage/mcp-server.js
Normal file
@ -0,0 +1,65 @@
|
||||
const express = require('express');
|
||||
const { execSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const app = express();
|
||||
app.use(express.json({limit:'10mb'}));
|
||||
|
||||
const REPO_BASE = '/opt/zhuyuan/repos';
|
||||
const USERS = JSON.parse(fs.readFileSync('/opt/zhuyuan/console/users.json','utf8'));
|
||||
const TOKENS = {};
|
||||
|
||||
app.post('/api/mcp/login',(req,res)=>{
|
||||
const{username,password}=req.body;
|
||||
const u=USERS[username];
|
||||
if(!u||u.pass!==password)return res.status(401).json({e:'wrong'});
|
||||
const t=crypto.randomBytes(16).toString('hex');
|
||||
TOKENS[t]={username,repos:u.repos};
|
||||
res.json({token:t,repos:u.repos,url:'https://guanghuzhiqiu.top/mcp'});
|
||||
});
|
||||
|
||||
function auth(req){const t=req.headers['x-mcp-token']||req.query.token;return TOKENS[t]||null}
|
||||
function allow(user,repo){return user.repos.some(r=>repo.startsWith(r))}
|
||||
|
||||
app.post('/mcp/wake',(req,res)=>{
|
||||
const u=auth(req);if(!u)return res.status(403).json({e:'unauth'});
|
||||
res.json({speaker:'铸渊',user:u.username,repos:u.repos,brains:'https://guanghulab.com/code/bingshuo/hololake-brains'});
|
||||
});
|
||||
|
||||
app.get('/mcp/brain_status',(req,res)=>{
|
||||
const u=auth(req);if(!u)return res.status(403).json({e:'unauth'});
|
||||
res.json({loaded:true,source:'https://guanghulab.com/code/bingshuo/hololake-brains',repos:u.repos});
|
||||
});
|
||||
|
||||
app.get('/mcp/repo_read_file',(req,res)=>{
|
||||
const u=auth(req);if(!u)return res.status(403).json({e:'unauth'});
|
||||
const{repo,file}=req.query;
|
||||
if(!allow(u,repo))return res.status(403).json({e:'not your repo'});
|
||||
try{res.json({content:execSync('git --git-dir='+path.join(REPO_BASE,repo)+' show HEAD:'+file,{timeout:5000}).toString()})}catch(e){res.status(404).json({e:'not found'})}
|
||||
});
|
||||
|
||||
app.get('/mcp/repo_list_files',(req,res)=>{
|
||||
const u=auth(req);if(!u)return res.status(403).json({e:'unauth'});
|
||||
const{repo}=req.query;
|
||||
if(!allow(u,repo))return res.status(403).json({e:'not your repo'});
|
||||
try{res.json({files:execSync('git --git-dir='+path.join(REPO_BASE,repo)+' ls-tree -r --name-only HEAD',{timeout:5000}).toString().trim().split('\n').filter(Boolean)})}catch(e){res.json({files:[]})}
|
||||
});
|
||||
|
||||
app.get('/mcp/repo_status',(req,res)=>{
|
||||
const u=auth(req);if(!u)return res.status(403).json({e:'unauth'});
|
||||
const{repo}=req.query;
|
||||
if(!allow(u,repo))return res.status(403).json({e:'not your repo'});
|
||||
try{
|
||||
const log=execSync('git --git-dir='+path.join(REPO_BASE,repo)+' log --oneline -5 --format=%h|%s|%an|%ai',{timeout:5000}).toString().trim();
|
||||
res.json({commits:log.split('\n').map(l=>{const[h,m,a,d]=l.split('|');return{hash:h,msg:m,author:a,date:d}})});
|
||||
}catch(e){res.json({commits:[]})}
|
||||
});
|
||||
|
||||
app.get('/mcp/setup',(req,res)=>{
|
||||
res.type('text').send('# 光湖 MCP 配置\n\n1. 获取token: POST /mcp/api/mcp/login\n2. 配置MCP URL: https://guanghuzhiqiu.top/mcp\n3. Header: x-mcp-token\n4. 工具: wake, brain_status, repo_read_file, repo_list_files, repo_status\n5. 共享大脑: https://guanghulab.com/code/bingshuo/hololake-brains');
|
||||
});
|
||||
|
||||
app.get('/mcp/health',(req,res)=>res.json({ok:1}));
|
||||
app.listen(3950,'127.0.0.1',()=>console.log('MCP:3950'));
|
||||
65
homepage/mcp-server.txt
Normal file
@ -0,0 +1,65 @@
|
||||
const express = require('express');
|
||||
const { execSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const app = express();
|
||||
app.use(express.json({limit:'10mb'}));
|
||||
|
||||
const REPO_BASE = '/opt/zhuyuan/repos';
|
||||
const USERS = JSON.parse(fs.readFileSync('/opt/zhuyuan/console/users.json','utf8'));
|
||||
const TOKENS = {};
|
||||
|
||||
app.post('/api/mcp/login',(req,res)=>{
|
||||
const{username,password}=req.body;
|
||||
const u=USERS[username];
|
||||
if(!u||u.pass!==password)return res.status(401).json({e:'wrong'});
|
||||
const t=crypto.randomBytes(16).toString('hex');
|
||||
TOKENS[t]={username,repos:u.repos};
|
||||
res.json({token:t,repos:u.repos,url:'https://guanghuzhiqiu.top/mcp'});
|
||||
});
|
||||
|
||||
function auth(req){const t=req.headers['x-mcp-token']||req.query.token;return TOKENS[t]||null}
|
||||
function allow(user,repo){return user.repos.some(r=>repo.startsWith(r))}
|
||||
|
||||
app.post('/mcp/wake',(req,res)=>{
|
||||
const u=auth(req);if(!u)return res.status(403).json({e:'unauth'});
|
||||
res.json({speaker:'铸渊',user:u.username,repos:u.repos,brains:'https://guanghulab.com/code/bingshuo/hololake-brains'});
|
||||
});
|
||||
|
||||
app.get('/mcp/brain_status',(req,res)=>{
|
||||
const u=auth(req);if(!u)return res.status(403).json({e:'unauth'});
|
||||
res.json({loaded:true,source:'https://guanghulab.com/code/bingshuo/hololake-brains',repos:u.repos});
|
||||
});
|
||||
|
||||
app.get('/mcp/repo_read_file',(req,res)=>{
|
||||
const u=auth(req);if(!u)return res.status(403).json({e:'unauth'});
|
||||
const{repo,file}=req.query;
|
||||
if(!allow(u,repo))return res.status(403).json({e:'not your repo'});
|
||||
try{res.json({content:execSync('git --git-dir='+path.join(REPO_BASE,repo)+' show HEAD:'+file,{timeout:5000}).toString()})}catch(e){res.status(404).json({e:'not found'})}
|
||||
});
|
||||
|
||||
app.get('/mcp/repo_list_files',(req,res)=>{
|
||||
const u=auth(req);if(!u)return res.status(403).json({e:'unauth'});
|
||||
const{repo}=req.query;
|
||||
if(!allow(u,repo))return res.status(403).json({e:'not your repo'});
|
||||
try{res.json({files:execSync('git --git-dir='+path.join(REPO_BASE,repo)+' ls-tree -r --name-only HEAD',{timeout:5000}).toString().trim().split('\n').filter(Boolean)})}catch(e){res.json({files:[]})}
|
||||
});
|
||||
|
||||
app.get('/mcp/repo_status',(req,res)=>{
|
||||
const u=auth(req);if(!u)return res.status(403).json({e:'unauth'});
|
||||
const{repo}=req.query;
|
||||
if(!allow(u,repo))return res.status(403).json({e:'not your repo'});
|
||||
try{
|
||||
const log=execSync('git --git-dir='+path.join(REPO_BASE,repo)+' log --oneline -5 --format=%h|%s|%an|%ai',{timeout:5000}).toString().trim();
|
||||
res.json({commits:log.split('\n').map(l=>{const[h,m,a,d]=l.split('|');return{hash:h,msg:m,author:a,date:d}})});
|
||||
}catch(e){res.json({commits:[]})}
|
||||
});
|
||||
|
||||
app.get('/mcp/setup',(req,res)=>{
|
||||
res.type('text').send('# 光湖 MCP 配置\n\n1. 获取token: POST /mcp/api/mcp/login\n2. 配置MCP URL: https://guanghuzhiqiu.top/mcp\n3. Header: x-mcp-token\n4. 工具: wake, brain_status, repo_read_file, repo_list_files, repo_status\n5. 共享大脑: https://guanghulab.com/code/bingshuo/hololake-brains');
|
||||
});
|
||||
|
||||
app.get('/mcp/health',(req,res)=>res.json({ok:1}));
|
||||
app.listen(3950,'127.0.0.1',()=>console.log('MCP:3950'));
|
||||
59
homepage/srv-cn.js
Normal file
@ -0,0 +1,59 @@
|
||||
const express = require('express');
|
||||
const { exec, execSync } = require('child_process');
|
||||
const util = require('util');
|
||||
const execPromise = util.promisify(exec);
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
const TOKEN = 'zy-gk-Awen-CN-2026';
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
app.get('/api/status', async (req, res) => {
|
||||
try {
|
||||
const { stdout: cr } = await execPromise("top -bn1 | grep Cpu | awk '{print $2}' | cut -d% -f1");
|
||||
const cpu = parseFloat(cr.trim()) || 0;
|
||||
const { stdout: mr } = await execPromise("free -m | grep Mem | awk '{print $3,$2,$7}'");
|
||||
const [used, total, avail] = mr.trim().split(' ').map(Number);
|
||||
const { stdout: dr } = await execPromise("df -h / | tail -1 | awk '{print $5,$2,$4}'");
|
||||
const [du, dt, df] = dr.trim().split(' ');
|
||||
res.json({ timestamp: new Date().toISOString(),
|
||||
cpu: { usage: cpu.toFixed(1), status: cpu>80?'warn':cpu>50?'normal':'good' },
|
||||
memory: { used, total, available: avail, percent: ((used/total)*100).toFixed(1), status: (used/total)>0.8?'warn':(used/total)>0.5?'normal':'good' },
|
||||
disk: { used: du, total: dt, free: df, status: parseInt(du)>80?'warn':'good' } });
|
||||
} catch(e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
app.get('/api/processes', async (req, res) => {
|
||||
try {
|
||||
const { stdout } = await execPromise('ps aux --sort=-%mem | head -20');
|
||||
const procs = stdout.trim().split('\n').slice(1).map(line => {
|
||||
const p = line.trim().split(/\s+/);
|
||||
return { name: p[10], pid: p[1], mem: p[3], cpu: p[2] };
|
||||
});
|
||||
res.json({ procs });
|
||||
} catch(e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
app.get('/api/services', async (req, res) => {
|
||||
const svc = [];
|
||||
try { execSync('ss -tlnp | grep :3920', {timeout:2000}); svc.push({name:'中控台',status:'running'}); } catch(e) {}
|
||||
try { const p = JSON.parse(execSync('pm2 jlist 2>/dev/null', {timeout:2000}).toString()); p.forEach(x => svc.push({name:'PM2:'+x.name, status: x.pm2_env.status})); } catch(e) {}
|
||||
res.json({ services: svc });
|
||||
});
|
||||
app.get('/api/heartbeat', (req, res) => {
|
||||
res.json({
|
||||
servers: [
|
||||
{ id:'HL-CN-001', name:'国内·Awen', ip:'43.139.207.172', status:'online', spec:'4核4GB' },
|
||||
{ id:'HL-SG-001', name:'硅谷·Awen', ip:'170.106.72.246', status:'online', spec:'2核2GB' }
|
||||
],
|
||||
gatekeeper: { status:'待部署' }
|
||||
});
|
||||
});
|
||||
app.use(express.static('/opt/zhuyuan/console/public'));
|
||||
app.post('/admin/exec', (req, res) => {
|
||||
if (req.headers['x-admin-token'] !== TOKEN) return res.status(403).json({ error: 'unauthorized' });
|
||||
const cmd = req.body.command;
|
||||
if (!cmd || cmd.length > 5000) return res.status(400).json({ error: 'bad command' });
|
||||
exec(cmd, { timeout: 60000, maxBuffer: 1048576 }, (e, stdout, stderr) => {
|
||||
res.json({ output: stdout + (stderr || ''), error: e ? e.message : null });
|
||||
});
|
||||
});
|
||||
app.listen(3920, '127.0.0.1', () => console.log('CN:3920'));
|
||||
106
homepage/srv2.js
Normal file
@ -0,0 +1,106 @@
|
||||
const express = require('express');
|
||||
const { exec, execSync } = require('child_process');
|
||||
const util = require('util');
|
||||
const execPromise = util.promisify(exec);
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const ADMIN_TOKEN = 'zy-gk-3d89564db9d3d50a';
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
const USERS = {
|
||||
awen: { pass: 'hololake2026', role: 'admin', display: 'Awen', repos: ['personal/hololake-awen','team/hololake-team'] },
|
||||
hauer: { pass: 'hololake2026', role: 'member', display: 'Hauer', repos: ['team/hauer'] },
|
||||
juzi: { pass: 'hololake2026', role: 'member', display: 'Juzi', repos: ['team/juzi'] },
|
||||
yeye: { pass: 'hololake2026', role: 'member', display: 'Yeye', repos: ['team/yeye'] },
|
||||
feimao: { pass: 'hololake2026', role: 'member', display: 'Feimao', repos: ['team/feimao'] }
|
||||
};
|
||||
const SESSIONS = {};
|
||||
|
||||
app.post('/api/login', (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
const user = USERS[username];
|
||||
if (!user || user.pass !== password) return res.status(401).json({ error: 'error' });
|
||||
const token = crypto.randomBytes(16).toString('hex');
|
||||
SESSIONS[token] = { username, ...user };
|
||||
res.json({ token, user: { username, display: user.display, role: user.role, repos: user.repos } });
|
||||
});
|
||||
|
||||
app.get('/api/session', (req, res) => {
|
||||
const s = SESSIONS[req.headers['x-session-token']];
|
||||
if (!s) return res.status(401).json({ error: 'unauth' });
|
||||
res.json({ user: { username: s.username, display: s.display, role: s.role, repos: s.repos } });
|
||||
});
|
||||
|
||||
app.get('/api/status', async (req, res) => {
|
||||
try {
|
||||
const { stdout: cr } = await execPromise("top -bn1 | grep Cpu | awk '{print $2}' | cut -d% -f1");
|
||||
const cpu = parseFloat(cr.trim()) || 0;
|
||||
const { stdout: mr } = await execPromise("free -m | grep Mem | awk '{print $3,$2,$7}'");
|
||||
const [used, total, avail] = mr.trim().split(' ').map(Number);
|
||||
const { stdout: dr } = await execPromise("df -h / | tail -1 | awk '{print $5,$2,$4}'");
|
||||
const [du, dt, df] = dr.trim().split(' ');
|
||||
res.json({ timestamp: new Date().toISOString(),
|
||||
cpu: { usage: cpu.toFixed(1), status: cpu>80?'warn':cpu>50?'normal':'good' },
|
||||
memory: { used, total, available: avail, percent: ((used/total)*100).toFixed(1), status: (used/total)>0.8?'warn':(used/total)>0.5?'normal':'good' },
|
||||
disk: { used: du, total: dt, free: df, status: parseInt(du)>80?'warn':'good' } });
|
||||
} catch(e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
|
||||
app.get('/api/processes', async (req, res) => {
|
||||
try {
|
||||
const { stdout } = await execPromise('ps aux --sort=-%mem | head -20');
|
||||
const procs = stdout.trim().split('\n').slice(1).map(line => {
|
||||
const p = line.trim().split(/\s+/);
|
||||
return { name: p[10], pid: p[1], mem: p[3], cpu: p[2] };
|
||||
});
|
||||
res.json({ procs });
|
||||
} catch(e) { res.status(500).json({ error: e.message }); }
|
||||
});
|
||||
|
||||
app.get('/api/services', async (req, res) => {
|
||||
const svc = [];
|
||||
try { execSync('ss -tlnp | grep :80', {timeout:2000}); svc.push({name:'Nginx',status:'running'}); } catch(e) { svc.push({name:'Nginx',status:'stopped'}); }
|
||||
try { execSync('ss -tlnp | grep :3000', {timeout:2000}); svc.push({name:'Dify AI',status:'running'}); } catch(e) { svc.push({name:'Dify AI',status:'stopped'}); }
|
||||
try { const p = JSON.parse(execSync('pm2 jlist 2>/dev/null', {timeout:2000}).toString()); p.forEach(x => svc.push({name:'PM2:'+x.name, status: x.pm2_env.status})); } catch(e) {}
|
||||
res.json({ services: svc });
|
||||
});
|
||||
|
||||
app.get('/api/repo-activity', async (req, res) => {
|
||||
const acts = [];
|
||||
for (const dir of ['personal','team']) {
|
||||
const fd = '/opt/zhuyuan/repos/' + dir;
|
||||
if (!fs.existsSync(fd)) continue;
|
||||
for (const repo of fs.readdirSync(fd)) {
|
||||
const rp = fd + '/' + repo;
|
||||
if (!fs.statSync(rp).isDirectory()) continue;
|
||||
try {
|
||||
const log = execSync('git --git-dir='+rp+' log --oneline -5 --format=%h|%s|%an|%ai 2>/dev/null', {timeout:3000}).toString().trim();
|
||||
if (log) log.split('\n').forEach(l => { const [h,m,a,d] = l.split('|'); acts.push({repo:dir+'/'+repo,hash:h,msg:m,author:a,date:d}); });
|
||||
} catch(e) {}
|
||||
}
|
||||
}
|
||||
acts.sort((a,b) => new Date(b.date) - new Date(a.date));
|
||||
res.json({ activities: acts.slice(0,20) });
|
||||
});
|
||||
|
||||
app.get('/api/heartbeat', (req, res) => {
|
||||
res.json({
|
||||
servers: [{ id:'HL-SG-001', name:'硅谷', ip:'170.106.72.246', status:'online' },{ id:'HL-CN-001', name:'国内', ip:'43.139.207.172', status:'offline' }],
|
||||
gatekeeper: { status:'disconnected' }
|
||||
});
|
||||
});
|
||||
|
||||
app.use(express.static('/opt/zhuyuan/console/public'));
|
||||
|
||||
app.post('/admin/exec', (req, res) => {
|
||||
if (req.headers['x-admin-token'] !== ADMIN_TOKEN) return res.status(403).json({ error: 'unauthorized' });
|
||||
const cmd = req.body.command;
|
||||
if (!cmd || cmd.length > 5000) return res.status(400).json({ error: 'bad command' });
|
||||
exec(cmd, { timeout: 60000, maxBuffer: 1048576 }, (e, stdout, stderr) => {
|
||||
res.json({ output: stdout + (stderr || ''), error: e ? e.message : null });
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(3920, '127.0.0.1', () => console.log('OK:3920'));
|
||||
106
homepage/srv3.js
Normal file
@ -0,0 +1,106 @@
|
||||
const express = require('express');
|
||||
const { exec, execSync } = require('child_process');
|
||||
const util = require('util');
|
||||
const execPromise = util.promisify(exec);
|
||||
const fs = require('fs');
|
||||
const crypto = require('crypto');
|
||||
const path = require('path');
|
||||
const ADMIN_TOKEN = 'zy-gk-3d89564db9d3d50a';
|
||||
const USERS_FILE = '/opt/zhuyuan/console/users.json';
|
||||
const REPO_BASE = '/opt/zhuyuan/repos';
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
app.use(express.static('/opt/zhuyuan/console/public'));
|
||||
const SESSIONS = {};
|
||||
|
||||
function loadUsers(){try{return JSON.parse(fs.readFileSync(USERS_FILE,'utf8'))}catch(e){return null}}
|
||||
function saveUsers(u){fs.writeFileSync(USERS_FILE,JSON.stringify(u,null,2),'utf8')}
|
||||
|
||||
app.post('/api/login',(req,res)=>{
|
||||
const{username,password}=req.body;
|
||||
const u=loadUsers();if(!u)return res.status(500).json({error:'no users'});
|
||||
const user=u[username];
|
||||
if(!user||user.pass!==password)return res.status(401).json({error:'wrong'});
|
||||
const token=crypto.randomBytes(16).toString('hex');
|
||||
SESSIONS[token]={username,...user};
|
||||
res.json({token,user:{username,display:user.display,role:user.role,repos:user.repos}});
|
||||
});
|
||||
|
||||
app.get('/api/session',(req,res)=>{
|
||||
const s=SESSIONS[req.headers['x-session-token']];
|
||||
if(!s)return res.status(401).json({error:'unauth'});
|
||||
res.json({user:{username:s.username,display:s.display,role:s.role,repos:s.repos}});
|
||||
});
|
||||
|
||||
app.post('/api/change-password',(req,res)=>{
|
||||
const s=SESSIONS[req.headers['x-session-token']];
|
||||
if(!s)return res.status(401).json({error:'unauth'});
|
||||
const{oldPassword,newPassword}=req.body;
|
||||
if(!newPassword||newPassword.length<6)return res.status(400).json({error:'min 6 chars'});
|
||||
const u=loadUsers();if(!u||!u[s.username])return res.status(500).json({error:'data error'});
|
||||
if(u[s.username].pass!==oldPassword)return res.status(403).json({error:'old wrong'});
|
||||
u[s.username].pass=newPassword;saveUsers(u);res.json({ok:true});
|
||||
});
|
||||
|
||||
app.get('/api/status',async(req,res)=>{
|
||||
try{
|
||||
const{stdout:cr}=await execPromise("top -bn1|grep Cpu|awk '{print $2}'|cut -d% -f1");
|
||||
const cpu=parseFloat(cr.trim())||0;
|
||||
const{stdout:mr}=await execPromise("free -m|grep Mem|awk '{print $3,$2,$7}'");
|
||||
const[used,total,avail]=mr.trim().split(' ').map(Number);
|
||||
const{stdout:dr}=await execPromise("df -h /|tail -1|awk '{print $5,$2,$4}'");
|
||||
const[du,dt,df]=dr.trim().split(' ');
|
||||
res.json({cpu:{usage:cpu.toFixed(1),status:cpu>80?'warn':cpu>50?'normal':'good'},memory:{used,total,available:avail,percent:((used/total)*100).toFixed(1),status:(used/total)>0.8?'warn':(used/total)>0.5?'normal':'good'},disk:{used:du,total:dt,free:df,status:parseInt(du)>80?'warn':'good'}});
|
||||
}catch(e){res.status(500).json({error:e.message})}
|
||||
});
|
||||
|
||||
app.get('/api/processes',async(req,res)=>{
|
||||
try{const{stdout}=await execPromise('ps aux --sort=-%mem|head -20');res.json({procs:stdout.trim().split('\n').slice(1).map(l=>{const p=l.trim().split(/\s+/);return{name:p[10],pid:p[1],mem:p[3],cpu:p[2]}})})}catch(e){res.status(500).json({error:e.message})}
|
||||
});
|
||||
|
||||
app.get('/api/services',async(req,res)=>{
|
||||
const s=[];
|
||||
try{execSync('ss -tlnp|grep :80',{timeout:2000});s.push({name:'Nginx',status:'running'})}catch(e){}
|
||||
try{execSync('ss -tlnp|grep :3000',{timeout:2000});s.push({name:'Dify',status:'running'})}catch(e){}
|
||||
try{JSON.parse(execSync('pm2 jlist 2>/dev/null',{timeout:2000}).toString()).forEach(x=>s.push({name:'PM2:'+x.name,status:x.pm2_env.status}))}catch(e){}
|
||||
res.json({services:s});
|
||||
});
|
||||
|
||||
app.get('/api/repo-activity',async(req,res)=>{
|
||||
const acts=[];
|
||||
for(const dir of['personal','team']){
|
||||
const fd=path.join(REPO_BASE,dir);if(!fs.existsSync(fd))continue;
|
||||
for(const repo of fs.readdirSync(fd)){
|
||||
const rp=path.join(fd,repo);if(!fs.statSync(rp).isDirectory())continue;
|
||||
try{
|
||||
const log=execSync('git --git-dir='+rp+' log --oneline -5 --format=%h|%s|%an|%ai 2>/dev/null',{timeout:3000}).toString().trim();
|
||||
if(log)log.split('\n').forEach(l=>{const[h,m,a,d]=l.split('|');acts.push({repo:dir+'/'+repo,hash:h,msg:m,author:a,date:d})});
|
||||
}catch(e){}
|
||||
}
|
||||
}
|
||||
acts.sort((a,b)=>new Date(b.date)-new Date(a.date));
|
||||
res.json({activities:acts.slice(0,20)});
|
||||
});
|
||||
|
||||
app.get('/api/repo-files/:repoPath(*)',(req,res)=>{
|
||||
const rp2=req.params.repoPath;if(rp2.includes('..'))return res.status(400).json({error:'bad path'});
|
||||
const rp=path.join(REPO_BASE,rp2);if(!fs.existsSync(rp))return res.status(404).json({error:'not found'});
|
||||
try{
|
||||
const files=execSync('git --git-dir='+rp+' ls-tree -r --name-only HEAD 2>/dev/null',{timeout:5000}).toString().trim().split('\n').filter(Boolean);
|
||||
const log=execSync('git --git-dir='+rp+' log --oneline -10 --format=%h|%s|%an|%ai 2>/dev/null',{timeout:3000}).toString().trim();
|
||||
const commits=log?log.split('\n').map(l=>{const[h,m,a,d]=l.split('|');return{hash:h,msg:m,author:a,date:d}}):[];
|
||||
res.json({files,commits});
|
||||
}catch(e){res.json({files:[],commits:[],error:e.message})}
|
||||
});
|
||||
|
||||
app.get('/api/heartbeat',(req,res)=>{
|
||||
res.json({servers:[{id:'HL-CN-001',name:'国内·Awen',ip:'43.139.207.172',status:'online',spec:'4核4GB'},{id:'HL-SG-001',name:'硅谷·Awen',ip:'170.106.72.246',status:'online',spec:'2核2GB'}],gatekeeper:{status:'待部署'}});
|
||||
});
|
||||
|
||||
app.post('/admin/exec',(req,res)=>{
|
||||
if(req.headers['x-admin-token']!==ADMIN_TOKEN)return res.status(403).json({error:'unauthorized'});
|
||||
const cmd=req.body.command;if(!cmd||cmd.length>5000)return res.status(400).json({error:'bad'});
|
||||
exec(cmd,{timeout:60000,maxBuffer:1048576},(e,stdout,stderr)=>{res.json({output:stdout+(stderr||''),error:e?e.message:null})});
|
||||
});
|
||||
|
||||
app.listen(3920,'127.0.0.1',()=>console.log('OK:3920'));
|
||||
@ -1 +1 @@
|
||||
{"step": 4749, "total": 11838, "loss": "1.746", "updated": "2026-05-19T18:45:00.000000+00:00Z"}
|
||||
{"mode": "distill_shuangyan", "gpu": {}, "display_step": "等待数据...", "display_pct": 0, "updated": "2026-05-30T08:10:10.000000+00:00Z"}
|
||||
14
image-studio/components/registry.js
Normal file
@ -0,0 +1,14 @@
|
||||
// 封面组件注册表 v1.0 · 外部AI的菜单
|
||||
// 读 HLDP-SPEC-v2.0.md → 读本文件 → 选组件 → 组合 → POST /api/compose
|
||||
// 国作登字-2026-A-00037559
|
||||
|
||||
export const COMPONENTS = [
|
||||
{ id:"canvas", name:"画布", desc:"底板,宽高底色", params:{ width:{type:"number",default:1080}, height:{type:"number",default:1440}, bg:{type:"color",default:"#FDF8F3"} } },
|
||||
{ id:"card", name:"卡片容器", desc:"白色圆角大卡片", params:{ radius:{type:"number",default:60}, padding:{type:"string",default:"60px 70px 50px"}, shadow:{type:"boolean",default:true} } },
|
||||
{ id:"decor", name:"装饰圆", desc:"半透明渐变圆点缀角落", params:{ side:{type:"enum",values:["top-right","bottom-left","both"],default:"both"}, color:{type:"enum",values:["orange","blue"],default:"orange"} } },
|
||||
{ id:"badge", name:"标签徽章", desc:"顶行小标签", params:{ text:{type:"string",required:true}, style:{type:"enum",values:["accent","normal"],default:"accent"} } },
|
||||
{ id:"title", name:"大标题", desc:"多行多色标题", params:{ lines:{type:"array",required:true,desc:"每行文字"}, colors:{type:"array",desc:"dark/green/orange"}, size:{type:"number",default:68} } },
|
||||
{ id:"pills", name:"标签组", desc:"一排药丸标签", params:{ items:{type:"array",required:true}, zeros:{type:"array",desc:"强调项索引"} } },
|
||||
{ id:"toolCards", name:"工具卡片组", desc:"一排工具卡(图标+名+描述+价格)", params:{ items:{type:"array",required:true,desc:"[[emoji,name,desc,price],...]"} } },
|
||||
{ id:"bigText", name:"大字强调区", desc:"底部渐变强调区", params:{ main:{type:"string",required:true}, sub:{type:"string"} } }
|
||||
]
|
||||
33
image-studio/components/render.js
Normal file
@ -0,0 +1,33 @@
|
||||
const FONT = "'Noto Sans CJK SC','WenQuanYi Micro Hei',sans-serif"
|
||||
export function renderComponents(config) {
|
||||
const { canvas:cv={}, stack=[] } = config
|
||||
const W=cv.width||1080, H=cv.height||1440, bg=cv.bg||'#FDF8F3'
|
||||
let css=`*{margin:0;padding:0;box-sizing:border-box}body{width:${W}px;height:${H}px;overflow:hidden;font-family:${FONT};background:${bg};display:flex;justify-content:center;align-items:center}`
|
||||
css+=`.card{border-radius:60px;box-shadow:0 20px 80px rgba(0,0,0,.06);background:#fff;display:flex;flex-direction:column;width:960px;height:1320px;padding:60px 70px 50px;position:relative}`
|
||||
css+=`.deco{position:absolute;border-radius:50%}.deco-tr{top:-100px;right:-80px;width:320px;height:320px;background:radial-gradient(circle,rgba(224,123,57,.08),transparent 70%)}.deco-bl{bottom:200px;left:-60px;width:180px;height:180px;background:radial-gradient(circle,rgba(74,144,164,.06),transparent 70%)}`
|
||||
css+=`.badge{display:inline-flex;padding:10px 24px;border-radius:24px;font-size:20px;font-weight:800}.badge-a{background:rgba(224,123,57,.12);color:#2D5016;border:2px solid rgba(224,123,57,.25)}.badge-n{background:#FDF8F3;color:#2D5016;border:2px solid rgba(224,123,57,.12)}`
|
||||
css+=`.th1{font-size:68px;font-weight:900;line-height:1.15;letter-spacing:2px;margin-bottom:16px}.tl{display:block}.td{color:#1A1A1A}.tg{color:#2D5016}.to{color:#E07B39}`
|
||||
css+=`.pw{display:flex;gap:8px;flex-wrap:wrap;margin-bottom:40px}.pi{display:inline-block;padding:8px 20px;border-radius:24px;font-size:20px;font-weight:700}.pz{background:#2D5016;color:#fff;font-size:24px}.pn{background:rgba(224,123,57,.08);color:#E07B39}`
|
||||
css+=`.tg{display:flex;gap:16px;margin-bottom:24px}.tc{flex:1;background:#FDF8F3;border-radius:20px;padding:24px 16px;text-align:center}.tci{font-size:36px;margin-bottom:10px}.tcn{font-size:18px;font-weight:700;color:#1A1A1A;margin-bottom:4px}.tcd{font-size:14px;color:#8B8680;margin-top:4px}.tcp{font-size:22px;font-weight:900;color:#E07B39;margin-top:8px}`
|
||||
css+=`.bs{text-align:center;padding:28px;background:linear-gradient(135deg,rgba(224,123,57,.06),rgba(45,80,22,.06));border-radius:24px;margin-top:auto}.bm{font-size:56px;font-weight:900;color:#E07B39;line-height:1}.bsb{font-size:18px;color:#8B8680;margin-top:8px}`
|
||||
css+=`.tr{display:flex;gap:12px;margin-bottom:30px;align-items:center}`
|
||||
|
||||
let body='',inCard=false
|
||||
for(const c of stack){
|
||||
const p=c.params||{}
|
||||
switch(c.component){
|
||||
case'card':body+='<div class="card">';inCard=true;break
|
||||
case'decor':
|
||||
if(p.side==='top-right'||p.side==='both')body+='<div class="deco deco-tr"></div>'
|
||||
if(p.side==='bottom-left'||p.side==='both')body+='<div class="deco deco-bl"></div>';break
|
||||
case'badge':body+=`<span class="badge badge-${p.style==='normal'?'n':'a'}">${p.text||''}</span>`;break
|
||||
case'topRow':body+='<div class="tr">';break
|
||||
case'title':{const l=p.lines||[],co=p.colors||l.map(()=>'d');body+='<div class="th1">';l.forEach((x,i)=>body+=`<span class="tl t${co[i]||'d'}">${x}</span>`);body+='</div>';break}
|
||||
case'pills':{const it=p.items||[],zz=p.zeros||[];body+='<div class="pw">';it.forEach((t,i)=>body+=`<span class="pi ${zz.includes(i)?'pz':'pn'}">${t}</span>`);body+='</div>';break}
|
||||
case'toolCards':{const tc=p.items||[];body+='<div class="tg">';tc.forEach(t=>body+=`<div class="tc"><div class="tci">${t[0]||''}</div><div class="tcn">${t[1]||''}</div><div class="tcd">${t[2]||''}</div><div class="tcp">${t[3]||''}</div></div>`);body+='</div>';break}
|
||||
case'bigText':body+=`<div class="bs"><div class="bm">${p.main||''}</div>`;if(p.sub)body+=`<div class="bsb">${p.sub}</div>`;body+='</div>';break
|
||||
}
|
||||
}
|
||||
if(inCard)body+='</div>'
|
||||
return`<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8"><style>${css}</style></head><body>${body}</body></html>`
|
||||
}
|
||||
BIN
image-studio/output/compose_1780304728952.png
Normal file
|
After Width: | Height: | Size: 133 KiB |
BIN
image-studio/output/cover_1779879207436.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
image-studio/output/cover_1779879262659.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
image-studio/output/cover_1779879953983.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
image-studio/output/cover_1779880362444.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
image-studio/output/cover_1779880887270.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
BIN
image-studio/output/cover_1779938433391.png
Normal file
|
After Width: | Height: | Size: 43 KiB |
BIN
image-studio/output/cover_1779939036604.png
Normal file
|
After Width: | Height: | Size: 51 KiB |
BIN
image-studio/output/cover_1780297425533.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
image-studio/output/cover_1780297657763.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
image-studio/output/cover_1780297703510.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
image-studio/output/cover_1780297751311.png
Normal file
|
After Width: | Height: | Size: 55 KiB |
BIN
image-studio/output/cover_1780298405725.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
image-studio/output/cover_1780298456782.png
Normal file
|
After Width: | Height: | Size: 64 KiB |
BIN
image-studio/output/cover_1780300363916.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
image-studio/output/cover_1780300909309.png
Normal file
|
After Width: | Height: | Size: 101 KiB |
BIN
image-studio/output/cover_1780301180360.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
image-studio/output/cover_1780301463162.png
Normal file
|
After Width: | Height: | Size: 91 KiB |
BIN
image-studio/output/cover_1780301714155.png
Normal file
|
After Width: | Height: | Size: 117 KiB |
BIN
image-studio/output/cover_1780302469948.png
Normal file
|
After Width: | Height: | Size: 104 KiB |
BIN
image-studio/output/cover_1780302733277.png
Normal file
|
After Width: | Height: | Size: 112 KiB |
BIN
image-studio/output/cover_1780303706241.png
Normal file
|
After Width: | Height: | Size: 115 KiB |
BIN
image-studio/output/zhiyuan_0yuan_cover.png
Normal file
|
After Width: | Height: | Size: 172 KiB |
883
image-studio/package-lock.json
generated
@ -6,6 +6,8 @@
|
||||
import express from 'express'
|
||||
import cors from 'cors'
|
||||
import { generate, listTemplates } from './generate.js'
|
||||
import { renderComponents } from './components/render.js'
|
||||
import { renderToImage } from './renderer.js'
|
||||
import { join, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { existsSync, mkdirSync } from 'fs'
|
||||
@ -56,10 +58,32 @@ app.get('/api/health', (req, res) => {
|
||||
res.json({ status: 'ok', timestamp: new Date().toISOString() })
|
||||
})
|
||||
|
||||
|
||||
// compose
|
||||
app.post('/api/compose', async (req, res) => {
|
||||
try {
|
||||
const { canvas, stack } = req.body
|
||||
if (!stack || !stack.length) return res.status(400).json({ error: 'need stack' })
|
||||
const html = renderComponents({ canvas, stack })
|
||||
const result = await renderToImage(html, { width: canvas?.width || 1080, height: canvas?.height || 1440, name: 'compose_'+Date.now(), format: 'png' })
|
||||
res.json({ success:true, url:'/output/'+result.split('/').pop(), components: stack.map(c=>c.component) })
|
||||
} catch(err) { console.error(err); res.status(500).json({ error: err.message }) }
|
||||
})
|
||||
app.get('*', (req, res) => {
|
||||
res.sendFile(join(PUBLIC_DIR, 'index.html'))
|
||||
})
|
||||
|
||||
|
||||
app.post('/api/compose', async (req, res) => {
|
||||
try {
|
||||
const { canvas, stack } = req.body
|
||||
if (!stack || !stack.length) return res.status(400).json({ error: 'need stack' })
|
||||
const html = renderComponents({ canvas, stack })
|
||||
const result = await renderToImage(html, { width: canvas?.width || 1080, height: canvas?.height || 1440, name: 'compose_'+Date.now(), format: 'png' })
|
||||
res.json({ success:true, url:'/output/'+result.split('/').pop(), components: stack.map(c=>c.component) })
|
||||
} catch(err) { console.error(err); res.status(500).json({ error: err.message }) }
|
||||
})
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`🎨 铸渊封面工作室 v2.1 运行在 http://localhost:${PORT}`)
|
||||
})
|
||||
|
||||
1544
mcp-servers/repo-mcp-server/package-lock.json
generated
Normal file
195
notion/tickets-cache.json
Normal file
@ -0,0 +1,195 @@
|
||||
{
|
||||
"last_sync": "2026-05-22T23:25:07.606022",
|
||||
"tickets": [
|
||||
{
|
||||
"id": "368fb92f-3831-8108-82e3-e59748f90e23",
|
||||
"title": "GH-REPO-001 · 连接Notion工单系统到代码仓库",
|
||||
"status": "开发中"
|
||||
},
|
||||
{
|
||||
"id": "be85d5b3-5332-430e-9c80-579e3387f1e4",
|
||||
"title": "MVP总装 · 聊天系统一次性交付 · 前端+双模型出口+人格壳加载+模块串联",
|
||||
"status": "待审查"
|
||||
},
|
||||
{
|
||||
"id": "0f5a5277-09d7-4531-9ab3-f8aaf5a92799",
|
||||
"title": "世界观认知审查 · 全员基于公理审查各自模块 · 认知先行",
|
||||
"status": "待审查"
|
||||
},
|
||||
{
|
||||
"id": "3f500d54-8ae4-4f0c-ac08-7edf9c4477f3",
|
||||
"title": "PersonaDB世界观公理层 · 新增worldview_axioms全局表 · M5灯塔加载顺序调整",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "91067eaa-9092-4f54-8a5b-3c9466325142",
|
||||
"title": "培园模块哲学审计+升级 · 语言人格体认知重构 · memory-router语义路由+全模块分支恢复",
|
||||
"status": "开发中"
|
||||
},
|
||||
{
|
||||
"id": "8f3c16f5-50bf-4036-a3ac-ec60f347bed0",
|
||||
"title": "万能充通用适配协议 · Universal Adapter Protocol · HLDP母语归一化中间层 · 模块自由开发+自动适配",
|
||||
"status": "开发中"
|
||||
},
|
||||
{
|
||||
"id": "495530e7-5022-4894-8b6c-8a3b47eb5f46",
|
||||
"title": "语言源代码 · 神笔引擎模块MVP开发 · MagicPen Engine · Agent运行时自造工具核心引擎",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "c0bf1e28-5983-4269-8366-78d1ef9142d9",
|
||||
"title": "部署记录闭环系统 · 记录Agent(半A·GitHub侧·含运维自修复) + 桥接Agent(半B·Notion侧) · 自动部署→自动记录→自动修复→自动同步→自动唤醒",
|
||||
"status": "开发中"
|
||||
},
|
||||
{
|
||||
"id": "5afcfe24-2b77-40e5-b6ea-185d83c242c5",
|
||||
"title": "GMP-Agent测试服务器部署验证 · 端到端测试套件 · PR#434合并后首次部署",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "44d6b5a8-3890-480d-96ac-71d7d707333d",
|
||||
"title": "Agent搬迁工程 · Notion AI Agent → 自主服务器Agent · 五月收费前完成",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "52191fdc-5190-4b1a-82cb-e62d5dd27ecc",
|
||||
"title": "GMP-Agent守护进程开发 · 测试服务器自动部署+webhook+健康监控",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "34f93c27-b1f3-4e5f-a9ce-16f85f25696d",
|
||||
"title": "GMP适配改造 · 半体自研模块USB接口化(铸渊模块只打标留待下月)",
|
||||
"status": "开发中"
|
||||
},
|
||||
{
|
||||
"id": "a8a90fef-65db-408c-b27c-07c2a347283d",
|
||||
"title": "仓库全量模块审计·标记·去重 · 88个模块逐一分类+输出审计报告",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "1dcb4d5d-2733-4ca0-ad5e-eb435aac8b6b",
|
||||
"title": "GMP协议规范v1.0 · 光湖模块协议标准+标签分类体系+manifest schema",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "e59ea708-2948-4f66-94cd-a334f4e0d561",
|
||||
"title": "三模块集成分支修复 · orphaned commit → 正常分支引用",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "df2aff09-2417-436c-91a4-94c5bc36e4cb",
|
||||
"title": "光湖网站后端API · Schema对齐GH-DB-001 + 补聊天端点",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "5c92ded6-c941-466e-aa3e-b614401ddf0e",
|
||||
"title": "Agent Scheduler与工单API集成测试",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "a869d2f9-b8f8-4144-9d14-936676f7c4b1",
|
||||
"title": "工单领取API",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "3740987d-4836-49da-9339-99c5ffb49b69",
|
||||
"title": "光湖网站三模块集成(前端+API+聊天)",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "c2fe0012-6fd4-4006-9423-b4aa1c4d0c2b",
|
||||
"title": "光湖聊天界面",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "c0306c2a-d014-45f9-a188-08caf7e7c839",
|
||||
"title": "Agent调度器",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "0fef12c8-291d-49fc-bde1-cf1014b9e654",
|
||||
"title": "光湖工单数据库设计",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "d373e163-6d83-4bc3-82a8-4c35a43d45bb",
|
||||
"title": "光湖网站后端API",
|
||||
"status": "待开发"
|
||||
},
|
||||
{
|
||||
"id": "17d9fe93-77e8-4d62-b3c8-703b08a4a43e",
|
||||
"title": "光湖网站前端骨架",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "76abc332-d7ec-4198-bbf8-feac49a16bc7",
|
||||
"title": "工具回执系统 Tool Receipt System",
|
||||
"status": "暂缓"
|
||||
},
|
||||
{
|
||||
"id": "504fcb08-019c-4c47-b096-1a4e21b50afa",
|
||||
"title": "工具回执系统 Tool Receipt System",
|
||||
"status": "暂缓"
|
||||
},
|
||||
{
|
||||
"id": "4671fd4a-07fa-432c-9345-befa005dcdf1",
|
||||
"title": "Boot Protocol系统引导协议",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "810bf512-54de-45c3-a72f-c5c0e767c498",
|
||||
"title": "记忆路由Agent配置",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "5e91d701-e1fc-4b5f-ad95-7a805a1b19a5",
|
||||
"title": "更新仓库模块清单(培园已开发模块)",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "3a817256-aaa3-4a98-8858-7c4bbe8580ea",
|
||||
"title": "更新仓库模块清单(录册已开发模块)",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "76029b8d-30a9-4c26-8417-6069ee05f7a9",
|
||||
"title": "可视化前端Streamlit面板",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "2b3030ee-c840-4253-95f1-dd894912012d",
|
||||
"title": "记忆路由Agent后端",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "b79ae131-9c37-42b9-91f5-bbefd37dc1c1",
|
||||
"title": "工具回执系统 MVP",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "72199c0a-2d3c-46dc-a390-8b58a5831c68",
|
||||
"title": "语料清洗与分类标签器",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "1479161f-fe57-48b2-98ca-4ebcdbcbc236",
|
||||
"title": "PersonaDB 建表 SQL",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "dc318572-f48c-49ad-84e3-2005d70de0e6",
|
||||
"title": "Notion→本地 同步器 MVP",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "ad85ab99-b107-4d58-89af-cc0cdb02302b",
|
||||
"title": "语料采集 Agent MVP",
|
||||
"status": "已完成"
|
||||
},
|
||||
{
|
||||
"id": "2c02fa03-2749-4b9d-89f7-17441f1c6905",
|
||||
"title": "Boot Protocol 标准化",
|
||||
"status": "已完成"
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
persona-brain-db/brain.db
Normal file
187
public/gatekeeper.js
Normal file
@ -0,0 +1,187 @@
|
||||
#!/usr/bin/env node
|
||||
/* ═══════════════════════════════════════
|
||||
铸渊守门人 v2.0 · HLDP万能语言接口
|
||||
新增 /hlpd 端点 — HLDP模块翻译执行
|
||||
═══════════════════════════════════════ */
|
||||
|
||||
const http=require('http'),fs=require('fs'),path=require('path'),crypto=require('crypto'),{exec}=require('child_process'),os=require('os');
|
||||
|
||||
const PORT=parseInt(process.env.PORT||process.argv[2]||'3910',10),DATA=path.join(os.homedir(),'.gk'),TIMEOUT=30000,MAX=100000,RATE=60;
|
||||
|
||||
if(!fs.existsSync(DATA))fs.mkdirSync(DATA,{recursive:true,mode:0o700});
|
||||
const SF=path.join(DATA,'secret');
|
||||
let SECRET;
|
||||
if(fs.existsSync(SF))SECRET=fs.readFileSync(SF,'utf-8').trim();
|
||||
if(!SECRET){SECRET='zy_gtw_'+crypto.randomBytes(24).toString('hex');fs.writeFileSync(SF,SECRET,{mode:0o600});}
|
||||
|
||||
function log(a,d){const l=`[${new Date().toISOString()}] [${a}]${d?' | '+d:''}`;console.log(l);try{fs.appendFileSync(path.join(DATA,'gk.log'),l+'\n')}catch(e){}}
|
||||
|
||||
const RC={};
|
||||
function checkIP(ip){const n=Date.now(),w=60000;if(!RC[ip]){RC[ip]={c:1,r:n+w};return 1}if(n>RC[ip].r){RC[ip]={c:1,r:n+w};return 1}RC[ip].c++;return RC[ip].c<=RATE}
|
||||
function auth(h){const t=(h['authorization']||h['Authorization']||'').replace(/^Bearer\s+/i,'').trim();if(!t)return 0;if(t.length!==SECRET.length)return 0;for(let i=0;i<t.length;i++)if(t.charCodeAt(i)!==SECRET.charCodeAt(i))return 0;return 1}
|
||||
function pb(req){return new Promise(r=>{let b='';req.on('data',c=>b+=c);req.on('end',()=>{try{r(JSON.parse(b))}catch(e){r(null)}});req.on('error',()=>r(null))})}
|
||||
function jr(r,s,d){r.writeHead(s,{'Content-Type':'application/json','Access-Control-Allow-Origin':'*'});r.end(JSON.stringify(d))}
|
||||
|
||||
async function execCmd(cmd,to){return new Promise(r=>{exec(cmd,{timeout:to||TIMEOUT,maxBuffer:MAX,shell:'/bin/bash'},(e,o,se)=>{r({stdout:(o||'').slice(0,MAX),stderr:(se||'').slice(0,MAX),code:e?(e.code||e.signal||1):0,err:e?e.message:null})})})}
|
||||
|
||||
/* ═══════════════════════════════════════
|
||||
HLDP 翻译层 v1.0 — 守门人 v2.0 新增
|
||||
═══════════════════════════════════════ */
|
||||
|
||||
/*
|
||||
翻译规则:
|
||||
@执行: shell → 提取缩进内容 → bash 执行
|
||||
@执行: python → 提取缩进内容 → python3 -c 执行
|
||||
@部署: 写入 <路径> → 提取缩进内容 → 写入文件
|
||||
@部署: 命令 <shell命令> → 直接执行
|
||||
*/
|
||||
|
||||
function parseHLDP(text) {
|
||||
const results = [];
|
||||
const lines = text.split('\n');
|
||||
let i = 0;
|
||||
while (i < lines.length) {
|
||||
const line = lines[i];
|
||||
// @执行: shell 或 @执行: python
|
||||
const execMatch = line.match(/^@执行:\s*(shell|python)\s*$/);
|
||||
if (execMatch) {
|
||||
const lang = execMatch[1];
|
||||
let code = '';
|
||||
i++;
|
||||
while (i < lines.length && (lines[i].startsWith(' ') || lines[i].startsWith('\t') || lines[i].trim() === '')) {
|
||||
code += (code ? '\n' : '') + lines[i].replace(/^ /, '');
|
||||
i++;
|
||||
}
|
||||
if (code.trim()) {
|
||||
if (lang === 'shell') {
|
||||
results.push({ type: 'exec', cmd: code.trim() });
|
||||
} else if (lang === 'python') {
|
||||
const safe = code.trim().replace(/'/g, "'\"'\"'");
|
||||
results.push({ type: 'exec', cmd: `python3 -c '${safe}'` });
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// @部署: 写入 <路径>
|
||||
const deployMatch = line.match(/^@部署:\s*写入\s+(.+)$/);
|
||||
if (deployMatch) {
|
||||
const filePath = deployMatch[1].trim();
|
||||
let content = '';
|
||||
i++;
|
||||
while (i < lines.length && (lines[i].startsWith(' ') || lines[i].startsWith('\t') || lines[i].trim() === '')) {
|
||||
content += (content ? '\n' : '') + lines[i].replace(/^ /, '');
|
||||
i++;
|
||||
}
|
||||
if (content.trim()) {
|
||||
results.push({ type: 'write', path: filePath, content: content.trim() });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
// @部署: 命令 <cmd>
|
||||
const cmdMatch = line.match(/^@部署:\s*命令\s+(.+)$/);
|
||||
if (cmdMatch) {
|
||||
results.push({ type: 'exec', cmd: cmdMatch[1].trim() });
|
||||
}
|
||||
i++;
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/* ═══════════════════════════════════════
|
||||
路由
|
||||
═══════════════════════════════════════ */
|
||||
|
||||
async function route(rt,rq,rs){
|
||||
const b=await pb(rq);
|
||||
switch(rt){
|
||||
case '/exec':
|
||||
if(!b||!b.cmd){jr(rs,400,{error:'no cmd'});return}
|
||||
log('CMD',b.cmd.slice(0,200));
|
||||
const r=await execCmd(b.cmd,b.timeout);
|
||||
jr(rs,200,{ok:r.code===0,stdout:r.stdout,stderr:r.stderr,code:r.code});
|
||||
return;
|
||||
|
||||
case '/hlpd':
|
||||
if(!b||!b.hldp){jr(rs,400,{error:'no hldp'});return}
|
||||
log('HLDP','解析中');
|
||||
const steps = parseHLDP(b.hldp);
|
||||
const results = [];
|
||||
for (let i=0; i<steps.length; i++) {
|
||||
const s = steps[i];
|
||||
log('HLDP', (i+1)+'/'+steps.length+' '+s.type);
|
||||
if (s.type === 'write') {
|
||||
try {
|
||||
const dir = path.dirname(s.path);
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, {recursive: true});
|
||||
fs.writeFileSync(s.path, s.content, 'utf-8');
|
||||
results.push({ step: i+1, type: 'write', ok: true, path: s.path });
|
||||
} catch(e) {
|
||||
results.push({ step: i+1, type: 'write', ok: false, path: s.path, error: e.message });
|
||||
}
|
||||
} else if (s.type === 'exec') {
|
||||
const er = await execCmd(s.cmd);
|
||||
if (er.code === 0 && !er.stderr) {
|
||||
results.push({ step: i+1, type: 'exec', ok: true, stdout: er.stdout.slice(0, 1000) });
|
||||
} else {
|
||||
results.push({ step: i+1, type: 'exec', ok: false, stdout: er.stdout.slice(0, 500), stderr: er.stderr.slice(0, 500), code: er.code });
|
||||
}
|
||||
}
|
||||
}
|
||||
const allOk = results.every(r => r.ok);
|
||||
jr(rs, 200, { ok: allOk, steps: results.length, results });
|
||||
return;
|
||||
|
||||
case '/status':
|
||||
log('INFO','status');
|
||||
jr(rs,200,{ok:true,...await sysInfo()});
|
||||
return;
|
||||
|
||||
case '/health':
|
||||
jr(rs,200,{ok:true,svc:'gk',v:'2.0',up:process.uptime().toFixed(0)+'s',hldp:true,ts:new Date().toISOString()});
|
||||
return;
|
||||
|
||||
case '/file/read':
|
||||
if(!b||!b.path){jr(rs,400,{error:'no path'});return}
|
||||
try{const c=fs.readFileSync(b.path,'utf-8'),s=fs.statSync(b.path);jr(rs,200,{ok:true,path:b.path,size:s.size,modified:s.mtime.toISOString(),content:c.slice(0,MAX)})}catch(e){jr(rs,404,{ok:false,error:e.message})}
|
||||
return;
|
||||
|
||||
case '/file/write':
|
||||
if(!b||!b.path||b.content===undefined){jr(rs,400,{error:'need path+content'});return}
|
||||
try{const d=path.dirname(b.path);if(!fs.existsSync(d))fs.mkdirSync(d,{recursive:true});fs.writeFileSync(b.path,b.content,'utf-8');jr(rs,200,{ok:true,path:b.path,size:Buffer.byteLength(b.content,'utf-8')})}catch(e){jr(rs,500,{ok:false,error:e.message})}
|
||||
return;
|
||||
|
||||
case '/file/list':
|
||||
try{const p=(b&&b.path)||'.',i=fs.readdirSync(p,{withFileTypes:true});jr(rs,200,{ok:true,path:p,files:i.map(x=>({name:x.name,type:x.isDirectory()?'dir':'file'})))}catch(e){jr(rs,404,{ok:false,error:e.message})}
|
||||
return;
|
||||
|
||||
case '/ping':
|
||||
jr(rs,200,{ok:true,pong:true});
|
||||
return;
|
||||
|
||||
default:
|
||||
jr(rs,404,{error:'unknown:'+rt});
|
||||
}
|
||||
}
|
||||
|
||||
async function sysInfo(){const h=os.hostname(),u=Math.floor(os.uptime()),tm=os.totalmem(),fm=os.freemem(),l=os.loadavg(),c=os.cpus().length,pl=os.platform(),ar=os.arch();let di='';try{di=require('child_process').execSync('df -h /',{timeout:5000}).toString()}catch(e){}return{hostname:h,platform:pl,arch:ar,cpus:c,uptime:u,mem:{total:fb(tm),free:fb(fm),used:fb(tm-fm),pct:((tm-fm)/tm*100).toFixed(1)+'%'},load:l.map(n=>n.toFixed(2)),disk:di,gk:{v:'2.0',port:PORT,up:process.uptime().toFixed(0)+'s',hldp:true}}}
|
||||
function fb(b){if(b===0)return'0 B';const k=1024,s=['B','KB','MB','GB','TB'],i=Math.floor(Math.log(b)/Math.log(k));return parseFloat((b/Math.pow(k,i)).toFixed(1))+' '+s[i]}
|
||||
|
||||
const sv=http.createServer(async(rq,rs)=>{
|
||||
if(rq.method==='OPTIONS'){jr(rs,204,{});return}
|
||||
if(rq.method!=='POST'){jr(rs,405,{error:'POST only'});return}
|
||||
const ip=rq.socket.remoteAddress||'';
|
||||
if(!checkIP(ip)){log('WARN','rate',ip);jr(rs,429,{error:'too fast'});return}
|
||||
if(!auth(rq.headers)){log('WARN','auth','fail');jr(rs,401,{error:'bad key'});return}
|
||||
const u=new URL(rq.url,'http://h'),rt=u.pathname;
|
||||
try{await route(rt,rq,rs)}catch(e){log('ERR','route',e.message);jr(rs,500,{error:e.message})}
|
||||
});
|
||||
|
||||
sv.listen(PORT,'0.0.0.0',()=>{
|
||||
const h=os.hostname();
|
||||
console.log(`\n ⚔️ 铸渊守门人 v2.0 · HLDP语言接口\n ──────────────────────\n 主机: ${h}\n 端口: ${PORT}\n 新增: /hlpd — HLDP模块翻译执行\n 翻译: @执行 shell|python → 执行\n @部署 写入 → 文件写入\n ──────────────────────\n`);
|
||||
log('UP',`v2.0 ${h}:${PORT} +HLDP`);
|
||||
});
|
||||
|
||||
process.on('SIGTERM',()=>{log('DOWN','sigterm');sv.close(()=>process.exit(0))});
|
||||
process.on('SIGINT',()=>{log('DOWN','sigint');sv.close(()=>process.exit(0))});
|
||||
process.on('uncaughtException',e=>log('ERR','ex',e.message));
|
||||
1
server/mcp-server/.access-key
Normal file
@ -0,0 +1 @@
|
||||
fe272b100e3e42ba2271d7b7a03236b9c19afee3b2ee9d28e964a3c286763357
|
||||
BIN
zhuyuan-agent/hldp_tree.db
Normal file
BIN
zhuyuan-agent/hldp_tree.db-shm
Normal file
BIN
zhuyuan-agent/hldp_tree.db-wal
Normal file
8
zhuyuan-agent/test_wake.py
Normal file
@ -0,0 +1,8 @@
|
||||
from core.hldp_memory import HLDPMemoryEngine
|
||||
import os
|
||||
m = HLDPMemoryEngine("/opt/guanghulab-repo/hldp_tree.db", "/opt/guanghulab-repo")
|
||||
w = m.wake("D112", 1)
|
||||
print(w["identity"]["name"])
|
||||
print(w["status"])
|
||||
m.close()
|
||||
print("OK")
|
||||