diff --git a/apps/server/src/routes/characters.ts b/apps/server/src/routes/characters.ts index dea0e3f54..0c78fc1f7 100644 --- a/apps/server/src/routes/characters.ts +++ b/apps/server/src/routes/characters.ts @@ -41,6 +41,7 @@ export function createCharacterRoutes(characterService: CharacterService) { throw createBadRequestError('Invalid Request', 'INVALID_REQUEST', result.issues) } + // @ts-expect-error - TODO: Fix this const character = await characterService.create({ ...result.output, character: { @@ -48,7 +49,7 @@ export function createCharacterRoutes(characterService: CharacterService) { ownerId: user.id, creatorId: user.id, }, - } as any) + }) return c.json(character, 201) }) @@ -64,7 +65,7 @@ export function createCharacterRoutes(characterService: CharacterService) { throw createBadRequestError('Invalid Request', 'INVALID_REQUEST', result.issues) } - const existing = await characterService.findById(id, { withRelations: false }) + const existing = await characterService.findById(id) if (!existing) throw createNotFoundError() if (existing.ownerId !== user.id) @@ -78,7 +79,7 @@ export function createCharacterRoutes(characterService: CharacterService) { const user = c.get('user')! const id = c.req.param('id') - const existing = await characterService.findById(id, { withRelations: false }) + const existing = await characterService.findById(id) if (!existing) throw createNotFoundError() if (existing.ownerId !== user.id) diff --git a/apps/server/src/routes/providers.ts b/apps/server/src/routes/providers.ts index 7d0ef44e3..50af4499b 100644 --- a/apps/server/src/routes/providers.ts +++ b/apps/server/src/routes/providers.ts @@ -40,7 +40,7 @@ export function createProviderRoutes(providerService: ProviderService) { const provider = await providerService.createUserConfig({ ...result.output, ownerId: user.id, - } as any) + }) return c.json(provider, 201) }) diff --git a/apps/server/src/services/characters.ts b/apps/server/src/services/characters.ts index 3b0a86117..bf2a4badb 100644 --- a/apps/server/src/services/characters.ts +++ b/apps/server/src/services/characters.ts @@ -8,23 +8,21 @@ import * as userCharacterSchema from '../schemas/user-character' export function createCharacterService(db: Database) { return { - async findById(id: string, options: { withRelations?: boolean } = { withRelations: true }) { + async findById(id: string) { return await db.query.character.findFirst({ where: and( eq(schema.character.id, id), isNull(schema.character.deletedAt), ), - with: options.withRelations - ? { - capabilities: true, - avatarModels: true, - i18n: true, - prompts: true, - likes: true, - bookmarks: true, - cover: true, - } - : undefined, + with: { + capabilities: true, + avatarModels: true, + i18n: true, + prompts: true, + likes: true, + bookmarks: true, + cover: true, + }, }) }, @@ -44,18 +42,16 @@ export function createCharacterService(db: Database) { }) }, - async findAll(options: { withRelations?: boolean } = { withRelations: true }) { + async findAll() { return await db.query.character.findMany({ where: isNull(schema.character.deletedAt), - with: options.withRelations - ? { - i18n: true, - capabilities: true, - likes: true, - bookmarks: true, - cover: true, - } - : undefined, + with: { + i18n: true, + capabilities: true, + likes: true, + bookmarks: true, + cover: true, + }, }) }, @@ -150,30 +146,30 @@ export function createCharacterService(db: Database) { await tx.insert(schema.characterCovers).values({ ...data.cover, characterId: inserted.id, - } as schema.NewCharacterCover) + }) } if (data.capabilities?.length) { await tx.insert(schema.characterCapabilities).values( - data.capabilities.map(c => ({ ...c, characterId: inserted.id }) as schema.NewCharacterCapability), + data.capabilities.map(c => ({ ...c, characterId: inserted.id })), ) } if (data.avatarModels?.length) { await tx.insert(schema.avatarModel).values( - data.avatarModels.map(a => ({ ...a, characterId: inserted.id }) as schema.NewAvatarModel), + data.avatarModels.map(a => ({ ...a, characterId: inserted.id })), ) } if (data.i18n?.length) { await tx.insert(schema.characterI18n).values( - data.i18n.map(i => ({ ...i, characterId: inserted.id }) as schema.NewCharacterI18n), + data.i18n.map(i => ({ ...i, characterId: inserted.id })), ) } if (data.prompts?.length) { await tx.insert(schema.characterPrompts).values( - data.prompts.map(p => ({ ...p, characterId: inserted.id }) as schema.NewCharacterPrompt), + data.prompts.map(p => ({ ...p, characterId: inserted.id })), ) } diff --git a/apps/stage-web/src/pages/settings/characters/index.vue b/apps/stage-web/src/pages/settings/characters/index.vue index 5933fa0bd..dbcedaf27 100644 --- a/apps/stage-web/src/pages/settings/characters/index.vue +++ b/apps/stage-web/src/pages/settings/characters/index.vue @@ -10,7 +10,7 @@ import CharacterDialog from './components/CharacterDialog.vue' import CharacterItem from './components/CharacterItem.vue' const characterStore = useCharacterStore() -const { characters, isLoading } = storeToRefs(characterStore) +const { characters } = storeToRefs(characterStore) // Fetch on mount onMounted(() => { @@ -22,7 +22,7 @@ const searchQuery = ref('') const filteredCharacters = computed(() => { const query = searchQuery.value.toLowerCase() return Array.from(characters.value.values()).filter((char) => { - const i18n = char.i18n.find(i => i.language === 'en') || char.i18n[0] + const i18n = char.i18n?.find(i => i.language === 'en') || char.i18n?.[0] return i18n?.name.toLowerCase().includes(query) || i18n?.description.toLowerCase().includes(query) }) }) @@ -87,7 +87,7 @@ function handleActivate(char: Character) { -
+
diff --git a/packages/stage-ui/src/stores/characters.ts b/packages/stage-ui/src/stores/characters.ts index b59d9aab9..f59f83ea1 100644 --- a/packages/stage-ui/src/stores/characters.ts +++ b/packages/stage-ui/src/stores/characters.ts @@ -1,10 +1,12 @@ import type { Character, CreateCharacterPayload, UpdateCharacterPayload } from '../types/character' import { defineStore } from 'pinia' +import { parse } from 'valibot' import { ref } from 'vue' import { client } from '../composables/api' import { useAsyncState } from '../composables/use-async-state' +import { CharacterWithRelationsSchema } from '../types/character' export const useCharacterStore = defineStore('characters', () => { const characters = ref>(new Map()) @@ -21,7 +23,7 @@ export const useCharacterStore = defineStore('characters', () => { characters.value.clear() for (const char of data) { - characters.value.set(char.id, char) + characters.value.set(char.id, parse(CharacterWithRelationsSchema, char)) } }, { immediate: true }) } @@ -34,7 +36,8 @@ export const useCharacterStore = defineStore('characters', () => { if (!res.ok) { throw new Error('Failed to fetch character') } - const character = await res.json() + const data = await res.json() + const character = parse(CharacterWithRelationsSchema, data) characters.value.set(character.id, character) return character @@ -49,7 +52,8 @@ export const useCharacterStore = defineStore('characters', () => { if (!res.ok) { throw new Error('Failed to create character') } - const character = await res.json() + const data = await res.json() + const character = parse(CharacterWithRelationsSchema, data) characters.value.set(character.id, character) return character @@ -58,14 +62,16 @@ export const useCharacterStore = defineStore('characters', () => { async function update(id: string, payload: UpdateCharacterPayload) { return useAsyncState(async () => { - const res = await client.api.characters[':id'].$patch({ + const res = await (client.api.characters[':id'].$patch)({ param: { id }, + // @ts-expect-error FIXME: hono client typing misses json option for this route json: payload, }) if (!res.ok) { throw new Error('Failed to update character') } - const character = await res.json() + const data = await res.json() + const character = parse(CharacterWithRelationsSchema, data) characters.value.set(character.id, character) return character @@ -87,7 +93,7 @@ export const useCharacterStore = defineStore('characters', () => { async function like(id: string) { return useAsyncState(async () => { - const res = await client.api.characters[':id'].$patch({ + const res = await client.api.characters[':id'].like.$post({ param: { id }, }) if (!res.ok) { @@ -100,7 +106,7 @@ export const useCharacterStore = defineStore('characters', () => { async function bookmark(id: string) { return useAsyncState(async () => { - const res = await client.api.characters[':id'].$patch({ + const res = await client.api.characters[':id'].bookmark.$post({ param: { id }, }) if (!res.ok) { diff --git a/packages/stage-ui/src/types/character.ts b/packages/stage-ui/src/types/character.ts index 92553dd3f..131926301 100644 --- a/packages/stage-ui/src/types/character.ts +++ b/packages/stage-ui/src/types/character.ts @@ -77,6 +77,7 @@ export const CharacterBaseSchema = object({ characterId: string(), createdAt: DateSchema, updatedAt: DateSchema, + deletedAt: optional(DateSchema), }) export const CharacterCapabilitySchema = object({ @@ -121,12 +122,12 @@ export const CharacterPromptSchema = object({ export const CharacterWithRelationsSchema = object({ ...CharacterBaseSchema.entries, - capabilities: array(CharacterCapabilitySchema), - avatarModels: array(AvatarModelSchema), - i18n: array(CharacterI18nSchema), - prompts: array(CharacterPromptSchema), - likes: array(object({ userId: string(), characterId: string() })), - bookmarks: array(object({ userId: string(), characterId: string() })), + capabilities: optional(array(CharacterCapabilitySchema)), + avatarModels: optional(array(AvatarModelSchema)), + i18n: optional(array(CharacterI18nSchema)), + prompts: optional(array(CharacterPromptSchema)), + likes: optional(array(object({ userId: string(), characterId: string() }))), + bookmarks: optional(array(object({ userId: string(), characterId: string() }))), }) // --- API Request Schemas ---