Auto-generate an adventure outline when no adventure text is provided
When a player creates a game without pasting their own adventure, the DM previously improvised turn-by-turn with no overall arc, ending, or calibrated difficulty. Now the setup-chat interview generates a full outline (hook, three-to-four-part structure, milestone leveling, explicit ending condition, GM guidance) as a follow-up LLM call once genre/length/experience are known, and feeds it through the existing adventure_text -> RAG ingestion -> "follow strictly" pipeline exactly like a pasted adventure. Falls back silently to today's improvised behavior if generation fails. Verified end-to-end in the browser: setup interview -> outline generated and previewed -> game created -> RAG chunks ingested -> has_adventure true -> cascade-cleaned on delete. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -39,6 +39,7 @@ export interface GameSetupChatResponse {
|
||||
messages: Record<string, unknown>[];
|
||||
assistant_text: string | null;
|
||||
proposal: GameSetupProposal | null;
|
||||
generated_adventure: string | null;
|
||||
}
|
||||
|
||||
export const listGames = () => apiFetch<Game[]>("/api/games");
|
||||
|
||||
@@ -15,6 +15,7 @@ export default function GameSetupChat({ onCreated }: { onCreated: (game: Game) =
|
||||
const [rawMessages, setRawMessages] = useState<Record<string, unknown>[]>([]);
|
||||
const [turns, setTurns] = useState<DisplayTurn[]>([]);
|
||||
const [proposal, setProposal] = useState<GameSetupProposal | null>(null);
|
||||
const [generatedAdventure, setGeneratedAdventure] = useState<string | null>(null);
|
||||
const [name, setName] = useState("");
|
||||
const [description, setDescription] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -26,6 +27,7 @@ export default function GameSetupChat({ onCreated }: { onCreated: (game: Game) =
|
||||
messages: Record<string, unknown>[];
|
||||
assistant_text: string | null;
|
||||
proposal: GameSetupProposal | null;
|
||||
generated_adventure?: string | null;
|
||||
}) {
|
||||
setRawMessages(res.messages);
|
||||
if (res.assistant_text) {
|
||||
@@ -42,6 +44,9 @@ export default function GameSetupChat({ onCreated }: { onCreated: (game: Game) =
|
||||
]);
|
||||
}
|
||||
}
|
||||
if (res.generated_adventure) {
|
||||
setGeneratedAdventure(res.generated_adventure);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
@@ -79,7 +84,7 @@ export default function GameSetupChat({ onCreated }: { onCreated: (game: Game) =
|
||||
setCreating(true);
|
||||
setError(null);
|
||||
try {
|
||||
const game = await createGame(name, description, adventureText);
|
||||
const game = await createGame(name, description, adventureText || generatedAdventure || "");
|
||||
onCreated(game);
|
||||
} catch {
|
||||
setError("Spiel konnte nicht erstellt werden.");
|
||||
@@ -129,7 +134,13 @@ export default function GameSetupChat({ onCreated }: { onCreated: (game: Game) =
|
||||
<MarkdownContent content={turn.content} />
|
||||
</div>
|
||||
))}
|
||||
{loading && <p className="text-sm text-slate-500">Der Dungeon Master überlegt…</p>}
|
||||
{loading && (
|
||||
<p className="text-sm text-slate-500">
|
||||
{adventureText.trim()
|
||||
? "Der Dungeon Master überlegt…"
|
||||
: "Der Dungeon Master überlegt … und entwirft bei Bedarf ein Abenteuer (kann einen Moment dauern)"}
|
||||
</p>
|
||||
)}
|
||||
<div ref={bottomRef} />
|
||||
</div>
|
||||
<SendMessageForm onSend={handleSend} disabled={loading} placeholder="Deine Antwort..." />
|
||||
@@ -150,6 +161,14 @@ export default function GameSetupChat({ onCreated }: { onCreated: (game: Game) =
|
||||
className="rounded bg-slate-800 px-3 py-2 outline-none focus:ring-2 focus:ring-amber-400"
|
||||
rows={3}
|
||||
/>
|
||||
{!adventureText.trim() && generatedAdventure && (
|
||||
<details className="rounded border border-slate-800 bg-slate-950 p-3 text-sm text-slate-300">
|
||||
<summary className="cursor-pointer font-semibold text-amber-400">
|
||||
Abenteuer-Entwurf ansehen (wird automatisch verwendet)
|
||||
</summary>
|
||||
<div className="mt-2 max-h-64 overflow-y-auto whitespace-pre-wrap">{generatedAdventure}</div>
|
||||
</details>
|
||||
)}
|
||||
{error && <p className="text-sm text-red-400">{error}</p>}
|
||||
<button
|
||||
type="submit"
|
||||
@@ -157,7 +176,7 @@ export default function GameSetupChat({ onCreated }: { onCreated: (game: Game) =
|
||||
className="self-start rounded bg-amber-500 px-4 py-2 text-sm font-semibold text-slate-950 hover:bg-amber-400 disabled:opacity-50"
|
||||
>
|
||||
{creating
|
||||
? adventureText.trim()
|
||||
? adventureText.trim() || generatedAdventure
|
||||
? "Erstelle… (Abenteuertext wird verarbeitet, kann etwas dauern)"
|
||||
: "Erstelle…"
|
||||
: "Erstellen"}
|
||||
|
||||
Reference in New Issue
Block a user