diff --git a/src/games/ai/components/header/header.tsx b/src/games/ai/components/header/header.tsx index 445e174..2b21610 100644 --- a/src/games/ai/components/header/header.tsx +++ b/src/games/ai/components/header/header.tsx @@ -47,7 +47,7 @@ export const Header = () => { const handleBlurBannedWords = useCallback((e: Event) => { if (e.target instanceof HTMLTextAreaElement) { - const words = e.target.value.split('\n').sort(); + const words = e.target.value.toLowerCase().split('\n').sort(); setBannedWords(words); } }, [setBannedWords]); diff --git a/src/games/ai/components/message/message.module.css b/src/games/ai/components/message/message.module.css index 81cf4c8..c6ebf1f 100644 --- a/src/games/ai/components/message/message.module.css +++ b/src/games/ai/components/message/message.module.css @@ -27,10 +27,14 @@ border: var(--border); min-height: 100px; height: unset; - resize: vertical; line-height: 1.5; padding: 5px; border-radius: var(--border-radius); + overflow: hidden; + } + + .summary { + opacity: 0.7; } .buttons { diff --git a/src/games/ai/components/message/message.tsx b/src/games/ai/components/message/message.tsx index 1ae8470..dce5548 100644 --- a/src/games/ai/components/message/message.tsx +++ b/src/games/ai/components/message/message.tsx @@ -14,25 +14,31 @@ interface IProps { } export const Message = ({ message, index, isLastUser, isLastAssistant }: IProps) => { - const { messages, editMessage, deleteMessage, setCurrentSwipe, setMessages } = useContext(StateContext); + const { messages, editMessage, editSummary, deleteMessage, setCurrentSwipe, setMessages } = useContext(StateContext); const [editing, setEditing] = useState(false); - const [savedMessage, setSavedMessage] = useState(''); + const [editedMessage, setEditedMessage] = useState(''); const textRef = useRef(null); - const content = useMemo(() => MessageTools.getSwipe(message)?.content, [message]); + const swipe = useMemo(() => MessageTools.getSwipe(message), [message]); + + const content = swipe?.content; + const summary = swipe?.summary; const htmlContent = useMemo(() => MessageTools.format(content ?? ''), [content]); - const handleToggleEdit = useCallback(() => { - setEditing(!editing); - if (!editing) { - setSavedMessage(content ?? ''); - } - }, [editing, content]); + const handleEnableEdit = useCallback(() => { + setEditing(true); + setEditedMessage(content ?? ''); + }, [content]); + + const handleSaveEdit = useCallback(() => { + editMessage(index, editedMessage.trim()); + editSummary(index, ''); + setEditing(false); + }, [editMessage, editSummary, index, editedMessage]); const handleCancelEdit = useCallback(() => { setEditing(false); - editMessage(index, savedMessage); - }, [editMessage, index, savedMessage]); + }, [editMessage, index]); const handleDeleteMessage = useCallback(() => { if (confirm('Delete message?')) { @@ -51,9 +57,9 @@ export const Message = ({ message, index, isLastUser, isLastAssistant }: IProps) const handleEdit = useCallback((e: InputEvent) => { if (e.target instanceof HTMLTextAreaElement) { const newContent = e.target.value; - editMessage(index, newContent); + setEditedMessage(newContent); } - }, [editMessage, index]); + }, []); const handleSwipeLeft = useCallback(() => { setCurrentSwipe(index, message.currentSwipe - 1); @@ -69,17 +75,20 @@ export const Message = ({ message, index, isLastUser, isLastAssistant }: IProps)
{editing - ? - :
+ ? + : <> +
+ {summary && {summary}} + } {(isLastUser || message.role === 'assistant') &&
{editing ? <> - - + + - + : <> {isLastAssistant && @@ -89,7 +98,7 @@ export const Message = ({ message, index, isLastUser, isLastAssistant }: IProps)
} - + }
diff --git a/src/games/ai/components/minichat/minichat.tsx b/src/games/ai/components/minichat/minichat.tsx index 152347d..68f7e0b 100644 --- a/src/games/ai/components/minichat/minichat.tsx +++ b/src/games/ai/components/minichat/minichat.tsx @@ -58,11 +58,11 @@ export const MiniChat = ({ history = [], buttons = {}, open, onClose }: IProps) for await (const chunk of generate(prompt)) { text += chunk; - setMessages(MessageTools.updateContent(newMessages, messageId, text)); + setMessages(MessageTools.updateSwipe(newMessages, messageId, text)); } setMessages([ - ...MessageTools.updateContent(newMessages, messageId, MessageTools.trimSentence(text)), + ...MessageTools.updateSwipe(newMessages, messageId, MessageTools.trimSentence(text)), MessageTools.create('', 'user', true), ]); MessageTools.playReady(); @@ -78,7 +78,7 @@ export const MiniChat = ({ history = [], buttons = {}, open, onClose }: IProps) const handleChange = useCallback((i: number, e: InputEvent) => { if (e.target instanceof HTMLTextAreaElement) { - setMessages(MessageTools.updateContent(messages, i, e.target.value)); + setMessages(MessageTools.updateSwipe(messages, i, e.target.value)); } }, [messages]); diff --git a/src/games/ai/contexts/llm.tsx b/src/games/ai/contexts/llm.tsx index 4c41200..24760e6 100644 --- a/src/games/ai/contexts/llm.tsx +++ b/src/games/ai/contexts/llm.tsx @@ -84,10 +84,11 @@ export const LLMContext = createContext({} as ILLMContext); export const LLMContextProvider = ({ children }: { children?: any }) => { const { connectionUrl, messages, triggerNext, lore, userPrompt, systemPrompt, bannedWords, instruct, summarizePrompt, - setTriggerNext, addMessage, editMessage, setInstruct, + setTriggerNext, addMessage, editMessage, editSummary, setInstruct, } = useContext(StateContext); const generating = useBool(false); + const summarizing = useBool(false); const blockConnection = useBool(false); const [promptTokens, setPromptTokens] = useState(0); const [contextLength, setContextLength] = useState(0); @@ -293,7 +294,7 @@ export const LLMContextProvider = ({ children }: { children?: any }) => { const tokens = await Array.fromAsync(actions.generate(prompt)); - return tokens.join(''); + return MessageTools.trimSentence(tokens.join('')); }, countTokens: async (prompt) => { if (!connectionUrl) { @@ -333,6 +334,7 @@ export const LLMContextProvider = ({ children }: { children?: any }) => { messageId++; } + editSummary(messageId, 'Generating...'); for await (const chunk of actions.generate(prompt)) { text += chunk; setPromptTokens(tokens + Math.round(text.length * 0.25)); @@ -340,7 +342,8 @@ export const LLMContextProvider = ({ children }: { children?: any }) => { } text = MessageTools.trimSentence(text); - editMessage(messageId, text.trim()); + editMessage(messageId, text); + editSummary(messageId, ''); setPromptTokens(0); // trigger calculation @@ -348,6 +351,21 @@ export const LLMContextProvider = ({ children }: { children?: any }) => { } })(), [actions, triggerNext, messages, generating.value]); + useEffect(() => void (async () => { + if (!generating.value && !summarizing.value) { + summarizing.setTrue(); + for (let id = 0; id < messages.length; id++) { + const message = messages[id]; + const swipe = MessageTools.getSwipe(message); + if (message.role === 'assistant' && swipe?.content?.includes('\n') && !swipe.summary) { + const summary = await actions.summarize(swipe.content); + editSummary(id, summary); + } + } + summarizing.setFalse(); + } + })(), [messages, generating.value, summarizing.value]); + useEffect(() => { if (!blockConnection.value) { setPromptTokens(0); diff --git a/src/games/ai/contexts/state.tsx b/src/games/ai/contexts/state.tsx index b69c96d..70dadae 100644 --- a/src/games/ai/contexts/state.tsx +++ b/src/games/ai/contexts/state.tsx @@ -29,7 +29,8 @@ interface IActions { setMessages: (messages: IMessage[]) => void; addMessage: (content: string, role: IMessage['role'], triggerNext?: boolean) => void; - editMessage: (index: number, content: string, triggerNext?: boolean) => void; + editMessage: (index: number, content: string) => void; + editSummary: (index: number, summary: string) => void; deleteMessage: (index: number) => void; setCurrentSwipe: (index: number, swipe: number) => void; addSwipe: (index: number, content: string) => void; @@ -120,9 +121,11 @@ export const StateContextProvider = ({ children }: { children?: any }) => { ]); setTriggerNext(triggerNext); }, - editMessage: (index, content, triggerNext = false) => { - setMessages(messages => MessageTools.updateContent(messages, index, content)); - setTriggerNext(triggerNext); + editMessage: (index, content) => { + setMessages(messages => MessageTools.updateSwipe(messages, index, { content })); + }, + editSummary: (index, summary) => { + setMessages(messages => MessageTools.updateSwipe(messages, index, { summary })); }, deleteMessage: (index) => setMessages(messages => messages.filter((_, i) => i !== index) diff --git a/src/games/ai/messages.ts b/src/games/ai/messages.ts index 425044d..19420e6 100644 --- a/src/games/ai/messages.ts +++ b/src/games/ai/messages.ts @@ -3,6 +3,7 @@ import messageSound from './assets/message.mp3'; export interface ISwipe { content: string; + summary?: string; } export interface IMessage { @@ -94,15 +95,15 @@ export namespace MessageTools { if (latestEnd > 0) { text = text.slice(0, latestEnd + 1); } - return text.trimEnd(); + return text.trim(); } - export const updateContent = (messages: IMessage[], index: number, content: string) => ( + export const updateSwipe = (messages: IMessage[], index: number, update: Partial) => ( messages.map( (m, i) => ({ ...m, swipes: i === index - ? m.swipes.map((s, si) => (si === m.currentSwipe ? { content } : s)) + ? m.swipes.map((s, si) => (si === m.currentSwipe ? { ...s, ...update } : s)) : m.swipes }) )