mirror of
https://github.com/danny-avila/LibreChat.git
synced 2025-12-18 09:20:15 +01:00
tests scrolling to top, show/hide templates
This commit is contained in:
parent
5af5a97d8f
commit
7dd4e78bbf
11 changed files with 101 additions and 29 deletions
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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',
|
||||||
|
|
|
||||||
|
|
@ -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');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 && (
|
||||||
|
|
|
||||||
|
|
@ -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 ">
|
||||||
|
|
|
||||||
|
|
@ -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 "
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
|
|
@ -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,10 +9,11 @@ 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
|
<button
|
||||||
onClick={clickHandler}
|
onClick={clickHandler}
|
||||||
className="btn btn-primary m-auto flex justify-center gap-2"
|
className="btn btn-primary m-auto flex justify-center gap-2"
|
||||||
|
|
@ -20,7 +21,11 @@ export default function Regenerate({ submitMessage, tryAgain }) {
|
||||||
<RegenerateIcon />
|
<RegenerateIcon />
|
||||||
Regenerate response
|
Regenerate response
|
||||||
</button>
|
</button>
|
||||||
<button onClick={tryAgain} className="btn btn-neutral flex justify-center gap-2 border-0 md:border">
|
)}
|
||||||
|
<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>
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue