feat: AI沙箱流式对话+引导学习+静态服务优化

- 后端: AI沙箱流式对话(SSE), reasoning_content 回退支持
- 前端: 沙箱页流式渲染, 引导学习面板, 默认场景/Starter兜底
- 优化: 前端改用静态文件服务器(node server.js)替代next dev, CSS永不丢失
- 修复: 通用模型默认改为可用模型, predev不再删.next缓存
This commit is contained in:
yuzhiran-dev
2026-05-27 18:26:30 +08:00
parent 0b66d752ce
commit 417fb266d4
14 changed files with 947 additions and 203 deletions
+92
View File
@@ -0,0 +1,92 @@
const http = require('http');
const fs = require('fs');
const path = require('path');
const PORT = process.env.PORT || 3000;
const ROOT = path.join(__dirname, 'out');
const MIME = {
'.html': 'text/html; charset=utf-8',
'.css': 'text/css; charset=utf-8',
'.js': 'application/javascript; charset=utf-8',
'.json': 'application/json; charset=utf-8',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.gif': 'image/gif',
'.svg': 'image/svg+xml',
'.ico': 'image/x-icon',
'.webp': 'image/webp',
'.woff': 'font/woff',
'.woff2': 'font/woff2',
'.ttf': 'font/ttf',
'.txt': 'text/plain; charset=utf-8',
'.xml': 'application/xml; charset=utf-8',
'.webmanifest': 'application/manifest+json',
'.map': 'application/octet-stream',
};
function resolvePath(url) {
const decoded = decodeURIComponent(url).split('?')[0];
if (decoded === '/') return path.join(ROOT, 'index.html');
const ext = path.extname(decoded);
if (ext) return path.join(ROOT, decoded);
const asHtml = path.join(ROOT, decoded + '.html');
if (fs.existsSync(asHtml)) return asHtml;
const asIndex = path.join(ROOT, decoded, 'index.html');
if (fs.existsSync(asIndex)) return asIndex;
return path.join(ROOT, decoded + '.html');
}
function sendFile(res, filePath, statusCode) {
const ext = path.extname(filePath);
const ct = MIME[ext] || 'application/octet-stream';
const isHtml = ext === '.html';
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(500);
res.end('Internal Server Error');
return;
}
res.writeHead(statusCode, {
'Content-Type': ct,
'Cache-Control': isHtml ? 'no-cache' : 'public, max-age=31536000, immutable',
});
res.end(data);
});
}
function serve(req, res) {
const filePath = path.normalize(resolvePath(req.url));
if (!filePath.startsWith(ROOT)) {
res.writeHead(403);
res.end('Forbidden');
return;
}
fs.access(filePath, fs.constants.F_OK, (err) => {
if (err) {
const four04 = path.join(ROOT, '404.html');
fs.access(four04, fs.constants.F_OK, (err2) => {
if (err2) {
sendFile(res, path.join(ROOT, 'index.html'), 200);
} else {
sendFile(res, four04, 404);
}
});
return;
}
sendFile(res, filePath, 200);
});
}
const server = http.createServer(serve);
server.listen(PORT, () => {
console.log(`Static server running at http://localhost:${PORT} (serving ${ROOT})`);
});