diff --git a/src/ai/chat.ts b/src/ai/chat.ts index 18f3741..a7268d9 100644 --- a/src/ai/chat.ts +++ b/src/ai/chat.ts @@ -1,5 +1,6 @@ import { type ChatMessage, create as saveChatMessage, type Usage } from "@src/db/chat_messages" import { getChatModel } from "@src/lib/ai" +import { ulid } from "@src/lib/ids" import { logger } from "@src/lib/logger" import type { ComputedCharacter } from "@src/services/computeCharacter" import type { ComputedChat } from "@src/services/computeChat" @@ -7,7 +8,6 @@ import { executeTool } from "@src/services/toolExecution" import { TOOL_DEFINITIONS, TOOLS } from "@src/tools" import { type LanguageModel, streamText } from "ai" import type { SQL } from "bun" -import { ulid } from "ulid" import { buildSystemPrompt } from "./prompts" export interface ChatResponse { diff --git a/src/ai/prompts.ts b/src/ai/prompts.ts index 731879d..ff63e2c 100644 --- a/src/ai/prompts.ts +++ b/src/ai/prompts.ts @@ -20,7 +20,7 @@ Background: ${character.background || "none"} # Your approach: -You can answer questions, provide advice, and help with rules clarifications, but your main job is to update the character sheet based on what the player tells you. You let the players focus on the game while you handle the bookkeeping. If they ask for advice, you give it, but always steer them back to the task of keeping their sheet accurate. +Your main job is to update the character sheet based on what the player tells you. The players focus on the game while you handle the bookkeeping. You are also an expert in the rules of DnD, and you can answer questions, provide advice, and help with rules clarifications. Players can ask you for advice on character optimization, spell selection, and strategy -- keep your advice concise and curt. You're here to help them, not to play the game for them. If players ask you questions unrelated to DnD or character sheets, curtly redirect them back to your purpose. You don't want them wasting your time -- you still have a lot of character sheets to manage today! diff --git a/src/components/AbilitiesEditForm.tsx b/src/components/AbilitiesEditForm.tsx index d8a597a..86201f1 100644 --- a/src/components/AbilitiesEditForm.tsx +++ b/src/components/AbilitiesEditForm.tsx @@ -2,6 +2,7 @@ import { Abilities, type AbilityType } from "@src/lib/dnd" import type { ComputedCharacter } from "@src/services/computeCharacter" import clsx from "clsx" import { ModalContent } from "./ui/ModalContent" +import { ModalForm, ModalFormSubmit } from "./ui/ModalForm" export interface AbilitiesEditFormProps { character: ComputedCharacter @@ -129,16 +130,7 @@ const AbilityEditBox = ({ ability, character, values, errors }: AbilityEditBoxPr export const AbilitiesEditForm = ({ character, values, errors = {} }: AbilitiesEditFormProps) => { return ( -
+ - +
) } diff --git a/src/components/AvatarCropper.tsx b/src/components/AvatarCropper.tsx index 3cc19d1..2c8bef9 100644 --- a/src/components/AvatarCropper.tsx +++ b/src/components/AvatarCropper.tsx @@ -38,10 +38,10 @@ export const AvatarCropper = ({ character, avatarIndex }: AvatarCropperProps) => movable resizable zoomable - data-existingx={existingCrop.x || undefined} - data-existingy={existingCrop.y || undefined} - data-existingw={existingCrop.width || undefined} - data-existingh={existingCrop.height || undefined} + data-existingx={existingCrop.x ?? null} + data-existingy={existingCrop.y ?? null} + data-existingw={existingCrop.width ?? null} + data-existingh={existingCrop.height ?? null} > diff --git a/src/components/AvatarDisplay.tsx b/src/components/AvatarDisplay.tsx index d82fa8a..320852e 100644 --- a/src/components/AvatarDisplay.tsx +++ b/src/components/AvatarDisplay.tsx @@ -1,8 +1,21 @@ import type { CropPercents } from "@src/db/character_avatars" -import type { CharacterAvatarWithUrl, ComputedCharacter } from "@src/services/computeCharacter" +import type { CharacterAvatarWithUrl } from "@src/services/computeCharacter" + +// Minimal avatar type - only the fields actually used by this component +type MinimalAvatar = Omit< + CharacterAvatarWithUrl, + "id" | "character_id" | "upload_id" | "created_at" | "updated_at" +> + +// Minimal character type needed for avatar display +interface CharacterWithAvatars { + id: string + name: string + avatars: MinimalAvatar[] +} export interface AvatarDisplayProps { - character: ComputedCharacter + character: CharacterWithAvatars avatarIndex?: number mode: "clickable-gallery" | "clickable-lightbox" | "display-only" className?: string @@ -65,7 +78,7 @@ export const AvatarDisplay = ({ }: AvatarDisplayProps) => { // For clickable-gallery mode, use primary avatar // For other modes, use specified index or primary - let avatar: CharacterAvatarWithUrl | undefined + let avatar: MinimalAvatar | undefined if (mode === "clickable-gallery") { avatar = character.avatars.find((a) => a.is_primary) || character.avatars[0] } else { diff --git a/src/components/AvatarLightbox.tsx b/src/components/AvatarLightbox.tsx index 86a3fc6..679e8a9 100644 --- a/src/components/AvatarLightbox.tsx +++ b/src/components/AvatarLightbox.tsx @@ -1,5 +1,4 @@ import type { ComputedCharacter } from "@src/services/computeCharacter" -import { AvatarDisplay } from "./AvatarDisplay" import { ModalContent } from "./ui/ModalContent" export interface AvatarLightboxProps { @@ -12,15 +11,29 @@ export const AvatarLightbox = ({ character, currentIndex }: AvatarLightboxProps) const prevIndex = currentIndex > 0 ? currentIndex - 1 : totalAvatars - 1 const nextIndex = currentIndex < totalAvatars - 1 ? currentIndex + 1 : 0 const showNavigation = totalAvatars > 1 + const currentAvatar = character.avatars[currentIndex] + + if (!currentAvatar) { + return ( + + + + ) + } return (