UI/UX "Living Codex" 4/n: assistant features + emoji purge
- Replace all emoji icons (~26 instances) with Lucide icons across the app (DashboardPage, SessionSidebar, PlayerBoards, MyCharacterPanel, PlayerViewPage, EncounterTracker, CampaignsPage, HandoutControl, SessionControl, CharacterSheet, EncounterTipCard, ActionGuide) - Expose real 5e race data in wizard (ASI/vision/traits instead of stub desc); add vision field to loadRaces5e return type - Surface subclass descriptions after picking a subclass in the wizard - Add per-class tips, race synergy notes, and ability/skill guidance in wizard - New ActionGuide component: collapsible "What can I do on my turn?" in player view - New SessionPrepCard: AI + fallback session hook generator on assistant page - New NpcGenCard: AI + fallback quick NPC generator with "Add to campaign" action - Inline NPC detail generator (wand button) on each NPC card in NpcsPage - Quest hook generator button in QuestsPage header - Condition tooltips in player party view (useConditionGlossary) - Pass campaign.system through session snapshot so player view is system-aware Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,8 +2,15 @@ import { useState } from 'react';
|
||||
import type { Campaign, Quest, Objective } from '@/lib/schemas';
|
||||
import { questsRepo } from '@/lib/db/repositories';
|
||||
import { newId } from '@/lib/ids';
|
||||
import { complete } from '@/lib/llm/client';
|
||||
import { getLlmConfig, useAssistantStore } from '@/stores/assistantStore';
|
||||
import { buildQuestHookPrompt, questHookSchema, fallbackQuestHook, type QuestHook } from '@/lib/assistant/session';
|
||||
import { buildCampaignContext } from '@/lib/assistant/context';
|
||||
import { useDebouncedCallback } from '@/lib/useDebouncedCallback';
|
||||
import { useQuests } from './hooks';
|
||||
import { useCharacters } from '@/features/characters/hooks';
|
||||
import { useNotes } from '@/features/world/hooks';
|
||||
import { useEncounters } from '@/features/combat/hooks';
|
||||
import { Page, PageHeader, EmptyState, RequireCampaign } from '@/components/ui/Page';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Input, Select } from '@/components/ui/Input';
|
||||
@@ -20,15 +27,51 @@ export function QuestsPage() {
|
||||
|
||||
function Quests({ campaign }: { campaign: Campaign }) {
|
||||
const quests = useQuests(campaign.id);
|
||||
const characters = useCharacters(campaign.id);
|
||||
const notes = useNotes(campaign.id);
|
||||
const encounters = useEncounters(campaign.id);
|
||||
const order = { active: 0, 'on-hold': 1, completed: 2, failed: 3 };
|
||||
const sorted = [...quests].sort((a, b) => order[a.status] - order[b.status] || a.title.localeCompare(b.title));
|
||||
const [genBusy, setGenBusy] = useState(false);
|
||||
const llmEnabled = useAssistantStore((s) => s.enabled);
|
||||
const hasKey = useAssistantStore((s) => !!s.apiKey);
|
||||
const canUseLlm = llmEnabled && hasKey;
|
||||
|
||||
const generateQuest = async () => {
|
||||
setGenBusy(true);
|
||||
try {
|
||||
let hook: QuestHook | null = null;
|
||||
if (canUseLlm) {
|
||||
const ctx = buildCampaignContext({ campaign, characters, encounters, quests, notes });
|
||||
const { system, user } = buildQuestHookPrompt(ctx);
|
||||
const res = await complete<QuestHook>(getLlmConfig(), { system, user, schema: questHookSchema });
|
||||
if (res.ok && 'data' in res) hook = res.data;
|
||||
}
|
||||
if (!hook) hook = fallbackQuestHook();
|
||||
const created = await questsRepo.create(campaign.id, hook.title);
|
||||
await questsRepo.update(created.id, {
|
||||
description: hook.description,
|
||||
reward: hook.reward,
|
||||
objectives: hook.objectives.map((text) => ({ id: newId(), text, done: false })),
|
||||
});
|
||||
} finally {
|
||||
setGenBusy(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Page>
|
||||
<PageHeader
|
||||
title="Quests"
|
||||
subtitle={campaign.name}
|
||||
actions={<Button variant="primary" onClick={() => questsRepo.create(campaign.id, 'New quest')}>+ New quest</Button>}
|
||||
actions={
|
||||
<div className="flex gap-2">
|
||||
<Button variant="secondary" disabled={genBusy} onClick={() => void generateQuest()}>
|
||||
{genBusy ? 'Generating…' : 'Generate quest hook'}
|
||||
</Button>
|
||||
<Button variant="primary" onClick={() => questsRepo.create(campaign.id, 'New quest')}>+ New quest</Button>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
{quests.length === 0 ? (
|
||||
<EmptyState title="No quests yet" hint="Track objectives, rewards, and progress." action={<Button variant="primary" onClick={() => questsRepo.create(campaign.id, 'New quest')}>+ New quest</Button>} />
|
||||
|
||||
Reference in New Issue
Block a user