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

109 lines
4.5 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, useMemo, useRef, useState } from "preact/hooks";
import { MessageTools, type IMessage } from "../../tools/messages";
import { StateContext } from "../../contexts/state";
import { DOMTools } from "../../tools/dom";
import styles from './message.module.css';
import { AutoTextarea } from "../autoTextarea";
import { useInputState } from "@common/hooks/useInputState";
interface IProps {
message: IMessage;
index: number;
isLastUser: boolean;
isLastAssistant: boolean;
}
export const Message = ({ message, index, isLastUser, isLastAssistant }: IProps) => {
const { messages, editMessage, editSummary, deleteMessage, setCurrentSwipe, setMessages, continueMessage } = useContext(StateContext);
const [editing, setEditing] = useState(false);
const [editedMessage, setEditedMessage] = useInputState('');
const textRef = useRef<HTMLDivElement>(null);
const swipe = useMemo(() => MessageTools.getSwipe(message), [message]);
const content = swipe?.content;
const summary = swipe?.summary;
const cost = swipe?.cost ?? 0;
const htmlContent = useMemo(() => MessageTools.format(content ?? ''), [content]);
const handleEnableEdit = useCallback(() => {
setEditing(true);
setEditedMessage(content ?? '');
}, [content]);
const handleSaveEdit = useCallback(() => {
editMessage(index, editedMessage.trim(), cost);
editSummary(index, '', 0);
setEditing(false);
}, [editMessage, editSummary, index, editedMessage, cost]);
const handleCancelEdit = useCallback(() => {
setEditing(false);
}, [editMessage, index]);
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 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]);
const handleContinueMessage = useCallback(() => {
continueMessage(true);
}, [continueMessage]);
return (
<div class={`${styles.message} ${styles[message.role]} ${isLastUser ? styles.lastUser : ''}`}>
<div class={styles.content}>
{editing
? <AutoTextarea onInput={setEditedMessage} value={editedMessage} />
: <>
<div class={styles.text} dangerouslySetInnerHTML={{ __html: htmlContent }} ref={textRef} />
{summary && <small class={styles.summary}>{summary}</small>}
{cost > 0 && <small class={styles.summary}>💲&nbsp;{cost}</small>}
</>
}
<div class={styles.buttons}>
{editing
? <>
<button class='icon' onClick={handleSaveEdit} title='Save'></button>
<button class='icon' onClick={handleDeleteMessage} title='Delete'>🗑</button>
<button class='icon' onClick={handleStopHere} title='Stop here'></button>
<button class='icon' onClick={handleCancelEdit} title='Cancel'></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={handleContinueMessage} title="Continue"></button>
</>}
<button class='icon' onClick={handleEnableEdit} title="Edit">🖊</button>
</>
}
</div>
</div>
</div>
);
};