1
0
Fork 0
tsgames/src/games/ai/components/message/message.tsx

101 lines
4.1 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks";
import { MessageTools, type IMessage } from "../../messages";
import { StateContext } from "../../contexts/state";
import { DOMTools } from "../../dom";
import styles from './message.module.css';
import { AutoTextarea } from "../autoTextarea";
interface IProps {
message: IMessage;
index: number;
isLastUser: boolean;
isLastAssistant: boolean;
}
export const Message = ({ message, index, isLastUser, isLastAssistant }: IProps) => {
const { messages, editMessage, deleteMessage, setCurrentSwipe, setMessages } = useContext(StateContext);
const [editing, setEditing] = useState(false);
const [savedMessage, setSavedMessage] = useState('');
const textRef = useRef<HTMLDivElement>(null);
const content = useMemo(() => MessageTools.getSwipe(message)?.content, [message]);
const htmlContent = useMemo(() => MessageTools.format(content ?? ''), [content]);
const handleToggleEdit = useCallback(() => {
setEditing(!editing);
if (!editing) {
setSavedMessage(content ?? '');
}
}, [editing, content]);
const handleCancelEdit = useCallback(() => {
setEditing(false);
editMessage(index, savedMessage);
}, [editMessage, index, savedMessage]);
const handleDeleteMessage = useCallback(() => {
if (confirm('Delete message?')) {
setEditing(false);
deleteMessage(index);
}
}, [deleteMessage, index]);
const handleStopHere = useCallback(() => {
if (confirm('Delete all messages after that?')) {
setMessages(messages.filter((_, i) => i <= index));
setEditing(false);
}
}, [messages, setMessages, index]);
const handleEdit = useCallback((e: InputEvent) => {
if (e.target instanceof HTMLTextAreaElement) {
const newContent = e.target.value;
editMessage(index, newContent);
}
}, [editMessage, index]);
const handleSwipeLeft = useCallback(() => {
setCurrentSwipe(index, message.currentSwipe - 1);
DOMTools.animate(textRef.current, 'swipe-from-left');
}, [setCurrentSwipe, index, message]);
const handleSwipeRight = useCallback(() => {
setCurrentSwipe(index, message.currentSwipe + 1);
DOMTools.animate(textRef.current, 'swipe-from-right');
}, [setCurrentSwipe, index, message]);
return (
<div class={`${styles.message} ${styles[message.role]} ${isLastUser ? styles.lastUser : ''}`}>
<div class={styles.content}>
{editing
? <AutoTextarea onInput={handleEdit} value={content} />
: <div class={styles.text} dangerouslySetInnerHTML={{ __html: htmlContent }} ref={textRef} />
}
{(isLastUser || message.role === 'assistant') &&
<div class={styles.buttons}>
{editing
? <>
<button class='icon' onClick={handleToggleEdit}></button>
<button class='icon' onClick={handleDeleteMessage}>🗑</button>
<button class='icon' onClick={handleStopHere} title='Stop here'></button>
<button class='icon' onClick={handleCancelEdit}></button>
</>
: <>
{isLastAssistant &&
<div class={styles.swipes}>
<div onClick={handleSwipeLeft}></div>
<div>{message.currentSwipe + 1}/{message.swipes.length}</div>
<div onClick={handleSwipeRight}></div>
</div>
}
<button class='icon' onClick={handleToggleEdit} title="Edit">🖊</button>
</>
}
</div>
}
</div>
</div>
);
};