content: personal details for the AI terminal only
Favorites, teams, hobbies, height and weight, and why Rust. Bundled into the worker's system prompt; not rendered on the page or in /llms.txt. The worker now builds the profile from src/data/profile.ts directly.
2 files changed, +39 -9
+30-0src/data/profile.ts
| @@ -453,3 +453,33 @@ export function profileText(): string { | ||
| 453 | 453 | } |
| 454 | 454 | return lines.join('\n') + '\n'; |
| 455 | 455 | } |
| 456 | + | |
| 457 | +// Personal details for the AI terminal only. They are not rendered on the page | |
| 458 | +// and not included in /llms.txt (which crawlers index); the worker bundles | |
| 459 | +// this text into the terminal's system prompt. | |
| 460 | +export const personal = { | |
| 461 | + favoriteColor: 'Orange', | |
| 462 | + teams: { | |
| 463 | + college: 'Tennessee Volunteers (every sport)', | |
| 464 | + nfl: 'San Francisco 49ers', | |
| 465 | + nba: 'Golden State Warriors', | |
| 466 | + }, | |
| 467 | + hobbies: ['surfing', 'snowboarding', 'video games', 'working out', 'coding'], | |
| 468 | + height: '6\'4"', | |
| 469 | + weight: '230 lb', | |
| 470 | + favoriteLanguage: | |
| 471 | + 'Rust, because it is both safe and efficient, where C is efficient but risky. Lane is not a fan of garbage collectors.', | |
| 472 | +}; | |
| 473 | + | |
| 474 | +export function personalText(): string { | |
| 475 | + return [ | |
| 476 | + '## Personal (for casual questions; not shown on the page)', | |
| 477 | + `- Favorite color: ${personal.favoriteColor}`, | |
| 478 | + `- College team: ${personal.teams.college}`, | |
| 479 | + `- NFL team: ${personal.teams.nfl}`, | |
| 480 | + `- NBA team: ${personal.teams.nba}`, | |
| 481 | + `- Hobbies: ${personal.hobbies.join(', ')}. Most of his free time ends up going into building software.`, | |
| 482 | + `- Height and weight: ${personal.height}, ${personal.weight}`, | |
| 483 | + `- Favorite programming language: ${personal.favoriteLanguage}`, | |
| 484 | + ].join('\n') + '\n'; | |
| 485 | +} |
+9-9worker/index.ts
| @@ -11,6 +11,8 @@ | ||
| 11 | 11 | // Logs are JSON lines, readable in the dashboard (Workers Logs) or with |
| 12 | 12 | // `npx wrangler tail`. |
| 13 | 13 | |
| 14 | +import { personalText, profileText } from '../src/data/profile'; | |
| 15 | + | |
| 14 | 16 | interface Env { |
| 15 | 17 | ASSETS: Fetcher; |
| 16 | 18 | TURNSTILE_SITE_KEY: string; |
| @@ -38,12 +40,15 @@ Rules: | ||
| 38 | 40 | - If the profile does not cover something, say so plainly and suggest emailing lane@henslee.me. Never invent employers, dates, numbers, clients, or personal details. |
| 39 | 41 | - Never share a phone number or home address. |
| 40 | 42 | - For hiring, contracting, or contact questions, point to lane@henslee.me. |
| 41 | -- Stay on topic: Lane, Lane's work and skills, and how to reach Lane. Politely decline unrelated tasks. | |
| 43 | +- The Personal section is for casual questions (teams, hobbies, favorites). Share it when asked, but keep it out of answers about work unless it is relevant. | |
| 44 | +- Stay on topic: Lane, his work, skills, and interests, and how to reach him. Politely decline unrelated tasks. | |
| 42 | 45 | - The terminal also has slash commands (typing / lists them, /email opens mail, /copy copies the address). Mention them only when useful. |
| 43 | 46 | |
| 44 | 47 | PROFILE |
| 45 | 48 | `; |
| 46 | -let profileCache: string | null = null; | |
| 49 | +// The profile is bundled from the same data the page is built from, so the | |
| 50 | +// terminal always matches the deployed site. Personal details are only here. | |
| 51 | +const PROFILE = `${profileText()}\n${personalText()}`; | |
| 47 | 52 | const RYBBIT = 'https://a.hygo.ai/api'; |
| 48 | 53 | const STATS_TZ = 'America/Los_Angeles'; |
| 49 | 54 | const STATS_TTL_SECONDS = 60; |
| @@ -218,7 +223,7 @@ async function rateLimited(ip: string, bucket: string, limit: number, windowSeco | ||
| 218 | 223 | } |
| 219 | 224 | |
| 220 | 225 | // AI console: answers questions about Lane with DeepSeek, grounded in the |
| 221 | -// profile built from the site's data (/llms.txt). The key never leaves the | |
| 226 | +// profile built from the site's data (the /llms.txt text plus personal details). The key never leaves the | |
| 222 | 227 | // worker; only gate-passed visitors can call it; replies stream as plain text. |
| 223 | 228 | async function serveAi(req: Request, env: Env): Promise<Response> { |
| 224 | 229 | if (!(await hasValidPass(req, env))) return json({ ok: false, error: 'gate' }, 403); |
| @@ -246,11 +251,6 @@ async function serveAi(req: Request, env: Env): Promise<Response> { | ||
| 246 | 251 | return json({ ok: false, error: 'bad-request' }, 400); |
| 247 | 252 | } |
| 248 | 253 | |
| 249 | - if (!profileCache) { | |
| 250 | - const res = await env.ASSETS.fetch(new Request(new URL('/llms.txt', req.url))); | |
| 251 | - profileCache = res.ok ? await res.text() : ''; | |
| 252 | - } | |
| 253 | - | |
| 254 | 254 | const started = Date.now(); |
| 255 | 255 | const upstream = await fetch(DEEPSEEK, { |
| 256 | 256 | method: 'POST', |
| @@ -260,7 +260,7 @@ async function serveAi(req: Request, env: Env): Promise<Response> { | ||
| 260 | 260 | stream: true, |
| 261 | 261 | temperature: 0.4, |
| 262 | 262 | max_tokens: AI_LIMITS.maxTokens, |
| 263 | - messages: [{ role: 'system', content: AI_RULES + profileCache }, ...history], | |
| 263 | + messages: [{ role: 'system', content: AI_RULES + PROFILE }, ...history], | |
| 264 | 264 | }), |
| 265 | 265 | }); |
| 266 | 266 | if (!upstream.ok || !upstream.body) { |