reset chat history

This commit is contained in:
timothycarambat 2024-02-01 16:20:44 -08:00
parent 1bdc4b9975
commit efc54be4ae
15 changed files with 160 additions and 89 deletions

View File

@ -3,7 +3,7 @@
<body>
<h1>This is an example testing page for embedded AnythingLLM.</h1>
<script data-embed-id="example-uuid" data-base-api-url='http://localhost:3001/api/embed'
<script data-embed-id="example-uuid" data-base-api-url='http://localhost:3001/api/embed' data-open-on-load="on"
src="/dist/embedded-anything-llm.umd.js">
</script>
</body>

View File

@ -12,7 +12,7 @@ export default function App() {
const sessionId = useSessionId();
useEffect(() => {
toggleOpenChat(embedSettings.openOnLoad === 'on')
toggleOpenChat(embedSettings.openOnLoad === "on");
}, [embedSettings.loaded]);
if (!embedSettings.loaded) return null;
@ -25,10 +25,11 @@ export default function App() {
width: isChatOpen ? 320 : "auto",
height: isChatOpen ? "93vh" : "auto",
}}
className={`transition-all duration-300 ease-in-out ${isChatOpen
? "max-w-md p-4 bg-white rounded-lg border shadow-lg w-72"
: "w-16 h-16 rounded-full"
}`}
className={`transition-all duration-300 ease-in-out ${
isChatOpen
? "max-w-md p-4 bg-white rounded-lg border shadow-lg w-72"
: "w-16 h-16 rounded-full"
}`}
>
{isChatOpen && (
<ChatWindow

View File

@ -46,11 +46,13 @@ export default function ChatHistory({ settings = {}, history = [] }) {
if (history.length === 0) {
return (
<div style={{ height: "85vh", paddingBottom: 100, paddingTop: 5 }}
className="bg-gray-100 rounded-lg px-2 h-full mt-2 gap-y-2 overflow-y-scroll flex flex-col justify-start no-scroll">
<div
style={{ height: "85vh", paddingBottom: 100, paddingTop: 5 }}
className="bg-gray-100 rounded-lg px-2 h-full mt-2 gap-y-2 overflow-y-scroll flex flex-col justify-start no-scroll"
>
<div className="flex h-full flex-col items-center justify-center">
<p className="text-slate-400 text-sm font-base py-4 text-center">
{settings?.greeting ?? 'Send a chat to get started!'}
{settings?.greeting ?? "Send a chat to get started!"}
</p>
</div>
</div>

View File

@ -1,4 +1,4 @@
import { useState, useEffect } from "react";
import React, { useState, useEffect } from "react";
import ChatHistory from "./ChatHistory";
import PromptInput from "./PromptInput";
import handleChat from "@/utils/chat";
@ -12,6 +12,14 @@ export default function ChatContainer({
const [message, setMessage] = useState("");
const [loadingResponse, setLoadingResponse] = useState(false);
const [chatHistory, setChatHistory] = useState(knownHistory);
// Resync history if the ref to known history changes
// eg: cleared.
useEffect(() => {
if (knownHistory.length !== chatHistory.length)
setChatHistory([...knownHistory]);
}, [knownHistory]);
const handleMessageChange = (event) => {
setMessage(event.target.value);
};
@ -68,7 +76,7 @@ export default function ChatContainer({
}, [loadingResponse, chatHistory]);
return (
<>
<React.Fragment>
<ChatHistory settings={settings} history={chatHistory} />
<PromptInput
settings={settings}
@ -78,6 +86,6 @@ export default function ChatContainer({
inputDisabled={loadingResponse}
buttonDisabled={loadingResponse}
/>
</>
</React.Fragment>
);
}

View File

@ -1,16 +1,66 @@
import AnythingLLMLogo from "@/assets/anything-llm-dark.png";
import { X } from "@phosphor-icons/react";
import ChatService from "@/models/chatService";
import { DotsThreeOutlineVertical, Lightning, X } from "@phosphor-icons/react";
import { useState } from "react";
export default function ChatWindowHeader({
sessionId,
settings = {},
iconUrl = null,
closeChat,
setChatHistory,
}) {
const [showingOptions, setShowOptions] = useState(false);
const handleChatReset = async () => {
await ChatService.resetEmbedChatSession(settings, sessionId);
setChatHistory([]);
setShowOptions(false);
};
export default function ChatWindowHeader({ iconUrl = null, closeChat }) {
return (
<div className="flex justify-between items-center">
<div className="flex justify-between items-center relative">
<img
style={{ maxWidth: 100, maxHeight: 20 }}
src={iconUrl ?? AnythingLLMLogo}
alt={iconUrl ? "Brand" : "AnythingLLM Logo"}
/>
<button onClick={closeChat} className="text-xl font-bold">
<X size={18} />
<div className="flex gap-x-1 items-center">
{settings.loaded && (
<button
type="button"
onClick={() => setShowOptions(!showingOptions)}
className="hover:bg-gray-100 rounded-sm text-slate-800"
>
<DotsThreeOutlineVertical
size={18}
weight={!showingOptions ? "regular" : "fill"}
/>
</button>
)}
<button
type="button"
onClick={closeChat}
className="hover:bg-gray-100 rounded-sm text-slate-800"
>
<X size={18} />
</button>
</div>
<OptionsMenu showing={showingOptions} resetChat={handleChatReset} />
</div>
);
}
function OptionsMenu({ showing, resetChat }) {
if (!showing) return null;
return (
<div className="absolute bg-white flex flex-col gap-y-2 rounded-lg shadow-lg border border-gray-300 top-[3vh] right-[1vw] max-w-[150px]">
<button
onClick={resetChat}
className="flex items-center gap-x-1 hover:bg-gray-100 text-sm text-gray-700 p-2 rounded-lg"
>
<Lightning size={14} />
<p>Reset Chat</p>
</button>
</div>
);

View File

@ -4,20 +4,21 @@ import useChatHistory from "@/hooks/chat/useChatHistory";
import ChatContainer from "./ChatContainer";
export default function ChatWindow({ closeChat, settings, sessionId }) {
const { chatHistory, loading } = useChatHistory(settings, sessionId);
const { chatHistory, setChatHistory, loading } = useChatHistory(
settings,
sessionId
);
if (loading)
return (
<div>
<p>loading...</p>
</div>
);
if (loading) return null;
setEventDelegatorForCodeSnippets();
return (
<div className="flex flex-col">
<ChatWindowHeader
sessionId={sessionId}
settings={settings}
iconUrl={settings.brandImageUrl}
closeChat={closeChat}
setChatHistory={setChatHistory}
/>
<ChatContainer
sessionId={sessionId}

View File

@ -5,8 +5,6 @@ export default function SessionId() {
if (!sessionId) return null;
return (
<div className="text-xs text-gray-300 w-full text-center">
{sessionId}
</div>
<div className="text-xs text-gray-300 w-full text-center">{sessionId}</div>
);
}

View File

@ -1,8 +1,5 @@
import ChatService from "@/models/chatService";
import { useEffect, useState } from "react";
import { v4 } from "uuid";
const historyURL = ({ embedId, baseApiUrl }, sessionId) =>
`${baseApiUrl}/${embedId}/${sessionId}`;
export default function useChatHistory(settings = null, sessionId = null) {
const [loading, setLoading] = useState(true);
@ -12,24 +9,10 @@ export default function useChatHistory(settings = null, sessionId = null) {
async function fetchChatHistory() {
if (!sessionId || !settings) return;
try {
const formattedMessages = await fetch(historyURL(settings, sessionId))
.then((res) => {
if (res.ok) return res.json();
throw new Error("Invalid response from server");
})
.then((res) => {
return res.history.map((msg) => ({
...msg,
id: v4(),
sender: msg.role === "user" ? "user" : "system",
textResponse: msg.content,
close: false,
}));
})
.catch((e) => {
console.error(e);
return [];
});
const formattedMessages = await ChatService.embedSessionHistory(
settings,
sessionId
);
setMessages(formattedMessages);
setLoading(false);
} catch (error) {

View File

@ -16,7 +16,7 @@ const DEFAULT_SETTINGS = {
greeting: null, // empty chat window greeting.
// behaviors
openOnLoad: 'off', // or "on"
openOnLoad: "off", // or "on"
};
export default function useGetScriptAttributes() {

View File

@ -2,6 +2,35 @@ import { fetchEventSource } from "@microsoft/fetch-event-source";
import { v4 } from "uuid";
const ChatService = {
embedSessionHistory: async function (embedSettings, sessionId) {
const { embedId, baseApiUrl } = embedSettings;
return await fetch(`${baseApiUrl}/${embedId}/${sessionId}`)
.then((res) => {
if (res.ok) return res.json();
throw new Error("Invalid response from server");
})
.then((res) => {
return res.history.map((msg) => ({
...msg,
id: v4(),
sender: msg.role === "user" ? "user" : "system",
textResponse: msg.content,
close: false,
}));
})
.catch((e) => {
console.error(e);
return [];
});
},
resetEmbedChatSession: async function (embedSettings, sessionId) {
const { baseApiUrl, embedId } = embedSettings;
return await fetch(`${baseApiUrl}/${embedId}/${sessionId}`, {
method: "DELETE",
})
.then((res) => res.ok)
.catch(() => false);
},
streamChat: async function (sessionId, embedSettings, message, handleChat) {
const { baseApiUrl, embedId } = embedSettings;
const overrides = {

View File

@ -10,7 +10,7 @@
"node": ">=18"
},
"scripts": {
"lint": "cd server && yarn lint && cd ../frontend && yarn lint && cd ../collector && yarn lint",
"lint": "cd server && yarn lint && cd ../frontend && yarn lint && cd ../embed && yarn lint && cd ../collector && yarn lint",
"setup": "cd server && yarn && cd ../collector && yarn && cd ../frontend && yarn && cd .. && yarn setup:envs && yarn prisma:setup && echo \"Please run yarn dev:server, yarn dev:collector, and yarn dev:frontend in separate terminal tabs.\"",
"setup:envs": "cp -n ./frontend/.env.example ./frontend/.env && cp -n ./server/.env.example ./server/.env.development && cp -n ./collector/.env.example ./collector/.env && cp -n ./docker/.env.example ./docker/.env && echo \"All ENV files copied!\n\"",
"dev:server": "cd server && yarn dev",

View File

@ -79,6 +79,23 @@ function embeddedEndpoints(app) {
}
}
);
app.delete(
"/embed/:embedId/:sessionId",
[validEmbedConfig],
async (request, response) => {
try {
const { sessionId } = request.params;
const embed = response.locals.embedConfig;
await EmbedChats.markHistoryInvalid(embed.id, sessionId);
response.status(200).end();
} catch (e) {
console.log(e.message, e);
response.sendStatus(500).end();
}
}
);
}
module.exports = { embeddedEndpoints };

View File

@ -38,6 +38,7 @@ const EmbedChats = {
where: {
embed_id: embedId,
session_id: sessionId,
include: true,
},
...(limit !== null ? { take: limit } : {}),
...(orderBy !== null ? { orderBy } : { orderBy: { id: "asc" } }),
@ -49,45 +50,24 @@ const EmbedChats = {
}
},
// forWorkspace: async function (
// workspaceId = null,
// limit = null,
// orderBy = null
// ) {
// if (!workspaceId) return [];
// try {
// const chats = await prisma.embed_chats.findMany({
// where: {
// workspaceId,
// include: true,
// },
// ...(limit !== null ? { take: limit } : {}),
// ...(orderBy !== null ? { orderBy } : { orderBy: { id: "asc" } }),
// });
// return chats;
// } catch (error) {
// console.error(error.message);
// return [];
// }
// },
markHistoryInvalid: async function (embedId = null, sessionId = null) {
if (!embedId || !sessionId) return [];
// markHistoryInvalid: async function (workspaceId = null, user = null) {
// if (!workspaceId) return;
// try {
// await prisma.embed_chats.updateMany({
// where: {
// workspaceId,
// user_id: user?.id,
// },
// data: {
// include: false,
// },
// });
// return;
// } catch (error) {
// console.error(error.message);
// }
// },
try {
await prisma.embed_chats.updateMany({
where: {
embed_id: embedId,
session_id: sessionId,
},
data: {
include: false,
},
});
return;
} catch (error) {
console.error(error.message);
}
},
get: async function (clause = {}, limit = null, orderBy = null) {
try {

View File

@ -24,6 +24,7 @@ CREATE TABLE "embed_chats" (
"prompt" TEXT NOT NULL,
"response" TEXT NOT NULL,
"session_id" TEXT NOT NULL,
"include" BOOLEAN NOT NULL DEFAULT true,
"connection_information" TEXT,
"embed_id" INTEGER NOT NULL,
"usersId" INTEGER,

View File

@ -160,6 +160,7 @@ model embed_chats {
prompt String
response String
session_id String
include Boolean @default(true)
connection_information String?
embed_id Int
usersId Int?