import { Avatar, IconButton, ImageList, ImageListItem, ListItem, ListItemAvatar, ListItemText, Stack, Typography } from '@mui/material'; import React, { Fragment, MutableRefObject } from 'react'; import { Maybe, doesExist } from '@apextoaster/js-utils'; import { Camera, Settings } from '@mui/icons-material'; import { useStore } from 'zustand'; import { formatters } from './format.js'; import { Actor } from './models.js'; import { StoreState, store } from './store.js'; export function openImage(image: string) { const byteCharacters = atob(image); const byteNumbers = new Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); const file = new Blob([byteArray], { type: 'image/jpeg;base64' }); const fileURL = URL.createObjectURL(file); window.open(fileURL, '_blank'); } export interface EventItemProps { // eslint-disable-next-line @typescript-eslint/no-explicit-any event: any; // eslint-disable-next-line @typescript-eslint/no-explicit-any focusRef?: MutableRefObject; renderEntity: (type: string, entity: string) => void; renderEvent: (event: string) => void; } export function characterSelector(state: StoreState) { return { character: state.character, }; } export function sameCharacter(a: Maybe, b: Maybe): boolean { if (doesExist(a) && doesExist(b)) { return a.name === b.name; } return false; } export function ActionEventItem(props: EventItemProps) { const { event, renderEvent } = props; const { id, actor, room, type } = event; const content = formatters[type](event); const state = useStore(store, characterSelector); const { character } = state; const playerAction = sameCharacter(actor, character); const typographyProps = { color: playerAction ? 'success.text' : 'primary.text', }; return renderEvent(id)}> } > {room.name.substring(0, 1)} {actor.name} {content} } /> ; } export function SnapshotEventItem(props: EventItemProps) { const { event } = props; const { step, world } = event; const { name, theme } = world; return {step} Step: {step} World Theme: {theme} } /> ; } export function ReplyEventItem(props: EventItemProps) { const { event } = props; const { text } = event; return {text} } /> ; } export function PlayerEventItem(props: EventItemProps) { const { event } = props; const { character, status, client } = event; let primary = ''; let secondary = ''; if (status === 'join') { primary = 'Player Joined'; secondary = `${client} is now playing as ${character}`; } if (status === 'leave') { primary = 'Player Left'; secondary = `${client} has left the game. ${character} is now controlled by an LLM`; } return {character.substring(0, 1)} {secondary} } /> ; } export function RenderEventItem(props: EventItemProps) { const { event } = props; const { images, prompt, title = 'Render' } = event; return {prompt} } /> {Object.entries(images).map(([name, image]) => openImage(image as string)}> Render )} ; } export function PromptEventItem(props: EventItemProps) { const { event } = props; const { character, prompt } = event; const state = useStore(store, characterSelector); const { character: playerCharacter } = state; const playerPrompt = sameCharacter(playerCharacter, character); const typographyProps = { color: playerPrompt ? 'success.text' : 'primary.text', }; return {character.substring(0, 1)} Prompt for {character}: {prompt} } /> ; } export function GenerateEventItem(props: EventItemProps) { const { event, renderEntity } = props; const { entity, name } = event; let renderButton; if (doesExist(entity)) { renderButton = renderEntity(entity.type, entity.name)}> ; } return {name.substring(0, 1)} {name} } /> ; } export function EventItem(props: EventItemProps) { const { event } = props; const { type } = event; switch (type) { case 'action': case 'result': return ; case 'reply': case 'status': // TODO: should have a different component return ; case 'player': return ; case 'render': return ; case 'snapshot': return ; case 'prompt': return ; case 'generate': return ; default: return ; } }