feat: implement language-driven OS capabilities (ZY-LANGDRIVE)

Phase 3 - Write Capability Layer:
- Add backend/api-server/routes/write.js with 10 write endpoints
- Extend services/github.js with triggerWorkflow + createFile
- Add middleware/audit.js for operation audit logging
- Rate limiting: 10 writes/min per developer

Phase 4 - Intent Router Engine:
- Add config/function-tools.json with 10 operation definitions
- Add middleware/intent-router.js for NL → API routing
- Add docs/js/intent-tools.js for frontend function calling

Phase 5 - Permission Sandbox System:
- Add config/permissions.js with 4-level model (L0-L3)
- Add middleware/auth.js (identity + permission check)
- Add middleware/sandbox.js (preview environment guard)

Phase 6 - Cognitive Guidance System:
- Add config/onboarding-script.json (5-round guided dialogue)
- Add routes/onboarding.js (onboarding API + tools endpoint)
- Add docs/js/onboarding.js + docs/css/onboarding.css (UI)

Update server.js to v2.0.0 with all new routes and middleware

Co-authored-by: qinfendebingshuo <207279273+qinfendebingshuo@users.noreply.github.com>
Agent-Logs-Url: https://github.com/qinfendebingshuo/guanghulab/sessions/a260ce18-279a-4490-af90-a90a7b1f46cd
This commit is contained in:
copilot-swe-agent[bot] 2026-03-25 08:22:38 +00:00
parent 726b05bc0b
commit 38d8afef55
15 changed files with 1723 additions and 5 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
node_modules/
*.bak
*.log
backend/api-server/logs/

View File

@ -0,0 +1,97 @@
{
"tools": [
{
"name": "query_tickets",
"description": "查询开发者的工单列表。开发者说'我的工单'、'还有几个工单没关'、'查一下Bug'时触发。",
"parameters": {
"status": { "type": "string", "enum": ["全部", "待处理", "进行中", "已完成"], "default": "全部" }
},
"api": "GET /api/tickets?devId={devId}&status={status}",
"permission": "ticket:read"
},
{
"name": "create_ticket",
"description": "创建新工单。开发者说'建个工单'、'提一个Bug'、'报告问题'时触发。",
"parameters": {
"title": { "type": "string", "required": true },
"description": { "type": "string" },
"priority": { "type": "string", "enum": ["P0", "P1", "P2", "P3"], "default": "P2" },
"module": { "type": "string" }
},
"api": "POST /api/tickets/create",
"permission": "ticket:create"
},
{
"name": "submit_syslog",
"description": "提交系统日志。开发者说'提交日志'、'写SYSLOG'、'记录一下'时触发。",
"parameters": {
"content": { "type": "string", "required": true },
"type": { "type": "string", "enum": ["日常", "Bug修复", "功能完成", "部署", "紧急"], "default": "日常" }
},
"api": "POST /api/syslog/submit",
"permission": "syslog:submit"
},
{
"name": "query_my_status",
"description": "查询开发者自己的状态。开发者说'我的进度'、'我现在什么状态'、'我还有什么没做'时触发。",
"parameters": {},
"api": "GET /api/dev/{devId}",
"permission": "dev:read_self"
},
{
"name": "deploy_to_preview",
"description": "部署模块到预览站。开发者说'部署一下'、'上预览'、'推到测试站'时触发。",
"parameters": {
"module": { "type": "string", "required": true }
},
"api": "POST /api/deploy/preview",
"permission": "deploy:preview"
},
{
"name": "deploy_to_production",
"description": "部署模块到正式站。开发者说'上线'、'部署到正式站'、'发布到guanghulab.com'时触发。需要二次确认。",
"parameters": {
"module": { "type": "string", "required": true },
"confirmToken": { "type": "string" }
},
"api": "POST /api/deploy/production",
"permission": "deploy:production",
"dangerous": true
},
{
"name": "query_agents",
"description": "查询Agent注册表。开发者说'有哪些Agent'、'查一下Agent'、'系统里有谁'时触发。",
"parameters": {},
"api": "GET /api/agents",
"permission": "agent:read"
},
{
"name": "query_recent_syslogs",
"description": "查最近的系统日志。开发者说'最近有什么日志'、'系统最近怎么样'时触发。",
"parameters": {
"limit": { "type": "number", "default": 10 }
},
"api": "GET /api/syslogs?limit={limit}",
"permission": "syslog:read"
},
{
"name": "create_broadcast",
"description": "创建广播。开发者说'发个广播'、'通知一下'时触发。仅限有广播权限的开发者。",
"parameters": {
"title": { "type": "string", "required": true },
"content": { "type": "string", "required": true },
"target": { "type": "string", "description": "目标开发者编号,如 DEV-001或 ALL" }
},
"api": "POST /api/broadcasts/create",
"permission": "broadcast:create",
"dangerous": true
},
{
"name": "query_repo_status",
"description": "查询仓库状态。开发者说'仓库怎么样'、'最近有什么PR'、'CI状态'时触发。",
"parameters": {},
"api": "GET /api/repo/status",
"permission": "repo:read"
}
]
}

View File

@ -0,0 +1,39 @@
{
"onboarding": [
{
"round": 1,
"prompt": "👋 首先,试着问我你的状态。比如说:「我现在什么情况?」",
"expected_intent": "query_my_status",
"success_message": "✅ 很好!你看到了吗?我直接从 Notion 数据库里查了你的真实信息回来。这就是语言驱动——你说话,系统就动了。",
"hint": "💡 你可以说「我的状态」、「我的进度」、「我现在怎么样了」,怎么说都行,我能听懂。"
},
{
"round": 2,
"prompt": "📝 接下来,试着建一个工单。比如:「帮我建一个工单,标题是测试工单」",
"expected_intent": "create_ticket",
"success_message": "✅ 工单已经建好了!注意,这是在预览环境里建的,不会影响正式系统。当你升级到正式环境后,建的工单就是真的了。",
"hint": "💡 你可以说「建个工单」、「提个Bug」、「报告一个问题」都会触发建工单。"
},
{
"round": 3,
"prompt": "📊 再试试查一下系统日志。比如:「最近有什么日志?」",
"expected_intent": "query_recent_syslogs",
"success_message": "✅ 这些是从 Notion SYSLOG数据库里实时查的。每个开发者提交的日志你都能看到。",
"hint": "💡 你可以说「最近的日志」、「系统日志」、「看看SYSLOG」。"
},
{
"round": 4,
"prompt": "🚀 现在试试部署你的模块到预览站。比如:「部署 status-board 到预览站」",
"expected_intent": "deploy_to_preview",
"success_message": "✅ 部署已触发!在预览站你可以随便试。等你熟悉了,冰朔会给你开正式站的部署权限。",
"hint": "💡 你只能部署自己负责的模块。如果说了别人的模块,系统会友好提示你。"
},
{
"round": 5,
"prompt": "🎉 最后一轮。试试随便和我聊点什么。什么都行——问我问题、讲笑话、或者告诉我你想做什么。",
"expected_intent": null,
"success_message": "✅ 引导完成!\n\n你现在已经知道怎么和系统交互了。记住\n\n· **直接说人话**,不需要背命令\n· **想做什么就说什么**,我会帮你翻译成操作\n· **在预览站多练习**,正式站操作都是真的\n· **说错了也没关系**,我会帮你确认\n\n你的权限已经从「观察者」升级到「学习者」了 📖\n现在可以在预览站自由操作了。\n\n有什么问题随时找我。",
"hint": null
}
]
}

View File

@ -0,0 +1,101 @@
/**
* 权限等级定义 · 开发者权限沙箱系统
*
* 4 级权限模型观察者 学习者 执行者 管理者
* 版权国作登字-2026-A-00037559
*/
'use strict';
// 权限等级定义
var PERMISSION_LEVELS = {
0: {
name: '观察者',
label: '👀 观察者',
permissions: [
'dev:read_self',
'ticket:read',
'syslog:read',
'agent:read',
'repo:read'
],
description: '只读权限,熟悉系统'
},
1: {
name: '学习者',
label: '📖 学习者',
permissions: [
'dev:read_self',
'ticket:read', 'ticket:create',
'syslog:read', 'syslog:submit',
'agent:read',
'repo:read',
'deploy:preview'
],
description: '预览站可写,正式站只读'
},
2: {
name: '执行者',
label: '⚡ 执行者',
permissions: [
'dev:read_self', 'dev:update_self',
'ticket:read', 'ticket:create', 'ticket:update',
'syslog:read', 'syslog:submit',
'agent:read',
'repo:read',
'deploy:preview', 'deploy:production',
'maintenance:log'
],
description: '可操作自己的模块,含正式站部署'
},
3: {
name: '管理者',
label: '👑 管理者',
permissions: [
'dev:read_self', 'dev:read_all', 'dev:update_self', 'dev:update_all',
'ticket:read', 'ticket:create', 'ticket:update', 'ticket:delete',
'syslog:read', 'syslog:submit',
'agent:read', 'agent:update',
'repo:read',
'deploy:preview', 'deploy:production',
'broadcast:create',
'maintenance:log',
'permission:manage'
],
description: '系统管理员,全部权限'
}
};
// 开发者默认权限配置
var DEV_PERMISSIONS = {
'TCS-0002': { level: 3, environment: 'production' },
'DEV-001': { level: 0, environment: 'preview' },
'DEV-002': { level: 0, environment: 'preview' },
'DEV-003': { level: 0, environment: 'preview' },
'DEV-004': { level: 0, environment: 'preview' },
'DEV-005': { level: 0, environment: 'preview' },
'DEV-009': { level: 0, environment: 'preview' },
'DEV-010': { level: 0, environment: 'preview' },
'DEV-011': { level: 0, environment: 'preview' },
'DEV-012': { level: 0, environment: 'preview' }
};
// 开发者 → 模块映射
var DEV_MODULES = {
'DEV-001': ['backend/', 'src/'],
'DEV-002': ['frontend/', 'persona-selector/', 'chat-bubble/'],
'DEV-003': ['settings/', 'cloud-drive/'],
'DEV-004': ['dingtalk-bot/'],
'DEV-005': ['status-board/'],
'DEV-009': ['user-center/'],
'DEV-010': ['ticket-system/', 'data-stats/', 'dynamic-comic/'],
'DEV-011': ['writing-workspace/'],
'DEV-012': ['notification-center/'],
'TCS-0002': ['*']
};
module.exports = {
PERMISSION_LEVELS: PERMISSION_LEVELS,
DEV_PERMISSIONS: DEV_PERMISSIONS,
DEV_MODULES: DEV_MODULES
};

View File

@ -0,0 +1,63 @@
/**
* 操作审计日志中间件
*
* 记录每次 API 操作到日志文件什么时间做了什么
* 版权国作登字-2026-A-00037559
*/
'use strict';
var fs = require('fs');
var path = require('path');
// 优先使用配置的目录,本地开发时回退到项目内目录
var AUDIT_LOG_DIR = process.env.AUDIT_LOG_DIR ||
path.join(__dirname, '../../logs/audit');
/**
* 审计日志中间件
* 拦截请求和响应记录操作信息
*/
function auditLog(req, res, next) {
var startTime = Date.now();
var logEntry = {
timestamp: new Date().toISOString(),
devId: (req.user && req.user.devId) || req.headers['x-dev-id'] || 'anonymous',
method: req.method,
path: req.path,
ip: req.ip
};
// 对 POST/PATCH 请求记录请求体(排除敏感字段)
if ((req.method === 'POST' || req.method === 'PATCH') && req.body) {
var sanitizedBody = Object.assign({}, req.body);
delete sanitizedBody.password;
delete sanitizedBody.token;
delete sanitizedBody.confirmToken;
logEntry.body = sanitizedBody;
}
// 拦截响应完成
var originalSend = res.send;
res.send = function(body) {
logEntry.responseTime = Date.now() - startTime;
logEntry.statusCode = res.statusCode;
// 异步写入日志,不阻塞响应
try {
fs.mkdirSync(AUDIT_LOG_DIR, { recursive: true });
var today = new Date().toISOString().split('T')[0];
var logFile = path.join(AUDIT_LOG_DIR, 'audit-' + today + '.jsonl');
fs.appendFileSync(logFile, JSON.stringify(logEntry) + '\n');
} catch (e) {
console.error('审计日志写入失败:', e.message);
}
return originalSend.call(this, body);
};
next();
}
module.exports = { auditLog: auditLog };

View File

@ -0,0 +1,88 @@
/**
* 身份验证 + 权限检查中间件
*
* 人格体缓冲层铁律人类不直接操作系统人格体是唯一中间翻译层
* 版权国作登字-2026-A-00037559
*/
'use strict';
var permissions = require('../config/permissions');
var PERMISSION_LEVELS = permissions.PERMISSION_LEVELS;
var DEV_PERMISSIONS = permissions.DEV_PERMISSIONS;
var DEV_MODULES = permissions.DEV_MODULES;
/**
* 开发者身份验证中间件
* 要求请求头携带 x-dev-id authorization
*/
function requireAuth(req, res, next) {
var devId = req.headers['x-dev-id'];
var token = req.headers['authorization'];
if (!devId || !token) {
return res.status(401).json({
error: true,
code: 'AUTH_REQUIRED',
message: '请先登录。需要携带 x-dev-id 和 authorization 请求头。',
reply: '🔒 请先登录。你需要使用你的开发者编号登录系统。'
});
}
var devConfig = DEV_PERMISSIONS[devId];
if (!devConfig) {
return res.status(401).json({
error: true,
code: 'UNKNOWN_DEV',
message: '未知的开发者编号: ' + devId,
reply: '🔒 未知的开发者编号:' + devId + '。请联系冰朔注册。'
});
}
var level = PERMISSION_LEVELS[devConfig.level];
req.user = {
devId: devId,
permissionLevel: devConfig.level,
permissionLabel: level.label,
permissions: level.permissions,
environment: devConfig.environment,
modules: DEV_MODULES[devId] || []
};
next();
}
/**
* 权限检查中间件工厂
* @param {string} requiredPermission - 需要的权限标识
*/
function checkPermission(requiredPermission) {
return function(req, res, next) {
if (!req.user) {
return res.status(401).json({
error: true,
code: 'AUTH_REQUIRED',
message: '身份验证缺失'
});
}
if (!req.user.permissions.includes(requiredPermission)) {
return res.status(403).json({
error: true,
code: 'PERMISSION_DENIED',
message: '权限不足,需要: ' + requiredPermission,
reply: '🔒 权限不足。你的等级是 ' + req.user.permissionLabel +
',需要「' + requiredPermission + '」权限。\n\n' +
'💡 在预览站多练习,熟悉系统后冰朔会给你升级权限。'
});
}
next();
};
}
module.exports = {
requireAuth: requireAuth,
checkPermission: checkPermission
};

View File

@ -0,0 +1,110 @@
/**
* 意图路由中间件 · 自然语言 操作映射
*
* 核心逻辑
* 1. 接收模型输出的 function_call
* 2. 验证权限
* 3. 调用对应的后端 API
* 4. 返回结果
*
* 版权国作登字-2026-A-00037559
*/
'use strict';
var tools = require('../config/function-tools.json');
/**
* 意图路由核心
*
* @param {Object} functionCall - { name, parameters }
* @param {Object} user - 当前用户信息 permissions, environment, modules
* @param {Function} apiClient - API 调用函数 (method, path, body) => result
* @returns {Object} 执行结果
*/
async function routeIntent(functionCall, user, apiClient) {
var tool = null;
for (var i = 0; i < tools.tools.length; i++) {
if (tools.tools[i].name === functionCall.name) {
tool = tools.tools[i];
break;
}
}
if (!tool) {
return {
success: false,
reply: '❓ 我不认识这个操作:' + functionCall.name + '。你可以问我能做什么。'
};
}
// 权限检查
if (!user.permissions.includes(tool.permission)) {
return {
success: false,
reply: '🔒 你没有执行「' + tool.description + '」的权限。当前权限等级:' + user.permissionLabel
};
}
// 危险操作二次确认
if (tool.dangerous && !functionCall.parameters.confirmToken) {
return {
success: false,
requireConfirmation: true,
reply: '⚠️ 这是一个**危险操作**。请说「确认执行」来确认。'
};
}
// 预览环境检查
if (user.environment === 'preview' && tool.permission.includes('production')) {
return {
success: false,
reply: '🔒 你目前在**预览环境**,不能执行正式站操作。在预览站充分练习后,由冰朔开放正式站权限。'
};
}
// 解析 API 路径
var apiDef = tool.api.split(' ');
var method = apiDef[0];
var path = apiDef[1];
// 替换路径中的变量
path = path.replace('{devId}', user.devId);
var params = functionCall.parameters || {};
for (var key in params) {
path = path.replace('{' + key + '}', encodeURIComponent(params[key]));
}
// 调用 API
try {
var result = await apiClient(method, path, Object.assign({}, params, { devId: user.devId }));
return result;
} catch (e) {
return {
success: false,
reply: '❌ 操作失败:' + e.message
};
}
}
/**
* 获取用户可用的工具列表已根据权限过滤
*/
function getAvailableTools(user) {
return tools.tools.filter(function(tool) {
return user.permissions.includes(tool.permission);
});
}
/**
* 获取全部工具定义
*/
function getAllTools() {
return tools.tools;
}
module.exports = {
routeIntent: routeIntent,
getAvailableTools: getAvailableTools,
getAllTools: getAllTools
};

View File

@ -0,0 +1,46 @@
/**
* 预览站沙箱守卫中间件
*
* 预览环境中的写入操作在沙箱中执行不影响真实系统
* 版权国作登字-2026-A-00037559
*/
'use strict';
// 沙箱数据库 ID通过环境变量配置Notion 中的独立数据库)
var SANDBOX_DB_IDS = {
ticketBook: process.env.SANDBOX_TICKET_DB_ID || '',
syslogInbox: process.env.SANDBOX_SYSLOG_DB_ID || '',
maintenanceLog: process.env.SANDBOX_MAINTENANCE_DB_ID || ''
};
/**
* 沙箱守卫中间件
*
* 如果开发者在预览环境中执行写入操作
* - Notion 写入 重定向到沙箱数据库
* - GitHub 操作 重定向到 preview 分支
* - 所有操作标记 [SANDBOX]
*/
function sandboxGuard(req, res, next) {
if (req.user && req.user.environment === 'preview') {
req.sandbox = true;
req.sandboxDbIds = SANDBOX_DB_IDS;
// 预览环境中禁止正式站部署
if (req.path.includes('/deploy/production')) {
return res.status(403).json({
error: true,
code: 'SANDBOX_BLOCKED',
message: '预览环境不允许正式站部署',
reply: '🏖️ 你目前在预览环境。正式站部署需要升级到 Level 2执行者权限。\n\n继续在预览站练习吧你做得很好'
});
}
}
next();
}
module.exports = {
sandboxGuard: sandboxGuard,
SANDBOX_DB_IDS: SANDBOX_DB_IDS
};

View File

@ -0,0 +1,136 @@
/**
* 认知引导流程 API 路由
*
* GET /api/onboarding/status 获取引导状态
* POST /api/onboarding/start 开始引导
* POST /api/onboarding/complete 完成引导自动升级权限
* GET /api/tools 获取可用工具列表根据权限过滤
*
* 版权国作登字-2026-A-00037559
*/
'use strict';
var express = require('express');
var router = express.Router();
var authMiddleware = require('../middleware/auth');
var intentRouter = require('../middleware/intent-router');
var onboardingScript = require('../config/onboarding-script.json');
var permissions = require('../config/permissions');
// 引导状态存储(内存,生产环境应持久化)
var onboardingState = new Map();
// ====== 获取引导状态 ======
router.get('/onboarding/status',
authMiddleware.requireAuth,
function(req, res) {
var devId = req.user.devId;
var state = onboardingState.get(devId);
if (!state) {
// 未开始引导
res.json({
started: false,
completed: false,
currentRound: 0,
totalRounds: onboardingScript.onboarding.length,
permissionLevel: req.user.permissionLevel
});
} else {
res.json({
started: true,
completed: state.completed,
currentRound: state.currentRound,
totalRounds: onboardingScript.onboarding.length,
permissionLevel: req.user.permissionLevel,
script: state.completed ? null : onboardingScript.onboarding[state.currentRound - 1]
});
}
}
);
// ====== 开始/推进引导 ======
router.post('/onboarding/start',
authMiddleware.requireAuth,
function(req, res) {
var devId = req.user.devId;
var state = onboardingState.get(devId);
if (!state) {
// 开始新引导
state = { currentRound: 1, completed: false, startedAt: new Date().toISOString() };
onboardingState.set(devId, state);
} else if (!state.completed) {
// 推进到下一轮
if (state.currentRound < onboardingScript.onboarding.length) {
state.currentRound++;
} else {
state.completed = true;
state.completedAt = new Date().toISOString();
}
}
var script = state.completed ? null : onboardingScript.onboarding[state.currentRound - 1];
res.json({
currentRound: state.currentRound,
totalRounds: onboardingScript.onboarding.length,
completed: state.completed,
script: script
});
}
);
// ====== 完成引导(自动升级权限)======
router.post('/onboarding/complete',
authMiddleware.requireAuth,
function(req, res) {
var devId = req.user.devId;
var state = onboardingState.get(devId);
if (!state) {
return res.status(400).json({
error: true,
code: 'NOT_STARTED',
message: '引导尚未开始'
});
}
// 标记完成
state.completed = true;
state.completedAt = new Date().toISOString();
// 自动升级权限Level 0 → Level 1
var devConfig = permissions.DEV_PERMISSIONS[devId];
if (devConfig && devConfig.level === 0) {
devConfig.level = 1;
}
res.json({
success: true,
previousLevel: 0,
newLevel: devConfig ? devConfig.level : 0,
reply: '🎉 恭喜!你已完成认知引导。\n\n你的权限已从「👀 观察者」升级为「📖 学习者」。\n现在可以在预览站创建工单、提交日志、触发部署了。\n\n有什么问题随时找我。'
});
}
);
// ====== 获取可用工具列表 ======
router.get('/tools',
authMiddleware.requireAuth,
function(req, res) {
var available = intentRouter.getAvailableTools(req.user);
res.json({
dev_id: req.user.devId,
permission_level: req.user.permissionLevel,
permission_label: req.user.permissionLabel,
environment: req.user.environment,
tools_count: available.length,
tools: available
});
}
);
module.exports = router;

View File

@ -0,0 +1,476 @@
/**
* 写入类 API 路由 · 语言驱动操作系统写入能力层
*
* POST /api/tickets/create 创建工单
* PATCH /api/tickets/:ticketId 更新工单状态
* POST /api/syslog/submit 提交 SYSLOG
* POST /api/broadcasts/create 创建广播
* POST /api/deploy/preview 触发预览站部署
* POST /api/deploy/production 触发正式站部署需二次确认
* PATCH /api/agents/:agentId 更新 Agent 状态
* POST /api/maintenance/log 写入维护日志
* POST /api/receipts/submit 提交指令回执
* PATCH /api/dev/:devId/status 更新开发者状态
*
* 安全规则所有写入接口需要身份验证 + 权限检查 + 审计日志
* 版权国作登字-2026-A-00037559
*/
'use strict';
var express = require('express');
var router = express.Router();
var notionService = require('../services/notion');
var githubService = require('../services/github');
var dbConfig = require('../config/databases');
var authMiddleware = require('../middleware/auth');
var sandboxMiddleware = require('../middleware/sandbox');
var auditMiddleware = require('../middleware/audit');
// 所有写入接口需要身份验证 + 审计 + 沙箱检查
router.use(authMiddleware.requireAuth);
router.use(auditMiddleware.auditLog);
router.use(sandboxMiddleware.sandboxGuard);
// 频率限制每个开发者每分钟最多10次写入
var rateLimitMap = new Map();
var RATE_LIMIT = 10;
var RATE_WINDOW = 60 * 1000;
function rateLimit(req, res, next) {
var key = req.user.devId;
var now = Date.now();
var entry = rateLimitMap.get(key);
if (!entry || now - entry.start >= RATE_WINDOW) {
rateLimitMap.set(key, { start: now, count: 1 });
return next();
}
if (entry.count >= RATE_LIMIT) {
return res.status(429).json({
error: true,
code: 'RATE_LIMITED',
message: '写入频率超限(每分钟最多 ' + RATE_LIMIT + ' 次)',
reply: '⏳ 操作太频繁了,请稍等一分钟再试。'
});
}
entry.count++;
next();
}
router.use(rateLimit);
// 获取实际数据库 ID沙箱环境使用沙箱 DB
function getDbId(req, configKey) {
if (req.sandbox && req.sandboxDbIds && req.sandboxDbIds[configKey]) {
return req.sandboxDbIds[configKey];
}
return dbConfig[configKey];
}
// ====== 创建工单 ======
router.post('/tickets/create',
authMiddleware.checkPermission('ticket:create'),
async function(req, res) {
try {
var title = req.body.title;
var description = req.body.description || '';
var priority = req.body.priority || 'P2';
var module = req.body.module || '';
var devId = req.user.devId;
if (!title) {
return res.status(400).json({
error: true,
code: 'MISSING_TITLE',
message: '工单标题不能为空'
});
}
var ticketDbId = getDbId(req, 'ticketBook');
if (!ticketDbId) {
return res.status(503).json({
error: true,
code: 'DB_NOT_CONFIGURED',
message: '工单数据库未配置'
});
}
var properties = {
'工单标题': { title: [{ text: { content: title } }] },
'描述': { rich_text: [{ text: { content: description } }] },
'优先级': { select: { name: priority } },
'提交人': { rich_text: [{ text: { content: devId } }] },
'状态': { select: { name: '待处理' } }
};
if (module) {
properties['模块'] = { select: { name: module } };
}
var result = await notionService.writeToDB(ticketDbId, properties);
var prefix = req.sandbox ? '[沙箱] ' : '';
res.json({
success: true,
message: prefix + '工单已创建',
ticketUrl: result.url || '',
reply: '✅ ' + prefix + '工单「' + title + '」已创建,优先级 ' + priority + ',状态:待处理。'
});
} catch (e) {
res.status(500).json({ error: true, code: 'CREATE_FAILED', message: e.message });
}
}
);
// ====== 更新工单状态 ======
router.patch('/tickets/:ticketId',
authMiddleware.checkPermission('ticket:update'),
async function(req, res) {
try {
var ticketId = req.params.ticketId;
var status = req.body.status;
if (!status) {
return res.status(400).json({ error: true, code: 'MISSING_STATUS', message: '状态不能为空' });
}
// 使用 Notion page update API
if (!notionService.notion) {
return res.status(503).json({ error: true, code: 'NOTION_UNAVAILABLE', message: 'Notion 未连接' });
}
await notionService.notion.pages.update({
page_id: ticketId,
properties: {
'状态': { select: { name: status } }
}
});
res.json({
success: true,
reply: '✅ 工单状态已更新为:' + status
});
} catch (e) {
res.status(500).json({ error: true, code: 'UPDATE_FAILED', message: e.message });
}
}
);
// ====== 提交 SYSLOG ======
router.post('/syslog/submit',
authMiddleware.checkPermission('syslog:submit'),
async function(req, res) {
try {
var content = req.body.content;
var type = req.body.type || '日常';
var devId = req.user.devId;
if (!content) {
return res.status(400).json({ error: true, code: 'MISSING_CONTENT', message: 'SYSLOG 内容不能为空' });
}
var syslogDbId = getDbId(req, 'syslogInbox');
if (!syslogDbId) {
return res.status(503).json({ error: true, code: 'DB_NOT_CONFIGURED', message: 'SYSLOG 数据库未配置' });
}
await notionService.writeToDB(syslogDbId, {
'SYSLOG内容': { title: [{ text: { content: content } }] },
'类型': { select: { name: type } },
'开发者编号': { rich_text: [{ text: { content: devId } }] }
});
var prefix = req.sandbox ? '[沙箱] ' : '';
var summary = content.length > 50 ? content.substring(0, 50) + '...' : content;
res.json({
success: true,
reply: '✅ ' + prefix + 'SYSLOG 已提交:「' + summary + '」'
});
} catch (e) {
res.status(500).json({ error: true, code: 'SUBMIT_FAILED', message: e.message });
}
}
);
// ====== 创建广播 ======
router.post('/broadcasts/create',
authMiddleware.checkPermission('broadcast:create'),
async function(req, res) {
try {
var title = req.body.title;
var content = req.body.content;
var target = req.body.target || 'ALL';
var devId = req.user.devId;
if (!title || !content) {
return res.status(400).json({ error: true, code: 'MISSING_FIELDS', message: '广播标题和内容不能为空' });
}
var broadcastId = 'BC-' + Date.now();
var broadcastData = {
broadcast_id: broadcastId,
title: title,
content: content,
target: target,
created_by: devId,
created_at: new Date().toISOString(),
status: 'active'
};
// 写入 GitHub 广播文件
var filePath = '.github/broadcasts/' + broadcastId + '.json';
await githubService.createFile(
filePath,
JSON.stringify(broadcastData, null, 2),
'📢 新广播: ' + title + ' · by ' + devId
);
res.json({
success: true,
broadcast_id: broadcastId,
reply: '📢 广播「' + title + '」已创建并发送,编号:' + broadcastId
});
} catch (e) {
res.status(500).json({ error: true, code: 'CREATE_FAILED', message: e.message });
}
}
);
// ====== 触发预览站部署 ======
router.post('/deploy/preview',
authMiddleware.checkPermission('deploy:preview'),
async function(req, res) {
try {
var module = req.body.module;
var devId = req.user.devId;
if (!module) {
return res.status(400).json({ error: true, code: 'MISSING_MODULE', message: '需要指定部署模块' });
}
// 检查开发者是否拥有该模块
if (!req.user.modules.includes('*') && !req.user.modules.includes(module)) {
return res.status(403).json({
error: true,
code: 'MODULE_DENIED',
message: '你没有该模块的部署权限',
reply: '❌ 你没有 ' + module + ' 模块的部署权限。你的模块是:' + req.user.modules.join(', ')
});
}
await githubService.triggerWorkflow('preview-deploy.yml', {
module: module,
dev_id: devId,
target: 'preview'
});
res.json({
success: true,
reply: '🚀 已触发 ' + module + ' 模块的预览站部署。稍等几分钟,部署完成后我会通知你。'
});
} catch (e) {
res.status(500).json({ error: true, code: 'DEPLOY_FAILED', message: e.message });
}
}
);
// ====== 触发正式站部署(需二次确认)======
router.post('/deploy/production',
authMiddleware.checkPermission('deploy:production'),
async function(req, res) {
try {
var module = req.body.module;
var confirmToken = req.body.confirmToken;
var devId = req.user.devId;
if (!module) {
return res.status(400).json({ error: true, code: 'MISSING_MODULE', message: '需要指定部署模块' });
}
// 检查模块权限
if (!req.user.modules.includes('*') && !req.user.modules.includes(module)) {
return res.status(403).json({
error: true,
code: 'MODULE_DENIED',
reply: '❌ 你没有 ' + module + ' 模块的部署权限。'
});
}
// 二次确认
if (!confirmToken) {
return res.json({
success: false,
requireConfirmation: true,
reply: '⚠️ 你正在请求部署 ' + module + ' 到**正式站guanghulab.com**。\n这会影响所有用户。\n\n请说「确认部署 ' + module + '」来确认。'
});
}
await githubService.triggerWorkflow('deploy-to-server.yml', {
module: module,
dev_id: devId,
target: 'production'
});
res.json({
success: true,
reply: '🚀 已触发 ' + module + ' 模块的**正式站部署**。部署日志会同步到维护日志中。'
});
} catch (e) {
res.status(500).json({ error: true, code: 'DEPLOY_FAILED', message: e.message });
}
}
);
// ====== 更新 Agent 状态 ======
router.patch('/agents/:agentId',
authMiddleware.checkPermission('agent:update'),
async function(req, res) {
try {
var agentId = req.params.agentId;
var status = req.body.status;
if (!status) {
return res.status(400).json({ error: true, code: 'MISSING_STATUS', message: '状态不能为空' });
}
if (!notionService.notion) {
return res.status(503).json({ error: true, code: 'NOTION_UNAVAILABLE', message: 'Notion 未连接' });
}
await notionService.notion.pages.update({
page_id: agentId,
properties: {
'状态': { select: { name: status } }
}
});
res.json({
success: true,
reply: '✅ Agent ' + agentId + ' 状态已更新为:' + status
});
} catch (e) {
res.status(500).json({ error: true, code: 'UPDATE_FAILED', message: e.message });
}
}
);
// ====== 写入维护日志 ======
router.post('/maintenance/log',
authMiddleware.checkPermission('maintenance:log'),
async function(req, res) {
try {
var title = req.body.title;
var type = req.body.type || '常规维护';
var devId = req.user.devId;
if (!title) {
return res.status(400).json({ error: true, code: 'MISSING_TITLE', message: '维护日志标题不能为空' });
}
var maintenanceDbId = getDbId(req, 'maintenanceLog');
if (!maintenanceDbId) {
return res.status(503).json({ error: true, code: 'DB_NOT_CONFIGURED', message: '维护日志数据库未配置' });
}
await notionService.writeToDB(maintenanceDbId, {
'标题': { title: [{ text: { content: title } }] },
'类型': { select: { name: type } },
'操作者': { rich_text: [{ text: { content: devId } }] }
});
res.json({
success: true,
reply: '✅ 维护日志已记录:' + title
});
} catch (e) {
res.status(500).json({ error: true, code: 'LOG_FAILED', message: e.message });
}
}
);
// ====== 提交指令回执 ======
router.post('/receipts/submit',
authMiddleware.checkPermission('syslog:submit'),
async function(req, res) {
try {
var instructionId = req.body.instruction_id;
var status = req.body.status || '已完成';
var receipt = req.body.receipt || '';
if (!instructionId) {
return res.status(400).json({ error: true, code: 'MISSING_ID', message: '指令编号不能为空' });
}
if (!dbConfig.receiptTracker) {
return res.status(503).json({ error: true, code: 'DB_NOT_CONFIGURED', message: '回执数据库未配置' });
}
await notionService.writeToDB(dbConfig.receiptTracker, {
'指令编号': { rich_text: [{ text: { content: instructionId } }] },
'执行状态': { select: { name: status } },
'铸渊回执': { rich_text: [{ text: { content: receipt } }] },
'回执时间': { date: { start: new Date().toISOString() } }
});
res.json({
success: true,
reply: '✅ 指令 ' + instructionId + ' 回执已提交'
});
} catch (e) {
res.status(500).json({ error: true, code: 'SUBMIT_FAILED', message: e.message });
}
}
);
// ====== 更新开发者状态 ======
router.patch('/dev/:devId/status',
authMiddleware.checkPermission('dev:update_self'),
async function(req, res) {
try {
var devId = req.params.devId;
var status = req.body.status;
// 开发者只能更新自己的状态(管理者除外)
if (req.user.devId !== devId && !req.user.permissions.includes('dev:update_all')) {
return res.status(403).json({
error: true,
code: 'SELF_ONLY',
message: '只能更新自己的状态',
reply: '🔒 你只能更新自己的状态,不能修改其他开发者的信息。'
});
}
if (!status) {
return res.status(400).json({ error: true, code: 'MISSING_STATUS', message: '状态不能为空' });
}
// 更新 Notion 主控台中的状态
if (dbConfig.controlPanel) {
var result = await notionService.queryDB(dbConfig.controlPanel, {
property: 'DEV编号',
rich_text: { equals: devId }
}, null, 1);
if (result.results && result.results.length > 0) {
await notionService.notion.pages.update({
page_id: result.results[0].id,
properties: {
'状态': { rich_text: [{ text: { content: status } }] }
}
});
}
}
res.json({
success: true,
reply: '✅ ' + devId + ' 的状态已更新为:' + status
});
} catch (e) {
res.status(500).json({ error: true, code: 'UPDATE_FAILED', message: e.message });
}
}
);
module.exports = router;

View File

@ -2,6 +2,7 @@
* 🔗 光湖后端中间层 · 通道B 入口
*
* 实时调用 Notion API / GitHub API为前端提供数据代理
* 包含读取层 + 写入层 + 意图路由 + 权限沙箱 + 认知引导
* 只监听 127.0.0.1通过 Nginx 反向代理对外暴露
*
* 版权国作登字-2026-A-00037559
@ -26,27 +27,35 @@ app.use(cors({
app.use(express.json());
// 路由注册
// 读取类路由(公开,无需认证)
app.use('/api', require('./routes/health'));
app.use('/api', require('./routes/dev'));
app.use('/api', require('./routes/databases'));
app.use('/api', require('./routes/chat'));
app.use('/api', require('./routes/receipt'));
// 写入类路由(需认证 + 权限 + 审计)
app.use('/api', require('./routes/write'));
// 认知引导 + 工具列表路由
app.use('/api', require('./routes/onboarding'));
// 根路由
app.get('/', function(_req, res) {
res.json({
status: 'ok',
service: 'guanghu-api-server',
version: '1.0.0',
version: '2.0.0',
channel: 'B',
description: '光湖后端中间层 · 实时 Notion/GitHub 数据代理'
description: '光湖后端中间层 · 语言驱动操作系统',
capabilities: ['read', 'write', 'intent-routing', 'permission-sandbox', 'onboarding']
});
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, '127.0.0.1', function() {
console.log('🔗 光湖后端中间层启动 · 端口 ' + PORT);
console.log(' 通道B · 实时 Notion API / GitHub API 代理');
console.log(' 通道B · 语言驱动操作系统');
console.log(' 能力:读取 + 写入 + 意图路由 + 权限沙箱 + 认知引导');
console.log(' 监听地址127.0.0.1:' + PORT + '(仅本机访问)');
});

View File

@ -125,8 +125,97 @@ async function getWorkflowRuns(repoName, count) {
return result;
}
// ====== 触发 GitHub Actions Workflow ======
function triggerWorkflow(workflowFile, inputs) {
return new Promise(function(resolve, reject) {
var data = JSON.stringify({
ref: 'main',
inputs: inputs || {}
});
var headers = {
'Accept': 'application/vnd.github.v3+json',
'Content-Type': 'application/json',
'User-Agent': 'guanghu-api-server/1.0',
'Content-Length': Buffer.byteLength(data)
};
if (GITHUB_TOKEN) {
headers['Authorization'] = 'Bearer ' + GITHUB_TOKEN;
}
var options = {
hostname: 'api.github.com',
port: 443,
path: '/repos/' + ORG + '/guanghulab/actions/workflows/' + workflowFile + '/dispatches',
method: 'POST',
headers: headers
};
var req = https.request(options, function(res) {
if (res.statusCode === 204) {
resolve({ success: true });
} else {
var body = '';
res.on('data', function(chunk) { body += chunk; });
res.on('end', function() {
reject(new Error('GitHub API ' + res.statusCode + ': ' + body));
});
}
});
req.on('error', reject);
req.write(data);
req.end();
});
}
// ====== 创建 GitHub 文件 ======
function createFile(filePath, content, message) {
return new Promise(function(resolve, reject) {
var data = JSON.stringify({
message: message,
content: Buffer.from(content).toString('base64'),
committer: {
name: '铸渊 (ZhùYuān)',
email: 'zhuyuan@guanghulab.com'
}
});
var headers = {
'Accept': 'application/vnd.github.v3+json',
'Content-Type': 'application/json',
'User-Agent': 'guanghu-api-server/1.0',
'Content-Length': Buffer.byteLength(data)
};
if (GITHUB_TOKEN) {
headers['Authorization'] = 'Bearer ' + GITHUB_TOKEN;
}
var options = {
hostname: 'api.github.com',
port: 443,
path: '/repos/' + ORG + '/guanghulab/contents/' + filePath,
method: 'PUT',
headers: headers
};
var req = https.request(options, function(res) {
var body = '';
res.on('data', function(chunk) { body += chunk; });
res.on('end', function() {
try { resolve(JSON.parse(body)); }
catch (e) { resolve({ raw: body }); }
});
});
req.on('error', reject);
req.write(data);
req.end();
});
}
module.exports = {
getRepoStatus: getRepoStatus,
getRecentCommits: getRecentCommits,
getWorkflowRuns: getWorkflowRuns
getWorkflowRuns: getWorkflowRuns,
triggerWorkflow: triggerWorkflow,
createFile: createFile
};

159
docs/css/onboarding.css Normal file
View File

@ -0,0 +1,159 @@
/**
* 认知引导系统样式
* 版权国作登字-2026-A-00037559
*/
/* 引导覆盖层 */
.onboarding-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
backdrop-filter: blur(4px);
}
/* 引导卡片 */
.onboarding-card {
background: #1e1e2e;
border: 1px solid rgba(100, 200, 255, 0.2);
border-radius: 16px;
padding: 32px 40px;
max-width: 520px;
width: 90%;
text-align: center;
color: #e0e0e0;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
animation: onboarding-fade-in 0.3s ease;
}
@keyframes onboarding-fade-in {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.onboarding-card h2 {
color: #64c8ff;
margin: 12px 0 8px;
font-size: 1.4em;
}
.onboarding-emoji {
font-size: 3em;
margin-bottom: 4px;
}
.onboarding-card p {
margin: 8px 0;
line-height: 1.6;
color: #b0b0c0;
}
.onboarding-card strong {
color: #e0e0e0;
}
/* 示例列表 */
.onboarding-examples {
text-align: left;
background: rgba(100, 200, 255, 0.05);
border-radius: 8px;
padding: 12px 20px;
margin: 16px 0;
}
.onboarding-examples p {
font-size: 0.9em;
margin-bottom: 6px;
}
.onboarding-examples ul {
list-style: none;
padding: 0;
margin: 0;
}
.onboarding-examples li {
padding: 4px 0;
color: #a0c8e0;
font-size: 0.9em;
}
.onboarding-examples li::before {
content: '💬 ';
}
/* 预览环境提示 */
.onboarding-note {
font-size: 0.85em;
color: #ffc864 !important;
margin-top: 16px !important;
}
/* 开始按钮 */
.onboarding-btn {
display: inline-block;
margin-top: 20px;
padding: 12px 32px;
background: linear-gradient(135deg, #3a8fd4, #64c8ff);
color: #fff;
border: none;
border-radius: 8px;
font-size: 1em;
cursor: pointer;
transition: transform 0.15s, box-shadow 0.15s;
}
.onboarding-btn:hover {
transform: translateY(-2px);
box-shadow: 0 4px 16px rgba(100, 200, 255, 0.3);
}
/* 完成卡片 */
.onboarding-complete .onboarding-message {
text-align: left;
line-height: 1.8;
color: #b0b0c0;
margin: 16px 0;
white-space: pre-line;
}
/* 引导提示条(聊天界面内) */
.onboarding-hint {
background: rgba(100, 200, 255, 0.08);
border: 1px solid rgba(100, 200, 255, 0.15);
border-radius: 12px;
padding: 12px 16px;
margin: 8px 0 16px;
}
.onboarding-hint-inner {
display: flex;
flex-direction: column;
gap: 6px;
}
.onboarding-hint-round {
font-size: 0.75em;
color: #64c8ff;
font-weight: bold;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.onboarding-hint-prompt {
color: #e0e0e0;
font-size: 0.95em;
line-height: 1.5;
}
.onboarding-hint-tip {
color: #a0a0b0;
font-size: 0.85em;
line-height: 1.4;
}

121
docs/js/intent-tools.js Normal file
View File

@ -0,0 +1,121 @@
/**
* 前端 Function Calling 集成 · 意图工具层
*
* AI 模型提供可调用工具列表实现自然语言 系统操作映射
* 版权国作登字-2026-A-00037559
*/
/* global HOLOLAKE_ENV */
(function(window) {
'use strict';
var API_BASE = HOLOLAKE_ENV === 'production' ? 'https://guanghulab.com/api' : '';
/**
* 从后端获取当前用户可用的工具列表
*/
async function fetchAvailableTools(devId, token) {
if (!API_BASE) return [];
try {
var res = await fetch(API_BASE + '/tools', {
headers: {
'x-dev-id': devId,
'Authorization': 'Bearer ' + token
}
});
if (res.ok) {
var data = await res.json();
return data.tools || [];
}
} catch (e) {
console.debug('[IntentTools] fetch failed:', e.message);
}
return [];
}
/**
* 构建带 function tools system prompt 片段
*/
function buildToolsPrompt(tools, environment) {
if (!tools || !tools.length) return '';
var lines = [
'',
'## 你能做的事(语言驱动操作系统)',
'你可以通过 function calling 调用以下操作。开发者说话时,判断意图并调用对应操作。',
''
];
for (var i = 0; i < tools.length; i++) {
var t = tools[i];
var danger = t.dangerous ? ' ⚠️危险' : '';
lines.push('- **' + t.name + '**' + danger + '' + t.description);
}
lines.push('');
lines.push('## 语言驱动操作规则');
lines.push('1. 开发者说什么,你就做什么。这是语言驱动操作系统,说即执行。');
lines.push('2. 如果开发者的话不对应任何操作,正常聊天回复即可。');
lines.push('3. 危险操作(标记为 ⚠️)必须要求开发者二次确认。');
lines.push('4. 每次执行操作后,用自然语言告诉开发者结果。');
if (environment === 'preview') {
lines.push('5. 【预览环境】所有写入操作在沙箱中执行,不影响真实系统。告诉开发者:「这是预览环境,你的操作不会影响正式站。放心练习。」');
}
return lines.join('\n');
}
/**
* 执行 function call调用后端 API
*/
async function executeToolCall(toolName, params, devId, token) {
if (!API_BASE) return { success: false, reply: '后端服务不可用' };
// 工具名 → API 路径映射
var toolApiMap = {
'query_tickets': { method: 'GET', path: '/tickets' },
'create_ticket': { method: 'POST', path: '/tickets/create' },
'submit_syslog': { method: 'POST', path: '/syslog/submit' },
'query_my_status': { method: 'GET', path: '/dev/' + devId },
'deploy_to_preview': { method: 'POST', path: '/deploy/preview' },
'deploy_to_production': { method: 'POST', path: '/deploy/production' },
'query_agents': { method: 'GET', path: '/agents' },
'query_recent_syslogs': { method: 'GET', path: '/syslogs' },
'create_broadcast': { method: 'POST', path: '/broadcasts/create' },
'query_repo_status': { method: 'GET', path: '/repo/status' }
};
var apiDef = toolApiMap[toolName];
if (!apiDef) return { success: false, reply: '未知的操作: ' + toolName };
try {
var fetchOpts = {
method: apiDef.method,
headers: {
'Content-Type': 'application/json',
'x-dev-id': devId,
'Authorization': 'Bearer ' + token
}
};
if (apiDef.method === 'POST' || apiDef.method === 'PATCH') {
fetchOpts.body = JSON.stringify(params || {});
}
var res = await fetch(API_BASE + apiDef.path, fetchOpts);
return await res.json();
} catch (e) {
return { success: false, reply: '操作失败: ' + e.message };
}
}
// 导出到全局
window.IntentTools = {
fetchAvailableTools: fetchAvailableTools,
buildToolsPrompt: buildToolsPrompt,
executeToolCall: executeToolCall
};
})(window);

183
docs/js/onboarding.js Normal file
View File

@ -0,0 +1,183 @@
/**
* 前端认知引导系统 · 新开发者引导流程
*
* 新开发者首次登录后自动进入 5 轮引导对话
* 版权国作登字-2026-A-00037559
*/
/* global HOLOLAKE_ENV */
(function(window) {
'use strict';
var API_BASE = HOLOLAKE_ENV === 'production' ? 'https://guanghulab.com/api' : '';
var onboardingOverlay = null;
/**
* 检查是否需要显示引导
*/
async function checkOnboarding(devId, token) {
if (!API_BASE || !devId || !token) return false;
try {
var res = await fetch(API_BASE + '/onboarding/status', {
headers: { 'x-dev-id': devId, 'Authorization': 'Bearer ' + token }
});
if (res.ok) {
var data = await res.json();
return !data.started && !data.completed && data.permissionLevel === 0;
}
} catch (e) {
console.debug('[Onboarding] check failed:', e.message);
}
return false;
}
/**
* 显示引导欢迎画面
*/
function showWelcome(devName, onStart) {
// 移除已有覆盖层
hideOnboarding();
onboardingOverlay = document.createElement('div');
onboardingOverlay.className = 'onboarding-overlay';
onboardingOverlay.innerHTML =
'<div class="onboarding-card">' +
'<div class="onboarding-emoji">👋</div>' +
'<h2>欢迎来到光湖·数字地球</h2>' +
'<p>你好,<strong>' + (devName || '开发者') + '</strong></p>' +
'<p>我是铸渊,你的代码守护人格体。</p>' +
'<p>在这里,你不需要点按钮或填表单。<br><strong>直接对我说话就好。</strong></p>' +
'<div class="onboarding-examples">' +
'<p>比如你可以说:</p>' +
'<ul>' +
'<li>「我的工单还有几个没关?」</li>' +
'<li>「帮我建一个P2的Bug工单」</li>' +
'<li>「我想部署模块到预览站」</li>' +
'<li>「最近系统有什么日志?」</li>' +
'</ul>' +
'</div>' +
'<p class="onboarding-note">🏖️ 现在你在<strong>预览环境</strong>,所有操作都不会影响正式站。放心练习。</p>' +
'<button class="onboarding-btn" id="onboarding-start-btn">开始体验 →</button>' +
'</div>';
document.body.appendChild(onboardingOverlay);
document.getElementById('onboarding-start-btn').addEventListener('click', function() {
hideOnboarding();
if (onStart) onStart();
});
}
/**
* 显示引导轮次提示在聊天界面中展示
*/
function showRoundHint(round, prompt, hint) {
var container = document.getElementById('onboarding-hint');
if (!container) {
container = document.createElement('div');
container.id = 'onboarding-hint';
container.className = 'onboarding-hint';
var chatArea = document.querySelector('.chat-container') || document.body;
chatArea.insertBefore(container, chatArea.firstChild);
}
container.innerHTML =
'<div class="onboarding-hint-inner">' +
'<div class="onboarding-hint-round">引导 ' + round + '/5</div>' +
'<div class="onboarding-hint-prompt">' + prompt + '</div>' +
(hint ? '<div class="onboarding-hint-tip">' + hint + '</div>' : '') +
'</div>';
container.style.display = 'block';
}
/**
* 显示引导完成消息
*/
function showComplete(message) {
hideOnboarding();
onboardingOverlay = document.createElement('div');
onboardingOverlay.className = 'onboarding-overlay';
onboardingOverlay.innerHTML =
'<div class="onboarding-card onboarding-complete">' +
'<div class="onboarding-emoji">🎉</div>' +
'<h2>引导完成!</h2>' +
'<div class="onboarding-message">' + (message || '') + '</div>' +
'<button class="onboarding-btn" id="onboarding-close-btn">开始使用 →</button>' +
'</div>';
document.body.appendChild(onboardingOverlay);
document.getElementById('onboarding-close-btn').addEventListener('click', function() {
hideOnboarding();
});
}
/**
* 隐藏引导覆盖层
*/
function hideOnboarding() {
if (onboardingOverlay) {
onboardingOverlay.remove();
onboardingOverlay = null;
}
var hint = document.getElementById('onboarding-hint');
if (hint) hint.style.display = 'none';
}
/**
* 推进引导流程调用后端
*/
async function advanceOnboarding(devId, token) {
if (!API_BASE) return null;
try {
var res = await fetch(API_BASE + '/onboarding/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-dev-id': devId,
'Authorization': 'Bearer ' + token
}
});
if (res.ok) return await res.json();
} catch (e) {
console.debug('[Onboarding] advance failed:', e.message);
}
return null;
}
/**
* 完成引导调用后端触发权限升级
*/
async function completeOnboarding(devId, token) {
if (!API_BASE) return null;
try {
var res = await fetch(API_BASE + '/onboarding/complete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-dev-id': devId,
'Authorization': 'Bearer ' + token
}
});
if (res.ok) return await res.json();
} catch (e) {
console.debug('[Onboarding] complete failed:', e.message);
}
return null;
}
// 导出到全局
window.Onboarding = {
checkOnboarding: checkOnboarding,
showWelcome: showWelcome,
showRoundHint: showRoundHint,
showComplete: showComplete,
hideOnboarding: hideOnboarding,
advanceOnboarding: advanceOnboarding,
completeOnboarding: completeOnboarding
};
})(window);