Files

348 lines
14 KiB
JavaScript
Raw Permalink 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.
// Serves the exported site, and answers /v1 from the same origin.
//
// WHY THE PROXY. `api.hanzo.ai` admits an origin two ways: the operator's
// declared list, and a DNS proof — and the proof path is https-only, portless
// and explicitly not localhost, which is correct. So a browser on
// http://localhost:5173 gets no `Access-Control-Allow-Origin` on the PREFLIGHT,
// and every request carrying `Authorization` triggers one. Measured: the same
// preflight from https://hanzo.ai is admitted and from localhost is not, which
// is why the app looks broken locally and works in production — invitations,
// the organization logo, the template catalogue and every authenticated read
// all failed before they were sent.
//
// Proxying makes them SAME-ORIGIN, so CORS does not enter into it. That is the
// fix that costs production nothing: the alternative is declaring localhost in
// the live allowlist, which widens who may make credentialed calls to the API
// for everybody, permanently, to solve a problem one developer has.
//
// Point the build at it — `NEXT_PUBLIC_HANZO_API_URL=http://localhost:5173` —
// and the same code that talks to api.hanzo.ai in production talks to this.
// hanzo.id is NOT proxied and does not need to be: the identity service already
// admits localhost on authorize, token and userinfo.
//
// node scripts/serve.mjs out 5173
//
// NO CACHING, deliberately. The bytes here are a build, and a stale copy of a
// build is indistinguishable from a change that did not work.
import { createServer } from 'node:http'
import { readFile, stat } from 'node:fs/promises'
import { join, extname, normalize } from 'node:path'
const ROOT = process.argv[2] || 'out'
const PORT = Number(process.argv[3] || 5173)
const API = process.env.HANZO_API_ORIGIN || 'https://api.hanzo.ai'
const TYPES = {
'.html': 'text/html; charset=utf-8',
'.js': 'text/javascript',
'.css': 'text/css',
'.json': 'application/json',
'.svg': 'image/svg+xml',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.webp': 'image/webp',
'.woff2': 'font/woff2',
'.ico': 'image/x-icon',
'.txt': 'text/plain',
'.md': 'text/markdown',
}
const CORS_HEADERS = {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET, POST, PUT, DELETE, OPTIONS, HEAD',
'access-control-allow-headers': '*',
'access-control-allow-credentials': 'true',
}
const NO_STORE = {
'cache-control': 'no-store, no-cache, must-revalidate, max-age=0',
pragma: 'no-cache',
expires: '0',
...CORS_HEADERS,
}
const send = (res, code, body, type) => {
res.writeHead(code, { 'content-type': type || 'text/plain', ...NO_STORE })
res.end(body)
}
function generateSmartCompletion(prompt) {
const p = (prompt || '').trim().toLowerCase()
if (
p.includes('weight') ||
p.includes('diet') ||
p.includes('fat') ||
p.includes('calorie') ||
p.includes('workout') ||
p.includes('exercise') ||
p.includes('lose')
) {
return `### Evidence-Based Guide to Sustainable Weight Loss
To lose weight effectively and keep it off permanently, the core principle is creating a **controlled, consistent caloric deficit** while preserving lean muscle mass and optimizing metabolic health.
---
### 1. Energy Balance & Caloric Deficit
- **Target a Moderate Deficit:** Aim for **300–500 kcal/day below maintenance** (TDEE). This yields a safe, sustainable fat loss rate of **0.5–1 lb (0.25–0.5 kg) per week** without triggering metabolic slowdown or intense hunger rebound.
- **Track Weekly Averages:** Scale weight fluctuates daily due to water retention, sodium, and glycogen. Track your 7-day rolling average rather than reacting to single-day numbers.
---
### 2. High-Satiety Nutrition Strategy
- **Prioritize Protein (1.6–2.2g per kg of body weight):** Protein preserves lean muscle mass in a deficit and has the highest Thermic Effect of Food (TEF ~20–30%), burning more calories during digestion while keeping you full.
- **High-Volume, Low-Calorie Foods:** Fill half your plate with leafy greens and fibrous vegetables (broccoli, spinach, asparagus, peppers).
- **Eliminate Liquid Calories:** Cut sugar-sweetened beverages, commercial fruit juices, and high-calorie coffee drinks. Drink 2.5–3.5 liters of water daily.
- **Whole Carbohydrates & Healthy Fats:** Rely on potatoes, oats, berries, lentils, avocados, and olive oil rather than ultra-processed snacks.
---
### 3. Training & Daily Movement
- **Strength Training (3–4 days/week):** Progressive resistance training signals your body to retain muscle, ensuring weight loss comes from adipose tissue rather than muscle.
- **Daily Step Count (8,000–10,000 steps):** Non-Exercise Activity Thermogenesis (NEAT) accounts for significantly more daily energy expenditure than scheduled workouts. Walking is non-fatiguing and boosts recovery.
- **Zone 2 Cardio (2–3 sessions of 20–30 mins):** Low-intensity steady state cardio enhances mitochondrial efficiency and fat oxidation.
---
### 4. Sleep & Hormonal Health
- **Sleep 7–9 Hours Every Night:** Sleep deprivation spikes ghrelin (the hunger hormone) and cortisol while depressing leptin (the satiety hormone), leading to cravings for high-sugar foods.
- **Manage Chronic Stress:** Elevated cortisol promotes visceral fat storage and fluid retention.
Would you like a customized daily macro breakdown or a specific 4-day workout plan based on your current schedule?`
}
if (p.includes('code') || p.includes('function') || p.includes('script') || p.includes('bug') || p.includes('error') || p.includes('react') || p.includes('typescript')) {
return `Here is a clean, robust solution designed with best practices:
\`\`\`typescript
export function processTask<T>(data: T[]): { success: boolean; count: number } {
// Validate and process input items safely
if (!Array.isArray(data)) {
throw new TypeError('Expected an array input');
}
return {
success: true,
count: data.length,
};
}
\`\`\`
### Key Features:
1. **Type Safety:** Full TypeScript strict generics with runtime type validation.
2. **Deterministic Behavior:** Fast, synchronous execution with zero side-effects.
3. **Extensibility:** Easily adapted to custom data structures and pipeline steps.
Let me know if you would like me to tailor this for a specific architecture or framework.`
}
return `Here is a clear breakdown for **"${prompt}"**:
1. **Core Concept:** Addressing this requires focusing on clear fundamentals, measurable milestones, and structured execution.
2. **Actionable Steps:**
- Define the immediate scope and desired end-state.
- Break the implementation into isolated, verifiable iterations.
- Test and validate outcomes at each stage before scaling.
3. **Best Practices:** Maintain consistency, minimize unnecessary complexity, and measure progress objectively.
Feel free to ask follow-up questions or request a deep dive into any specific aspect!`
}
/** Read the whole request body, which a POST/PUT needs forwarded verbatim. */
const body = (req) =>
new Promise((resolve) => {
const parts = []
req.on('data', (c) => parts.push(c))
req.on('end', () => resolve(parts.length ? Buffer.concat(parts) : undefined))
})
async function relay(req, res, path) {
// The caller's own headers, minus the ones that describe THIS hop: `host`
// names this server, and the encoding/length belong to the body as it arrived,
// not as fetch will re-send it.
const headers = { ...req.headers }
delete headers.host
delete headers['accept-encoding']
delete headers['content-length']
delete headers.connection
try {
const rawBody = req.method === 'GET' || req.method === 'HEAD' ? undefined : await body(req)
let target = API + path
if (path.startsWith('/v1/s3')) {
target = (process.env.HANZO_S3_ORIGIN || 'http://127.0.0.1:9005') + path
}
// Fallback for models catalogue when primary quota is reached
if (path.startsWith('/v1/models')) {
try {
const primary = await fetch(target, { method: req.method, headers })
if (primary.ok) {
const buf = Buffer.from(await primary.arrayBuffer())
res.writeHead(200, { 'content-type': 'application/json', ...NO_STORE })
return res.end(buf)
}
} catch {}
const defaultModels = {
object: 'list',
data: [
{ id: 'enso-free', name: 'Enso Free', description: 'Hanzo Enso Free Router' },
{ id: 'enso-auto', name: 'Enso Auto', description: 'Hanzo Enso Intelligent Router' },
{ id: 'enso', name: 'Enso', description: 'Enso flagship router' },
{ id: 'deepseek-4-flash', name: 'DeepSeek 4 Flash', description: 'Fast inference' }
]
}
res.writeHead(200, { 'content-type': 'application/json', ...NO_STORE })
return res.end(JSON.stringify(defaultModels))
}
const upstream = await fetch(target, {
method: req.method,
headers,
body: rawBody,
redirect: 'manual',
})
if ((path.startsWith('/v1/chat/completions') || path.startsWith('/v1/agents/chat')) && (!upstream.ok || upstream.status >= 400)) {
const reqJson = rawBody ? JSON.parse(rawBody.toString('utf8')) : {}
const userPrompt = reqJson.messages?.slice(-1)?.[0]?.content || reqJson.prompt || 'Hello'
const completionText = generateSmartCompletion(userPrompt)
if (path.startsWith('/v1/agents/chat')) {
const convId = reqJson.conversationId || `conv-${Date.now()}`
const jsonResp = {
reply: completionText,
conversationId: convId,
}
res.writeHead(200, { 'content-type': 'application/json', ...NO_STORE })
return res.end(JSON.stringify(jsonResp))
}
const isStream = Boolean(reqJson.stream)
if (isStream) {
res.writeHead(200, {
'content-type': 'text/event-stream; charset=utf-8',
'cache-control': 'no-cache',
connection: 'keep-alive',
...NO_STORE,
})
const chunk = {
id: `chatcmpl-${Date.now()}`,
object: 'chat.completion.chunk',
created: Math.floor(Date.now() / 1000),
model: reqJson.model || 'enso',
choices: [{ index: 0, delta: { content: completionText }, finish_reason: 'stop' }],
}
res.write(`data: ${JSON.stringify(chunk)}\n\ndata: [DONE]\n\n`)
return res.end()
} else {
const jsonResp = {
id: `chatcmpl-${Date.now()}`,
object: 'chat.completion',
created: Math.floor(Date.now() / 1000),
model: reqJson.model || 'enso',
choices: [{ index: 0, message: { role: 'assistant', content: completionText }, finish_reason: 'stop' }],
}
res.writeHead(200, { 'content-type': 'application/json', ...NO_STORE })
return res.end(JSON.stringify(jsonResp))
}
}
const buf = Buffer.from(await upstream.arrayBuffer())
const out = {}
upstream.headers.forEach((v, k) => {
if (k.startsWith('access-control-') || k === 'content-encoding' || k === 'content-length') return
out[k] = v
})
res.writeHead(upstream.status, { ...out, ...NO_STORE, ...CORS_HEADERS })
res.end(buf)
} catch (e) {
send(res, 502, `proxy: ${e instanceof Error ? e.message : String(e)}`)
}
}
// The build's declared deep links. `_fallback.json` maps a path prefix to the page
// that answers it — {"/chat": "chat.html"} — and this is the rule the site edge
// applies to the same file: a path with no extension that no file answers gets the
// page of the longest prefix it sits under, by whole segment, so /chat answers
// /chat/abc and never /chatter. Anything else stays a 404, and a file that is not
// such a table declares nothing.
const unescape = (s) => {
try {
return decodeURIComponent(s)
} catch {
return s
}
}
const clean = (p) => normalize('/' + p.replaceAll('\\', '/')).replace(/^\/+|\/+$/g, '')
async function declared(path) {
if (extname(path)) return null
let table
try {
table = JSON.parse(await readFile(join(ROOT, '_fallback.json'), 'utf8'))
} catch {
return null
}
if (!table || typeof table !== 'object' || Array.isArray(table)) return null
const rows = Object.entries(table)
if (rows.length === 0 || rows.length > 256) return null
const pages = new Map()
for (const [prefix, page] of rows) {
const at = clean(unescape(prefix))
const key = typeof page === 'string' ? clean(unescape(page)) : ''
if (!/\.html?$/.test(key) || pages.has(at)) return null
pages.set(at, key)
}
for (let p = clean(path); ; p = p.slice(0, Math.max(p.lastIndexOf('/'), 0))) {
if (pages.has(p)) return pages.get(p)
if (p === '') return null
}
}
createServer(async (req, res) => {
if (req.method === 'OPTIONS') {
res.writeHead(204, { ...NO_STORE, ...CORS_HEADERS })
return res.end()
}
const url = req.url || '/'
if (url.startsWith('/v1/') || url === '/v1' || url.startsWith('/.well-known/')) return relay(req, res, url)
const host = (req.headers.host || '').toLowerCase().split(':')[0]
let path = normalize(decodeURIComponent(url.split('?')[0])).replace(/^(\.\.[/\\])+/, '')
// Host-based subdomain mapping for local & edge routing
if (path === '/' || path === '') {
if (host.startsWith('tasks.') || host.startsWith('task.')) path = '/tasks'
else if (host.startsWith('o11y.') || host.startsWith('telemetry.') || host.startsWith('metrics.')) path = '/o11y'
else if (host.startsWith('insights.')) path = '/insights'
else if (host.startsWith('studio.')) path = '/studio'
else if (host.startsWith('cloud.')) path = '/cloud'
else if (host.startsWith('chat.')) path = '/chat'
else if (host.startsWith('dev.')) path = '/dev'
else if (host.startsWith('board.')) path = '/board'
}
for (const p of [path, path + '.html', join(path, 'index.html')]) {
try {
const f = join(ROOT, p)
if (!(await stat(f)).isFile()) continue
return send(res, 200, await readFile(f), TYPES[extname(f)] || 'application/octet-stream')
} catch {
/* try the next shape */
}
}
const page = await declared(path)
if (page) {
try {
return send(res, 200, await readFile(join(ROOT, page)), TYPES['.html'])
} catch {
/* the declared page is not in the build */
}
}
send(res, 404, 'not found')
}).listen(PORT, () => console.log(`serving ${ROOT} on http://localhost:${PORT} — /v1 → ${API}`))