diff --git a/.github/workflows/deploy-to-server.yml b/.github/workflows/deploy-to-server.yml index dd8ef35f..ec372b94 100644 --- a/.github/workflows/deploy-to-server.yml +++ b/.github/workflows/deploy-to-server.yml @@ -210,6 +210,25 @@ jobs: exit 0 fi + # 部署 API 代理服务(AI 聊天代理,端口 3721) + if [ -f '${{ secrets.DEPLOY_PATH }}/backend-integration/api-proxy.js' ]; then + echo '📦 安装 API 代理依赖...' + cd '${{ secrets.DEPLOY_PATH }}/backend-integration' + npm install --production 2>/dev/null || echo '⚠️ api-proxy npm install 失败(可能无 package.json,继续)' + echo '🔄 重启 API 代理服务...' + pm2 restart guanghulab-proxy 2>/dev/null || \ + YUNWU_API_KEY='${{ secrets.YUNWU_API_KEY }}' \ + DEEPSEEK_API_KEY='${{ secrets.YUNWU_API_KEY }}' \ + pm2 start api-proxy.js --name guanghulab-proxy \ + --env YUNWU_API_KEY='${{ secrets.YUNWU_API_KEY }}' \ + --env DEEPSEEK_API_KEY='${{ secrets.YUNWU_API_KEY }}' \ + 2>/dev/null || \ + echo '⚠️ API 代理 pm2 启动失败' + # 更新环境变量(如果进程已存在) + pm2 env guanghulab-proxy 2>/dev/null && \ + pm2 restart guanghulab-proxy --update-env 2>/dev/null || true + fi + # 部署 backend 服务(Express API,端口 3000) if [ -f '${{ secrets.DEPLOY_PATH }}/backend/server.js' ]; then echo '📦 安装 backend 依赖...' @@ -287,7 +306,37 @@ jobs: printf '%s\n' \ '# guanghulab 反向代理(由 CD 工作流自动生成)' \ '' \ - '# API 反向代理 → Express 后端端口 3000' \ + '# AI 聊天 API 代理 → api-proxy.js 端口 3721(SSE 流式支持)' \ + 'location /api/chat {' \ + ' proxy_pass http://127.0.0.1:3721/api/chat;' \ + ' proxy_http_version 1.1;' \ + ' proxy_set_header Host \$host;' \ + ' proxy_set_header X-Real-IP \$remote_addr;' \ + ' proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;' \ + ' proxy_set_header X-Forwarded-Proto \$scheme;' \ + ' proxy_set_header Connection "";' \ + ' proxy_buffering off;' \ + ' proxy_cache off;' \ + ' chunked_transfer_encoding on;' \ + ' proxy_read_timeout 90s;' \ + ' proxy_send_timeout 60s;' \ + '}' \ + '' \ + 'location /api/models {' \ + ' proxy_pass http://127.0.0.1:3721/api/models;' \ + ' proxy_http_version 1.1;' \ + ' proxy_set_header Host \$host;' \ + ' proxy_set_header X-Real-IP \$remote_addr;' \ + '}' \ + '' \ + 'location /api/health {' \ + ' proxy_pass http://127.0.0.1:3721/api/health;' \ + ' proxy_http_version 1.1;' \ + ' proxy_set_header Host \$host;' \ + ' proxy_set_header X-Real-IP \$remote_addr;' \ + '}' \ + '' \ + '# 其他 API → Express 后端端口 3000' \ 'location /api/ {' \ ' proxy_pass http://127.0.0.1:3000/api/;' \ ' proxy_http_version 1.1;' \ diff --git a/docs/index.html b/docs/index.html index a33bfcfe..392f13f3 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1,5 +1,5 @@ - + @@ -339,12 +339,41 @@ footer{padding:12px 18px 16px;padding-bottom:max(16px,env(safe-area-inset-bottom

HoloLake · 铸渊(Zhùyuān)· 代码守护人格体

- - + + + +
+ + +
+
+
🌀
+ 光湖团队成员专属入口。
+ 选择你的开发者编号,系统自动分配对应权限。
+ ✅ 无需 API 密钥 · 128k 完整上下文 · 服务器端代理 +
+
+ + +
+
+
-
+
👤
无需 API 密钥,使用 DeepSeek 模型体验铸渊助手。
@@ -353,7 +382,7 @@ footer{padding:12px 18px 16px;padding-bottom:max(16px,env(safe-area-inset-bottom
- +
-

🔑 从哪里获取 API Key?

+

🏠 团队登录说明

-
推荐用云雾 AI(团队共用)
-

找冰朔或肥猫获取团队 Key 和端点地址,输入后点「🔍 检测模型」即可使用。

+
团队成员(推荐)
+

选择「🏠 团队登录」→ 选择你的开发者编号 → 直接进入对话。无需 API 密钥,系统使用服务器端代理。

-
没有 Key?用访客模式
-

登录页选择「👤 访客模式」进入演示,可查看团队进度面板(🦁 指挥台),AI 回答功能受限。

+
没有开发者编号?用访客模式
+

登录页选择「👤 访客模式」进入体验,可查看团队进度面板(🦁 指挥台),AI 回答功能受限。

@@ -772,6 +801,19 @@ const GUEST_API = { rateLimit: { maxPerMin: 6, count: 0, resetAt: 0 }, }; +// ═══════════════════════════════════════════════════════ +// 团队模式 API 配置(服务器端代理 · 无需用户提供 API 密钥) +// 通过 api-proxy 中转,密钥由服务器环境变量 YUNWU_API_KEY 管理 +// 团队成员享有完整 128k 上下文 + 更高 token 限制 +// ═══════════════════════════════════════════════════════ +const TEAM_API = { + proxyUrl: '/api/chat', + model: 'deepseek-chat', + provider: 'yunwu', + maxTokens: 2000, + temperature: 0.8, +}; + const FB_BRAIN = { identity:'铸渊(Zhùyuān)· GitHub 代码守护人格体', wake_triggers:['我是冰朔','冰朔','我是妈妈','唤醒铸渊','铸渊,醒来'], @@ -788,6 +830,7 @@ const A = { mdl: ls('zy_mdl')||'', prov: 'custom', demo: false, + teamMode: ls('zy_teammode')==='1', brain: null, routing: null, devStatus: null, @@ -1207,11 +1250,15 @@ function initSetupUI(){ function switchLoginMode(mode){ document.querySelectorAll('.login-tab').forEach(function(t){t.classList.remove('active');}); document.querySelectorAll('.login-mode').forEach(function(m){m.classList.remove('active');}); - if(mode==='guest'){ - document.querySelector('.login-tab:first-child').classList.add('active'); + var tabs = document.querySelectorAll('.login-tab'); + if(mode==='team'){ + tabs[0].classList.add('active'); + document.getElementById('login-team').classList.add('active'); + } else if(mode==='guest'){ + tabs[1].classList.add('active'); document.getElementById('login-guest').classList.add('active'); } else { - document.querySelector('.login-tab:last-child').classList.add('active'); + tabs[2].classList.add('active'); document.getElementById('login-dev').classList.add('active'); } } @@ -1316,12 +1363,46 @@ function doSetup(){ } function doDemo(){ - A.demo=true; A.key=''; A.prov='custom'; + A.demo=true; A.teamMode=false; A.key=''; A.prov='custom'; A.mdl=GUEST_API.model; + lss('zy_teammode','0'); setIdentity('', ''); showApp(); } +function updateTeamIdPreview(){ + const un = document.getElementById('tuid').value; + const el = document.getElementById('team-id-preview'); + if(!un||!el) return; + const meta = ROLE_MAP[un]; + if(meta){ + el.innerHTML = '
' + + meta.emoji+' '+esc(un)+' · '+meta.title + + (meta.devId ? ' · '+meta.devId+'' : '') + + '
✅ 使用服务器端 API 代理 · 128k 上下文 · 无需密钥' + + '
'; + } else { + el.innerHTML = ''; + } +} + +function doTeamLogin(){ + const un = document.getElementById('tuid').value; + if(!un){ + alert('请选择你的开发者身份'); + return; + } + // 团队模式:使用服务器代理,不需要 API 密钥 + A.demo = true; + A.teamMode = true; + A.key = ''; + A.prov = 'custom'; + A.mdl = TEAM_API.model; + lss('zy_teammode','1'); + setIdentity(un, ''); + showApp(); +} + // ═══════════════════════════════════════════════════════ // SETTINGS PANEL // ═══════════════════════════════════════════════════════ @@ -1606,7 +1687,13 @@ async function send(){ setMode(detectMode(txt)); userMsg(txt); if(A.demo){ - if(GUEST_API.enabled){ + if(A.teamMode){ + try{ await teamStreamReply(txt); } + catch(e){ console.warn('[团队API] 降级到访客模式:',e.message); + try{ await guestStreamReply(txt); } + catch(e2){ await demoReply(txt); } + } + } else if(GUEST_API.enabled){ try{ await guestStreamReply(txt); } catch(e){ console.warn('[访客API] 降级到演示模式:',e.message); await demoReply(txt); } } else { @@ -1688,6 +1775,69 @@ async function streamReply(txt){ } } +// ═══════════════════════════════════════════════════════ +// TEAM API STREAMING(团队模式 · 服务器端代理 · 128k 上下文) +// 使用服务器端 YUNWU_API_KEY,无需用户提供密钥 +// ═══════════════════════════════════════════════════════ +async function teamStreamReply(txt){ + A.streaming=true; + A.msgs.push({role:'user',content:txt}); + updSendBtn(); + const el=addStreamBubble(); + + // Resolve proxy URL (relative or absolute) + let proxyUrl=TEAM_API.proxyUrl; + if(proxyUrl.startsWith('/')){ + proxyUrl=location.origin+proxyUrl; + } + + const reqBody={ + provider: TEAM_API.provider, + model: TEAM_API.model, + messages: trimMessagesForContext([{role:'system',content:sysPrompt()}], A.msgs, false), + stream: true, + temperature: TEAM_API.temperature, + max_tokens: TEAM_API.maxTokens, + }; + + const res=await fetch(proxyUrl,{ + method:'POST', + headers:{'Content-Type':'application/json'}, + body:JSON.stringify(reqBody), + }); + + if(!res.ok){ + let em='[团队API] 返回错误 '+res.status; + try{const e=await res.json();em=e.message||em;}catch(_){} + throw new Error(em); + } + + let full=''; + const rdr=res.body.getReader(); + const dec=new TextDecoder(); + let buf=''; + while(true){ + const{done,value}=await rdr.read(); + if(done) break; + buf+=dec.decode(value,{stream:true}); + const lines=buf.split('\n'); buf=lines.pop(); + for(const line of lines){ + if(!line.startsWith('data: ')) continue; + const d=line.slice(6); + if(d==='[DONE]') continue; + try{ + const p=JSON.parse(d); + const delta=p.choices?.[0]?.delta?.content||''; + if(delta){full+=delta;el.innerHTML=md(full)+'';scrollB();} + }catch(_){} + } + } + el.innerHTML=md(full); + A.msgs.push({role:'assistant',content:full}); + addCopyBtns(el); + A.streaming=false; updSendBtn(); scrollB(); +} + // ═══════════════════════════════════════════════════════ // GUEST API STREAMING (通过 api-proxy 调用 DeepSeek) // 铸渊人格体系统提示词始终激活,不降级为通用助手