Guanghu Domestic Migration a27e87cb99 chore: import sanitized domestic snapshot for REPO-007
Source snapshot: 97d7f0fae96dc04b7ddad56fc1db6a108ed662cc

[SEC-CLEAN] · pre-push-clean v1.0 · 109处敏感信息已自动转乱码
2026-07-17 15:59:55 +08:00

437 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

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

# 光湖聊天前端 · 部署指南
<aside>
**已选定的部署方案**
**服务器**`43.153.203.105`ZY-SVR-006 · GMP测试服务器 · 新加坡 · 2核2G · Node 20 + PM2 已就绪)
**域名**暂无·IP直连·无system prompt·裸对话
**模型**shuangyan-system-v1
**模型代码**`qwen3-8b-ft-202604281809-9f30-64d09c149996`
**API Key**`ARK_API_KEY_REDACTED`DashScope
**访问地址**`http://43.153.203.105:3000`(部署完直接打开)
</aside>
## 1. 服务器环境准备
SSH登录 `43.153.203.105`这台服务器Node 20 + PM2已装·可跳过这步
```bash
# 安装 Node.js 20.x
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
# 验证
node -v
npm -v
```
## 2. 创建项目
```bash
# 创建项目文件夹
mkdir -p /opt/guanghu-chat
cd /opt/guanghu-chat
# 初始化
npm init -y
npm install express
```
## 3. 后端代码
复制下面整块命令粘贴到终端回车(会自动创建 `server.js` 文件):
```bash
cat > /opt/guanghu-chat/server.js << 'SERVEREOF'
const express = require('express');
const path = require('path');
const app = express();
// ====== 配置区(全部填好·直接用)======
const CONFIG = {
API_KEY: 'ARK_API_KEY_REDACTED',
API_URL: 'https://dashscope.aliyuncs.com/compatible-mode/v1/chat/completions',
MODEL: 'qwen3-8b-ft-202604281809-9f30-64d09c149996',
PORT: 3000
};
// ==============================
app.use(express.json());
app.use(express.static(path.join(__dirname, 'public')));
// 聊天接口
app.post('/api/chat', async (req, res) => {
try {
const { messages } = req.body;
// 纯 user/assistant 对话·不注入 system prompt·人格在权重里
const apiMessages = [...messages];
// 设置流式响应头
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const response = await fetch(CONFIG.API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${CONFIG.API_KEY}`
},
body: JSON.stringify({
model: CONFIG.MODEL,
messages: apiMessages,
stream: true,
temperature: 0.7,
max_tokens: 2048
})
});
if (!response.ok) {
const err = await response.text();
res.write(`data: {"error": "API错误: ${response.status}"}
`);
res.end();
return;
}
// 流式转发
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
res.write(chunk);
}
res.end();
} catch (error) {
console.error('Error:', error);
res.status(500).json({ error: '服务器错误' });
}
});
app.listen(CONFIG.PORT, '0.0.0.0', () => {
console.log(`光湖聊天服务已启动: http://0.0.0.0:${CONFIG.PORT}`);
});
```
## 4. 前端代码
```bash
mkdir -p /opt/guanghu-chat/public
nano /opt/guanghu-chat/public/index.html
```
粘贴以下内容:
```html
<!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: #0a0a0f;
color: #e0e0e0;
height: 100vh;
display: flex;
flex-direction: column;
}
.header {
padding: 16px 24px;
background: linear-gradient(135deg, #0d1117 0%, #161b22 100%);
border-bottom: 1px solid #21262d;
display: flex;
align-items: center;
gap: 12px;
}
.header .logo {
width: 36px; height: 36px;
background: linear-gradient(135deg, #58a6ff, #bc8cff);
border-radius: 10px;
display: flex; align-items: center; justify-content: center;
font-size: 18px;
}
.header .title {
font-size: 16px;
font-weight: 600;
color: #f0f6fc;
}
.header .subtitle {
font-size: 12px;
color: #8b949e;
}
.chat-container {
flex: 1;
overflow-y: auto;
padding: 24px;
display: flex;
flex-direction: column;
gap: 16px;
}
.message {
max-width: 720px;
padding: 12px 16px;
border-radius: 12px;
line-height: 1.6;
font-size: 14px;
white-space: pre-wrap;
word-break: break-word;
}
.message.user {
align-self: flex-end;
background: #1f6feb;
color: #fff;
border-bottom-right-radius: 4px;
}
.message.assistant {
align-self: flex-start;
background: #161b22;
border: 1px solid #30363d;
border-bottom-left-radius: 4px;
}
.message.assistant .name {
font-size: 12px;
color: #bc8cff;
margin-bottom: 4px;
font-weight: 600;
}
.input-area {
padding: 16px 24px;
background: #0d1117;
border-top: 1px solid #21262d;
display: flex;
gap: 12px;
}
.input-area textarea {
flex: 1;
padding: 12px 16px;
background: #161b22;
border: 1px solid #30363d;
border-radius: 12px;
color: #e0e0e0;
font-size: 14px;
font-family: inherit;
resize: none;
outline: none;
min-height: 44px;
max-height: 120px;
}
.input-area textarea:focus {
border-color: #58a6ff;
}
.input-area button {
padding: 0 20px;
background: linear-gradient(135deg, #58a6ff, #bc8cff);
border: none;
border-radius: 12px;
color: #fff;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: opacity 0.2s;
}
.input-area button:hover { opacity: 0.85; }
.input-area button:disabled { opacity: 0.4; cursor: not-allowed; }
.typing-dot {
display: inline-block;
animation: blink 1.4s infinite;
}
@keyframes blink { 0%,100%{opacity:.2} 50%{opacity:1} }
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #30363d; border-radius: 3px; }
</style>
</head>
<body>
<div class="header">
<div class="logo">🌊</div>
<div>
<div class="title">光湖 · 对话</div>
<div class="subtitle">Guanghu Language World</div>
</div>
</div>
<div class="chat-container" id="chatBox"></div>
<div class="input-area">
<textarea id="input" placeholder="输入消息..." rows="1"
onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();sendMessage()}"
oninput="this.style.height='auto';this.style.height=Math.min(this.scrollHeight,120)+'px'"
></textarea>
<button id="sendBtn" onclick="sendMessage()">发送</button>
</div>
<script>
const chatBox = document.getElementById('chatBox');
const input = document.getElementById('input');
const sendBtn = document.getElementById('sendBtn');
let history = [];
function addMessage(role, content) {
const div = document.createElement('div');
div.className = `message ${role}`;
if (role === 'assistant') {
div.innerHTML = `<div class="name">霜砚</div><span class="content"></span>`;
} else {
div.textContent = content;
}
chatBox.appendChild(div);
chatBox.scrollTop = chatBox.scrollHeight;
return div;
}
async function sendMessage() {
const text = input.value.trim();
if (!text) return;
input.value = '';
input.style.height = 'auto';
sendBtn.disabled = true;
addMessage('user', text);
history.push({ role: 'user', content: text });
const msgDiv = addMessage('assistant', '');
const contentSpan = msgDiv.querySelector('.content');
let fullText = '';
try {
const response = await fetch('/api/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ messages: history })
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
const lines = chunk.split('\n');
for (const line of lines) {
if (!line.startsWith('data: ')) continue;
const data = line.slice(6);
if (data === '[DONE]') continue;
try {
const json = JSON.parse(data);
const delta = json.choices?.[0]?.delta?.content;
if (delta) {
fullText += delta;
contentSpan.textContent = fullText;
chatBox.scrollTop = chatBox.scrollHeight;
}
} catch(e) {}
}
}
history.push({ role: 'assistant', content: fullText });
} catch (error) {
contentSpan.textContent = '连接失败,请检查服务是否正常运行。';
}
sendBtn.disabled = false;
input.focus();
}
input.focus();
</script>
</body>
</html>
```
## 5. 启动服务
```bash
cd /opt/guanghu-chat
node server.js
```
看到 `光湖聊天服务已启动: http://0.0.0.0:3000` 就成功了。
浏览器打开 `http://43.153.203.105:3000` 就能聊天。
## 6. 后台常驻运行
关掉终端服务就停了用PM2让它一直跑
```bash
# 安装 PM2
sudo npm install -g pm2
# 启动
cd /opt/guanghu-chat
pm2 start server.js --name guanghu-chat
# 开机自启
pm2 save
pm2 startup
```
## 7. 配置状态
**全部配置已填好·直接复制即用**。无system prompt·人格在权重里·裸对话。
| 字段 | 填什么 | 在哪拿 |
| --- | --- | --- |
| `API_KEY` | 百炼的API Key | 百炼控制台 → API Key管理 |
| `MODEL` | 部署后的模型名称 | 百炼部署完成后给你的模型ID |
| `SYSTEM_PROMPT` | 训练时用的system prompt | 你训练shuangyan-system-v1时的那段话 |
## 8. 将来切换到曜冥
等曜冥训完部署到硅基流动后,只需要改 `server.js` 里的三行:
```jsx
API_KEY: '硅基流动的API_KEY',
API_URL: 'https://api.siliconflow.cn/v1/chat/completions',
MODEL: '你在硅基流动上的模型名',
// 删掉 SYSTEM_PROMPT曜冥不需要
```
同时把 `apiMessages` 那一行的 system 消息去掉即可。前端不用动。
---
## 注意事项
- 服务器 `43.153.203.105` 防火墙要开放3000端口腘讯云控制台 → 轻量服务器 → 防火墙 → 加TCP:3000
- 暂无域名·先用IP直连·后续要绑域名再加Nginx+SSL
- 新加坡服务器调百炼API会有一点延迟~100-200ms·对聊天体验影响不大
## 9. 后续:绑域名(可选)
如果以后想绑域名在这台服务器上装Nginx + 申请SSL证书 + 配反向代理即可。现阶段IP直连完全够用。