Spaces:
Running
Running
| import type { TransitionEvent } from "story-engine"; | |
| export interface SummaryBuilderOptions { | |
| apiKey: string; | |
| model?: string; // defaults to gpt-4o-mini | |
| baseUrl?: string; // defaults to /openai/v1/chat/completions | |
| } | |
| export class SummaryBuilder { | |
| private apiKey: string; | |
| private model: string; | |
| private baseUrl: string; | |
| constructor(opts: SummaryBuilderOptions) { | |
| this.apiKey = opts.apiKey; | |
| this.model = opts.model ?? "gpt-4o-mini"; | |
| this.baseUrl = opts.baseUrl ?? "/openai/v1/chat/completions"; | |
| } | |
| async summarize(events: TransitionEvent[]): Promise<string> { | |
| if (events.length === 0) return "(The story is just beginning.)"; | |
| const log = events | |
| .map((e) => { | |
| switch (e.type) { | |
| case "storyEntered": return `Story "${e.storyId}" began.`; | |
| case "storyExited": return `User exited the story.`; | |
| case "sceneChanged": return `Scene changed: ${e.from ?? "?"} → ${e.to}.`; | |
| case "speakerChanged": return `Now speaking: ${e.to} (was ${e.from ?? "narrator"}).`; | |
| case "flagSet": return `Flag set: ${e.name} = ${e.value}.`; | |
| case "blocked": return `Blocked: ${e.reason}.`; | |
| } | |
| }) | |
| .join("\n"); | |
| const prompt = `Given these recent story events, write a 2-3 sentence summary suitable for handing off to a new character voice. Capture what just happened and what the user was doing. Plain spoken English, no markdown.\n\nEvents:\n${log}`; | |
| const res = await fetch(this.baseUrl, { | |
| method: "POST", | |
| headers: { | |
| "Content-Type": "application/json", | |
| Authorization: `Bearer ${this.apiKey}`, | |
| }, | |
| body: JSON.stringify({ | |
| model: this.model, | |
| messages: [{ role: "user", content: prompt }], | |
| max_tokens: 200, | |
| temperature: 0.4, | |
| }), | |
| }); | |
| if (!res.ok) { | |
| throw new Error(`Summary API ${res.status}: ${await res.text()}`); | |
| } | |
| const data = await res.json(); | |
| return data.choices[0]?.message?.content?.trim() ?? "(No summary available.)"; | |
| } | |
| } | |