Spaces:
Sleeping
Sleeping
| import clsx from 'clsx'; | |
| import { useMutation, useQuery } from 'convex/react'; | |
| import { KeyboardEvent, useRef, useState } from 'react'; | |
| import { api } from '../../convex/_generated/api'; | |
| import { Id } from '../../convex/_generated/dataModel'; | |
| import { useSendInput } from '../hooks/sendInput'; | |
| import { Player } from '../../convex/aiTown/player'; | |
| import { Conversation } from '../../convex/aiTown/conversation'; | |
| export function MessageInput({ | |
| worldId, | |
| engineId, | |
| humanPlayer, | |
| conversation, | |
| }: { | |
| worldId: Id<'worlds'>; | |
| engineId: Id<'engines'>; | |
| humanPlayer: Player; | |
| conversation: Conversation; | |
| }) { | |
| const descriptions = useQuery(api.world.gameDescriptions, { worldId }); | |
| const humanName = descriptions?.playerDescriptions.find((p) => p.playerId === humanPlayer.id) | |
| ?.name; | |
| const inputRef = useRef<HTMLParagraphElement>(null); | |
| const inflightUuid = useRef<string | undefined>(); | |
| const writeMessage = useMutation(api.messages.writeMessage); | |
| const startTyping = useSendInput(engineId, 'startTyping'); | |
| const currentlyTyping = conversation.isTyping; | |
| const onKeyDown = async (e: KeyboardEvent) => { | |
| e.stopPropagation(); | |
| // Set the typing indicator if we're not submitting. | |
| if (e.key !== 'Enter') { | |
| console.log(inflightUuid.current); | |
| if (currentlyTyping || inflightUuid.current !== undefined) { | |
| return; | |
| } | |
| inflightUuid.current = crypto.randomUUID(); | |
| try { | |
| // Don't show a toast on error. | |
| await startTyping({ | |
| playerId: humanPlayer.id, | |
| conversationId: conversation.id, | |
| messageUuid: inflightUuid.current, | |
| }); | |
| } finally { | |
| inflightUuid.current = undefined; | |
| } | |
| return; | |
| } | |
| // Send the current message. | |
| e.preventDefault(); | |
| if (!inputRef.current) { | |
| return; | |
| } | |
| const text = inputRef.current.innerText; | |
| inputRef.current.innerText = ''; | |
| if (!text) { | |
| return; | |
| } | |
| let messageUuid = inflightUuid.current; | |
| if (currentlyTyping && currentlyTyping.playerId === humanPlayer.id) { | |
| messageUuid = currentlyTyping.messageUuid; | |
| } | |
| messageUuid = messageUuid || crypto.randomUUID(); | |
| await writeMessage({ | |
| worldId, | |
| playerId: humanPlayer.id, | |
| conversationId: conversation.id, | |
| text, | |
| messageUuid, | |
| }); | |
| }; | |
| return ( | |
| <div className="leading-tight mb-6"> | |
| <div className="flex gap-4"> | |
| <span className="uppercase flex-grow">{humanName}</span> | |
| </div> | |
| <div className={clsx('bubble', 'bubble-mine')}> | |
| <p | |
| className="bg-white -mx-3 -my-1" | |
| ref={inputRef} | |
| contentEditable | |
| style={{ outline: 'none' }} | |
| tabIndex={0} | |
| placeholder="Type here" | |
| onKeyDown={(e) => onKeyDown(e)} | |
| /> | |
| </div> | |
| </div> | |
| ); | |
| } | |