import type { ChatContentPart, ChatMessage } from "./chats-types"; function text(value: string): ChatContentPart { return { type: "text", text: value }; } function toolResult(toolCallId: string, value: unknown): ChatContentPart { return { type: "tool_result", tool_call_id: toolCallId, content: [text(JSON.stringify(value))], }; } /** * Scripted assistant replies cycled through per chat. Generic content, * intentionally not Fabro-specific. Each entry is a single assistant * ChatMessage; tool calls and their results are siblings in the content * array so the renderer can pair them. */ export const SCRIPTED_REPLIES: ChatMessage[] = [ { role: "assistant", content: [ text( "Hi! I'm a scripted prototype reply. A few things I can show off:\n\n" + "- Markdown rendering (lists, **bold**, *italics*, `code`)\n" + "- Streaming text appearing incrementally\n" + "- Tool calls with arguments and results\n" + "- Multi-paragraph responses with code blocks\n\n" + "Send another message to see the next response in the bank.", ), ], }, { role: "assistant", content: [ text( "Here's a TypeScript snippet that debounces a function:\n\n" + "```ts\n" + "export function debounce void>(\n" + " fn: T,\n" + " ms: number,\n" + "): (...args: Parameters) => void {\n" + " let handle: ReturnType | undefined;\n" + " return (...args) => {\n" + " if (handle) clearTimeout(handle);\n" + " handle = setTimeout(() => fn(...args), ms);\n" + " };\n" + "}\n" + "```\n\n" + "The trailing-edge variant is the most common; a leading-edge variant fires immediately then suppresses subsequent calls.", ), ], }, { role: "assistant", content: [ text("Let me search for that real quick."), { type: "tool_call", id: "call_search_1", name: "search_web", input: { type: "function", arguments: { query: "current best practices for rate limiting an HTTP API", max_results: 5, }, }, }, toolResult("call_search_1", { results: [ { title: "Token bucket vs leaky bucket", url: "https://example.com/rate-limit-algorithms", snippet: "Token bucket allows bursts, leaky bucket smooths traffic.", }, { title: "Distributed rate limiting with Redis", url: "https://example.com/redis-rate-limit", snippet: "INCR + EXPIRE is the simplest fixed-window approach.", }, ], }), text( "\n\nTwo solid starting points. For most APIs, a Redis-backed sliding window keyed by API key gives you per-tenant fairness without a lot of moving parts. For burst tolerance, a token-bucket per route is a nice layer on top.", ), ], }, { role: "assistant", content: [ text( "## The 4-fold path of refactoring a hook\n\n" + "When a React hook starts feeling tangled, work the corners in order:\n\n" + "### 1. Extract pure computation\n" + "Anything that is a function of inputs (no side effects, no state) leaves the hook entirely.\n\n" + "### 2. Collapse derived state into `useMemo`\n" + "State that is computable from other state shouldn't be its own state.\n\n" + "### 3. Split orthogonal concerns into sibling hooks\n" + "If two effects don't share dependencies, they don't belong in the same hook.\n\n" + "### 4. Promote to a reducer\n" + "Once there are 3+ related `useState` calls coordinating updates, `useReducer` makes the state machine explicit.\n\n" + "> The honest test: can you write a one-sentence description of what the hook is responsible for? If not, it's doing too much.", ), ], }, { role: "assistant", content: [ text("I'll compute that for you."), { type: "tool_call", id: "call_calc_1", name: "run_calculation", input: { type: "function", arguments: { expression: "compound_interest(principal=10000, rate=0.05, years=10)", }, }, }, toolResult("call_calc_1", { value: 16288.95, currency: "USD", note: "Annual compounding; rounded to cents.", }), text( "\n\n**$16,288.95** after 10 years. Bumping the rate to 7% would put you at roughly $19,672, and continuous compounding at 5% lands at $16,487 — so the extra two points of rate matters more than the compounding cadence.", ), ], }, { role: "assistant", content: [ text( "Good question. The short answer: it depends on whether you need transactions across multiple writes.\n\n" + "If you do — Postgres. If everything you do is single-row, SQLite is faster, simpler to operate, and easier to back up. A surprising amount of production traffic can live happily on SQLite if you accept its one-writer-at-a-time constraint.\n\n" + "Next step: tell me about your read/write ratio and I can be more specific.", ), ], }, ]; const FALLBACK_REPLY: ChatMessage = { role: "assistant", content: [text("(No reply available.)")], }; export function pickReply(scriptIndex: number): ChatMessage { return ( SCRIPTED_REPLIES[scriptIndex % SCRIPTED_REPLIES.length] ?? FALLBACK_REPLY ); }