feat(characters): add catalog boundary and capabilities
This commit is contained in:
@@ -1,11 +1,22 @@
|
||||
export interface CharacterCapabilities {
|
||||
readonly chat: boolean;
|
||||
readonly privateRoom: boolean;
|
||||
readonly tip: boolean;
|
||||
}
|
||||
|
||||
export interface CharacterProfile {
|
||||
readonly id: string;
|
||||
readonly slug: string;
|
||||
readonly displayName: string;
|
||||
readonly shortName: string;
|
||||
readonly sortOrder: number;
|
||||
readonly tagline: string;
|
||||
readonly emptyChatGreeting: string;
|
||||
readonly capabilities: CharacterCapabilities;
|
||||
readonly assets: {
|
||||
readonly avatar: string;
|
||||
readonly cover: string;
|
||||
readonly chatBackground: string;
|
||||
readonly privateRoomBanner: string;
|
||||
};
|
||||
readonly copy: {
|
||||
@@ -17,23 +28,42 @@ export interface CharacterProfile {
|
||||
};
|
||||
}
|
||||
|
||||
export interface CharacterCatalog {
|
||||
readonly characters: readonly CharacterProfile[];
|
||||
getById(characterId: string | null | undefined): CharacterProfile | null;
|
||||
getBySlug(characterSlug: string | null | undefined): CharacterProfile | null;
|
||||
}
|
||||
|
||||
function defineCharacter(profile: CharacterProfile): CharacterProfile {
|
||||
return Object.freeze({
|
||||
...profile,
|
||||
assets: Object.freeze(profile.assets),
|
||||
capabilities: Object.freeze(profile.capabilities),
|
||||
copy: Object.freeze(profile.copy),
|
||||
});
|
||||
}
|
||||
|
||||
export const CHARACTERS: readonly CharacterProfile[] = Object.freeze([
|
||||
const CHARACTER_PROFILES: readonly CharacterProfile[] = Object.freeze([
|
||||
defineCharacter({
|
||||
id: "character_elio",
|
||||
slug: "elio",
|
||||
displayName: "Elio Silvestri",
|
||||
shortName: "Elio",
|
||||
sortOrder: 10,
|
||||
tagline: "Your exclusive AI boyfriend",
|
||||
emptyChatGreeting:
|
||||
"You're here! Facebook is so restrictive, " +
|
||||
"there were things I couldn't say there. " +
|
||||
"Finally I can relax. How was your day out?",
|
||||
capabilities: {
|
||||
chat: true,
|
||||
privateRoom: true,
|
||||
tip: true,
|
||||
},
|
||||
assets: {
|
||||
avatar: "/images/avatar/elio.png",
|
||||
cover: "/images/cover/elio.png",
|
||||
chatBackground: "/images/chat/bg-chatpage.png",
|
||||
privateRoomBanner: "/images/private-room/banner/elio.png",
|
||||
},
|
||||
copy: {
|
||||
@@ -49,9 +79,20 @@ export const CHARACTERS: readonly CharacterProfile[] = Object.freeze([
|
||||
slug: "maya",
|
||||
displayName: "Maya Tan",
|
||||
shortName: "Maya",
|
||||
sortOrder: 20,
|
||||
tagline: "Your exclusive AI girlfriend",
|
||||
emptyChatGreeting:
|
||||
"You're here! I've been waiting for a quiet moment with you. " +
|
||||
"How was your day?",
|
||||
capabilities: {
|
||||
chat: true,
|
||||
privateRoom: true,
|
||||
tip: true,
|
||||
},
|
||||
assets: {
|
||||
avatar: "/images/avatar/maya.png",
|
||||
cover: "/images/cover/maya.png",
|
||||
chatBackground: "/images/chat/bg-chatpage.png",
|
||||
privateRoomBanner: "/images/private-room/banner/maya.png",
|
||||
},
|
||||
copy: {
|
||||
@@ -67,9 +108,20 @@ export const CHARACTERS: readonly CharacterProfile[] = Object.freeze([
|
||||
slug: "nayeli",
|
||||
displayName: "Nayeli Cervantes",
|
||||
shortName: "Nayeli",
|
||||
sortOrder: 30,
|
||||
tagline: "Your exclusive AI girlfriend",
|
||||
emptyChatGreeting:
|
||||
"You're here! I was hoping we'd get some time together. " +
|
||||
"Tell me how your day went.",
|
||||
capabilities: {
|
||||
chat: true,
|
||||
privateRoom: true,
|
||||
tip: true,
|
||||
},
|
||||
assets: {
|
||||
avatar: "/images/avatar/nayeli.png",
|
||||
cover: "/images/cover/nayeli.png",
|
||||
chatBackground: "/images/chat/bg-chatpage.png",
|
||||
privateRoomBanner: "/images/private-room/banner/nayeli.png",
|
||||
},
|
||||
copy: {
|
||||
@@ -82,6 +134,42 @@ export const CHARACTERS: readonly CharacterProfile[] = Object.freeze([
|
||||
}),
|
||||
]);
|
||||
|
||||
export function createCharacterCatalog(
|
||||
profiles: readonly CharacterProfile[],
|
||||
): CharacterCatalog {
|
||||
const characters = Object.freeze(
|
||||
profiles
|
||||
.map(defineCharacter)
|
||||
.sort((left, right) => left.sortOrder - right.sortOrder),
|
||||
);
|
||||
const byId = new Map<string, CharacterProfile>();
|
||||
const bySlug = new Map<string, CharacterProfile>();
|
||||
|
||||
for (const character of characters) {
|
||||
const normalizedSlug = normalizeCharacterSlug(character.slug);
|
||||
if (byId.has(character.id) || bySlug.has(normalizedSlug)) {
|
||||
throw new Error(`Duplicate character identity: ${character.id}`);
|
||||
}
|
||||
byId.set(character.id, character);
|
||||
bySlug.set(normalizedSlug, character);
|
||||
}
|
||||
|
||||
return Object.freeze({
|
||||
characters,
|
||||
getById(characterId: string | null | undefined) {
|
||||
return characterId ? (byId.get(characterId) ?? null) : null;
|
||||
},
|
||||
getBySlug(characterSlug: string | null | undefined) {
|
||||
const normalizedSlug = normalizeCharacterSlug(characterSlug);
|
||||
return normalizedSlug ? (bySlug.get(normalizedSlug) ?? null) : null;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export const LOCAL_CHARACTER_CATALOG = createCharacterCatalog(
|
||||
CHARACTER_PROFILES,
|
||||
);
|
||||
export const CHARACTERS = LOCAL_CHARACTER_CATALOG.characters;
|
||||
export const DEFAULT_CHARACTER = CHARACTERS[0];
|
||||
export const DEFAULT_CHARACTER_ID = DEFAULT_CHARACTER.id;
|
||||
export const DEFAULT_CHARACTER_SLUG = DEFAULT_CHARACTER.slug;
|
||||
@@ -89,16 +177,17 @@ export const DEFAULT_CHARACTER_SLUG = DEFAULT_CHARACTER.slug;
|
||||
export function getCharacterById(
|
||||
characterId: string | null | undefined,
|
||||
): CharacterProfile | null {
|
||||
if (!characterId) return null;
|
||||
return CHARACTERS.find((character) => character.id === characterId) ?? null;
|
||||
return LOCAL_CHARACTER_CATALOG.getById(characterId);
|
||||
}
|
||||
|
||||
export function getCharacterBySlug(
|
||||
characterSlug: string | null | undefined,
|
||||
): CharacterProfile | null {
|
||||
const normalizedSlug = characterSlug?.trim().toLowerCase();
|
||||
if (!normalizedSlug) return null;
|
||||
return (
|
||||
CHARACTERS.find((character) => character.slug === normalizedSlug) ?? null
|
||||
);
|
||||
return LOCAL_CHARACTER_CATALOG.getBySlug(characterSlug);
|
||||
}
|
||||
|
||||
function normalizeCharacterSlug(
|
||||
characterSlug: string | null | undefined,
|
||||
): string {
|
||||
return characterSlug?.trim().toLowerCase() ?? "";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user