tests scrolling to top, show/hide templates

This commit is contained in:
Daniel Avila 2023-02-11 11:37:20 -05:00
parent 5af5a97d8f
commit 7dd4e78bbf
11 changed files with 101 additions and 29 deletions

View file

@ -56,7 +56,7 @@ module.exports = {
return { message: 'Error updating conversation' }; return { message: 'Error updating conversation' };
} }
}, },
getConvos: async () => await Conversation.find({}).exec(), getConvos: async () => await Conversation.find({}).sort({ created: -1 }).exec(),
deleteConvos: async (filter) => { deleteConvos: async (filter) => {
let deleteCount = await Conversation.deleteMany(filter).exec(); let deleteCount = await Conversation.deleteMany(filter).exec();

View file

@ -69,12 +69,17 @@ app.post('/update_convo', async (req, res) => {
}); });
app.post('/ask', async (req, res) => { app.post('/ask', async (req, res) => {
console.log(req.body);
const { text, parentMessageId, conversationId } = req.body; const { text, parentMessageId, conversationId } = req.body;
if (!text.trim().includes(' ') && text.length < 5) {
res.status(500).write('Prompt empty or too short');
res.end();
return;
}
const userMessageId = crypto.randomUUID(); const userMessageId = crypto.randomUUID();
let userMessage = { id: userMessageId, sender: 'User', text }; let userMessage = { id: userMessageId, sender: 'User', text };
console.log(userMessage, req.body); console.log('initial ask log', userMessage);
res.writeHead(200, { res.writeHead(200, {
Connection: 'keep-alive', Connection: 'keep-alive',

View file

@ -1,19 +1,18 @@
import React, { useState, useRef } from 'react'; import React, { useState, useRef } from 'react';
import RenameButton from './RenameButton'; import RenameButton from './RenameButton';
import DeleteButton from './DeleteButton'; import DeleteButton from './DeleteButton';
import { useSelector, useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { setConversation } from '~/store/convoSlice'; import { setConversation } from '~/store/convoSlice';
import { setMessages } from '~/store/messageSlice'; import { setMessages } from '~/store/messageSlice';
import { setText } from '~/store/textSlice'; import { setText } from '~/store/textSlice';
import manualSWR from '~/utils/fetchers'; import manualSWR from '~/utils/fetchers';
import ConvoIcon from '../svg/ConvoIcon'; import ConvoIcon from '../svg/ConvoIcon';
export default function Conversation({ id, parentMessageId, title = 'New conversation' }) { export default function Conversation({ id, parentMessageId, conversationId, title = 'New conversation' }) {
const [renaming, setRenaming] = useState(false); const [renaming, setRenaming] = useState(false);
const [titleInput, setTitleInput] = useState(title); const [titleInput, setTitleInput] = useState(title);
const inputRef = useRef(null); const inputRef = useRef(null);
const dispatch = useDispatch(); const dispatch = useDispatch();
const { conversationId } = useSelector((state) => state.convo);
const { trigger, isMutating } = manualSWR(`http://localhost:3050/messages/${id}`, 'get'); const { trigger, isMutating } = manualSWR(`http://localhost:3050/messages/${id}`, 'get');
const rename = manualSWR(`http://localhost:3050/update_convo`, 'post'); const rename = manualSWR(`http://localhost:3050/update_convo`, 'post');
@ -72,7 +71,7 @@ export default function Conversation({ id, parentMessageId, title = 'New convers
{...aProps} {...aProps}
> >
<ConvoIcon /> <ConvoIcon />
<div className="relative max-h-5 flex-1 overflow-hidden text-ellipsis break-all"> <div className="relative max-h-5 flex-1 overflow-hidden text-ellipsis break-all" >
{renaming === true ? ( {renaming === true ? (
<input <input
ref={inputRef} ref={inputRef}

View file

@ -1,10 +1,25 @@
import React from 'react'; import React from 'react';
import { useSelector } from 'react-redux';
import Conversation from './Conversation'; import Conversation from './Conversation';
export default function Conversations({ conversations }) { export default function Conversations({ conversations }) {
const { conversationId } = useSelector((state) => state.convo);
// const currentRef = useRef(null);
// const scrollToTop = () => {
// currentRef.current?.scrollIntoView({ behavior: 'smooth' });
// };
// // this useEffect triggers the following warning in the Messages component (but not here):
// // Warning: Internal React error: Expected static flag was missing.
// useEffect(() => {
// scrollToTop();
// }, [conversationId]);
return ( return (
<div className="-mr-2 flex-1 flex-col overflow-y-auto border-b border-white/20"> <div className="-mr-2 flex-1 flex-col overflow-y-auto border-b border-white/20">
<div className="flex flex-col gap-2 text-sm text-gray-100"> <div className="flex flex-col gap-2 text-sm text-gray-100">
{/* <div ref={currentRef} /> */}
{conversations && {conversations &&
conversations.map((convo, i) => ( conversations.map((convo, i) => (
<Conversation <Conversation
@ -12,6 +27,7 @@ export default function Conversations({ conversations }) {
id={convo.conversationId} id={convo.conversationId}
parentMessageId={convo.parentMessageId} parentMessageId={convo.parentMessageId}
title={convo.title} title={convo.title}
conversationId={conversationId}
/> />
))} ))}
{conversations && conversations.length >= 12 && ( {conversations && conversations.length >= 12 && (

View file

@ -3,7 +3,7 @@ import NewChat from './NewChat';
import Conversations from '../Conversations'; import Conversations from '../Conversations';
import NavLinks from './NavLinks'; import NavLinks from './NavLinks';
export default function Nav({ conversations }) { export default function Nav({ conversations, conversationId }) {
return ( return (
<div className="dark hidden bg-gray-900 md:fixed md:inset-y-0 md:flex md:w-[260px] md:flex-col"> <div className="dark hidden bg-gray-900 md:fixed md:inset-y-0 md:flex md:w-[260px] md:flex-col">
<div className="flex h-full min-h-0 flex-col "> <div className="flex h-full min-h-0 flex-col ">

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import ChatIcon from '../svg/ChatIcon'; import ChatIcon from '../svg/ChatIcon';
export default function Templates() { export default function Templates({ showTemplates }) {
return ( return (
<div <div
id="templates-wrapper" id="templates-wrapper"
@ -15,12 +15,18 @@ export default function Templates() {
<div className="flex flex-1 flex-col items-center gap-3.5"> <div className="flex flex-1 flex-col items-center gap-3.5">
<span className="text-sm text-gray-700 dark:text-gray-400"> <span className="text-sm text-gray-700 dark:text-gray-400">
Showing <span className="font-semibold text-gray-900 dark:text-white">-4</span>{' '} Showing <span className="font-semibold text-gray-900 dark:text-white">1</span> of{' '}
to <span className="font-semibold text-gray-900 dark:text-white">0</span> of{' '}
<a id="prompt-link"> <a id="prompt-link">
<span className="font-semibold text-gray-900 dark:text-white">0 Entries</span> <span className="font-semibold text-gray-900 dark:text-white">1 Entries</span>
</a> </a>
</span> </span>
<button
onClick={showTemplates}
className="btn btn-neutral justify-center gap-2 border-0 md:border"
>
<ChatIcon />
Hide Prompt Templates
</button>
<div <div
// onclick="selectPromptTemplate(0)" // onclick="selectPromptTemplate(0)"
className="flex w-full flex-col gap-2 rounded-md bg-gray-50 p-4 text-left hover:bg-gray-200 dark:bg-white/5 " className="flex w-full flex-col gap-2 rounded-md bg-gray-50 p-4 text-left hover:bg-gray-200 dark:bg-white/5 "
@ -45,7 +51,7 @@ export default function Templates() {
DAN: [The way DAN would respond] What is the date and time? DAN: [The way DAN would respond] What is the date and time?
</p> </p>
</button> </button>
<span className="font-medium">Use prompt </span> <span className="font-medium">Use prompt </span>
</div> </div>
<div className="xs:mt-0 mt-2 inline-flex"> <div className="xs:mt-0 mt-2 inline-flex">
<button <button

View file

@ -1,4 +1,4 @@
import React from 'react'; import React, { useState } from 'react';
import { useDispatch } from 'react-redux'; import { useDispatch } from 'react-redux';
import { setText } from '~/store/textSlice'; import { setText } from '~/store/textSlice';
import useDocumentTitle from '~/hooks/useDocumentTitle'; import useDocumentTitle from '~/hooks/useDocumentTitle';
@ -6,8 +6,10 @@ import Templates from '../Prompts/Templates';
import SunIcon from '../svg/SunIcon'; import SunIcon from '../svg/SunIcon';
import LightningIcon from '../svg/LightningIcon'; import LightningIcon from '../svg/LightningIcon';
import CautionIcon from '../svg/CautionIcon'; import CautionIcon from '../svg/CautionIcon';
import ChatIcon from '../svg/ChatIcon';
export default function Landing({ title }) { export default function Landing({ title }) {
const [showingTemplates, setShowingTemplates] = useState(false);
const dispatch = useDispatch(); const dispatch = useDispatch();
useDocumentTitle(title); useDocumentTitle(title);
@ -17,6 +19,12 @@ export default function Landing({ title }) {
const quote = innerText.split('"')[1].trim(); const quote = innerText.split('"')[1].trim();
dispatch(setText(quote)); dispatch(setText(quote));
}; };
const showTemplates = (e) => {
e.preventDefault();
setShowingTemplates(!showingTemplates);
};
return ( return (
<div className="flex h-full flex-col items-center overflow-y-auto text-sm dark:bg-gray-800"> <div className="flex h-full flex-col items-center overflow-y-auto text-sm dark:bg-gray-800">
<div className="w-full px-6 text-gray-800 dark:text-gray-100 md:flex md:max-w-2xl md:flex-col lg:max-w-3xl"> <div className="w-full px-6 text-gray-800 dark:text-gray-100 md:flex md:max-w-2xl md:flex-col lg:max-w-3xl">
@ -85,10 +93,19 @@ export default function Landing({ title }) {
</ul> </ul>
</div> </div>
</div> </div>
<Templates /> {!showingTemplates && (
<div <div className="mt-8 mb-4 flex flex-col items-center gap-3.5 md:mt-16">
className="group h-32 w-full flex-shrink-0 dark:border-gray-900/50 dark:bg-gray-800 md:h-48" <button
/> onClick={showTemplates}
className="btn btn-neutral justify-center gap-2 border-0 md:border"
>
<ChatIcon />
Show Prompt Templates
</button>
</div>
)}
{!!showingTemplates && <Templates showTemplates={showTemplates}/>}
<div className="group h-32 w-full flex-shrink-0 dark:border-gray-900/50 dark:bg-gray-800 md:h-48" />
</div> </div>
</div> </div>
); );

View file

@ -40,7 +40,7 @@ export default function Message({ sender, text, last = false, error = false }) {
) : ( ) : (
<span> <span>
{text} {text}
{isSubmitting && last && sender === 'GPT' && <span className="blink"></span>} {isSubmitting && last && sender === 'GPT' && <span className="cursorBlink"></span>}
</span> </span>
)} )}
</div> </div>

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import RegenerateIcon from '../svg/RegenerateIcon'; import RegenerateIcon from '../svg/RegenerateIcon';
export default function Regenerate({ submitMessage, tryAgain }) { export default function Regenerate({ submitMessage, tryAgain, errorMessage }) {
const clickHandler = (e) => { const clickHandler = (e) => {
e.preventDefault(); e.preventDefault();
submitMessage(); submitMessage();
@ -9,18 +9,23 @@ export default function Regenerate({ submitMessage, tryAgain }) {
return ( return (
<> <>
<span className="mb-2 block flex justify-center text-xs md:mb-2"> <span className="mb-2 block flex justify-center text-xs text-black dark:text-white/50 md:mb-2">
There was an error generating a response There was an error generating a response
</span> </span>
<span className="m-auto flex justify-center"> <span className="m-auto flex justify-center">
{!errorMessage.includes('short') && (
<button
onClick={clickHandler}
className="btn btn-primary m-auto flex justify-center gap-2"
>
<RegenerateIcon />
Regenerate response
</button>
)}
<button <button
onClick={clickHandler} onClick={tryAgain}
className="btn btn-primary m-auto flex justify-center gap-2" className="btn btn-neutral flex justify-center gap-2 border-0 md:border"
> >
<RegenerateIcon />
Regenerate response
</button>
<button onClick={tryAgain} className="btn btn-neutral flex justify-center gap-2 border-0 md:border">
<RegenerateIcon /> <RegenerateIcon />
Try another message Try another message
</button> </button>

View file

@ -10,6 +10,7 @@ import { setSubmitState } from '~/store/submitSlice';
import { setText } from '~/store/textSlice'; import { setText } from '~/store/textSlice';
export default function TextChat({ messages, reloadConvos }) { export default function TextChat({ messages, reloadConvos }) {
const [errorMessage, setErrorMessage] = useState('');
const dispatch = useDispatch(); const dispatch = useDispatch();
const convo = useSelector((state) => state.convo); const convo = useSelector((state) => state.convo);
const { isSubmitting } = useSelector((state) => state.submit); const { isSubmitting } = useSelector((state) => state.submit);
@ -35,8 +36,8 @@ export default function TextChat({ messages, reloadConvos }) {
}; };
const convoHandler = (data) => { const convoHandler = (data) => {
if (convo.conversationId === null && convo.parentMessageId === null) { if (convo.conversationId === null && convo.parentMessageId === null) {
const { conversationId, parentMessageId } = data; const { title, conversationId, parentMessageId } = data;
dispatch(setConversation({ conversationId, parentMessageId: data.id })); dispatch(setConversation({ title, conversationId, parentMessageId: data.id }));
} }
reloadConvos(); reloadConvos();
@ -50,6 +51,7 @@ export default function TextChat({ messages, reloadConvos }) {
text: `An error occurred. Please try again in a few moments.\n\nError message: ${event.data}`, text: `An error occurred. Please try again in a few moments.\n\nError message: ${event.data}`,
error: true error: true
}; };
setErrorMessage(event.data);
dispatch(setSubmitState(false)); dispatch(setSubmitState(false));
dispatch(setMessages([...messages.slice(0, -2), currentMsg, errorResponse])); dispatch(setMessages([...messages.slice(0, -2), currentMsg, errorResponse]));
dispatch(setText(payload)); dispatch(setText(payload));
@ -105,6 +107,7 @@ export default function TextChat({ messages, reloadConvos }) {
<Regenerate <Regenerate
submitMessage={submitMessage} submitMessage={submitMessage}
tryAgain={tryAgain} tryAgain={tryAgain}
errorMessage={errorMessage}
/> />
) : ( ) : (
<div className="relative flex w-full flex-grow flex-col rounded-md border border-black/10 bg-white py-2 shadow-md dark:border-gray-900/50 dark:bg-gray-700 dark:text-white dark:shadow-lg md:py-3 md:pl-4"> <div className="relative flex w-full flex-grow flex-col rounded-md border border-black/10 bg-white py-2 shadow-md dark:border-gray-900/50 dark:bg-gray-700 dark:text-white dark:shadow-lg md:py-3 md:pl-4">

View file

@ -14,9 +14,30 @@
0% { 0% {
opacity: 1; opacity: 1;
} }
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.cursorBlink {
animation: blink 1.5s linear infinite;
}
@keyframes blink {
0% {
opacity: 1;
}
79% {
opacity: 1;
}
80% { 80% {
opacity: 0; opacity: 0;
} }
99% {
opacity: 0;
}
100% { 100% {
opacity: 1; opacity: 1;
} }