Rebased concurrent chats (#2214)

* refactored for stop / regenerate

* properly reset blank screen

* functional new message carry-over

* robust chat session state persistence

* add env variable

* rebased onto regenerate

* squash

* squash

* squash

* rebase + robustify tool calling

* squash

* alembic

* remove environment variable

* simplify interface

* squash

* minor streaming improvement

* some robustification
This commit is contained in:
pablodanswer 2024-08-26 19:57:31 -07:00 committed by GitHub
parent a873fc6483
commit 5f12b7ad58
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 297 additions and 158 deletions

View File

@ -43,13 +43,20 @@ import {
uploadFilesForChat,
useScrollonStream,
} from "./lib";
import { useContext, useEffect, useRef, useState } from "react";
import {
Dispatch,
SetStateAction,
useContext,
useEffect,
useRef,
useState,
} from "react";
import { usePopup } from "@/components/admin/connectors/Popup";
import { SEARCH_PARAM_NAMES, shouldSubmitOnLoad } from "./searchParams";
import { useDocumentSelection } from "./useDocumentSelection";
import { LlmOverride, useFilters, useLlmOverride } from "@/lib/hooks";
import { computeAvailableFilters } from "@/lib/filters";
import { ChatState, FeedbackType } from "./types";
import { ChatState, FeedbackType, RegenerationState } from "./types";
import { DocumentSidebar } from "./documentSidebar/DocumentSidebar";
import { DanswerInitializingLoader } from "@/components/DanswerInitializingLoader";
import { FeedbackModal } from "./modal/FeedbackModal";
@ -84,6 +91,7 @@ import { SetDefaultModelModal } from "./modal/SetDefaultModelModal";
import { DeleteChatModal } from "./modal/DeleteChatModal";
import { MinimalMarkdown } from "@/components/chat_search/MinimalMarkdown";
import ExceptionTraceModal from "@/components/modals/ExceptionTraceModal";
import { SEARCH_TOOL_NAME } from "./tools/constants";
import { useUser } from "@/components/user/UserProvider";
@ -212,10 +220,18 @@ export function ChatPage({
}
}, [liveAssistant]);
const stopGeneration = () => {
if (abortController) {
abortController.abort();
const stopGenerating = () => {
const currentSession = currentSessionId();
const controller = abortControllers.get(currentSession);
if (controller) {
controller.abort();
setAbortControllers((prev) => {
const newControllers = new Map(prev);
newControllers.delete(currentSession);
return newControllers;
});
}
const lastMessage = messageHistory[messageHistory.length - 1];
if (
lastMessage &&
@ -223,16 +239,16 @@ export function ChatPage({
lastMessage.toolCalls[0] &&
lastMessage.toolCalls[0].tool_result === undefined
) {
const newCompleteMessageMap = new Map(completeMessageDetail.messageMap);
const newCompleteMessageMap = new Map(
currentMessageMap(completeMessageDetail)
);
const updatedMessage = { ...lastMessage, toolCalls: [] };
newCompleteMessageMap.set(lastMessage.messageId, updatedMessage);
setCompleteMessageDetail({
sessionId: completeMessageDetail.sessionId,
messageMap: newCompleteMessageMap,
});
updateCompleteMessageDetail(currentSession, newCompleteMessageMap);
}
};
updateChatState("input", currentSession);
};
// this is for "@"ing assistants
// this is used to track which assistant is being used to generate the current message
@ -308,10 +324,7 @@ export function ChatPage({
} else {
setSelectedAssistant(undefined);
}
setCompleteMessageDetail({
sessionId: null,
messageMap: new Map(),
});
updateCompleteMessageDetail(null, new Map());
setChatSessionSharedStatus(ChatSessionSharedStatus.Private);
// if we're supposed to submit on initial load, then do that here
@ -341,13 +354,11 @@ export function ChatPage({
// This corresponds to a "renaming" of chat, which occurs after first message
// stream
if (
messageHistory[messageHistory.length - 1]?.type !== "error" ||
loadedSessionId != null
(messageHistory[messageHistory.length - 1]?.type !== "error" ||
loadedSessionId != null) &&
!currentChatAnswering()
) {
setCompleteMessageDetail({
sessionId: chatSession.chat_session_id,
messageMap: newMessageMap,
});
updateCompleteMessageDetail(chatSession.chat_session_id, newMessageMap);
const latestMessageId =
newMessageHistory[newMessageHistory.length - 1]?.messageId;
@ -394,10 +405,31 @@ export function ChatPage({
searchParams.get(SEARCH_PARAM_NAMES.USER_MESSAGE) || ""
);
const [completeMessageDetail, setCompleteMessageDetail] = useState<{
sessionId: number | null;
messageMap: Map<number, Message>;
}>({ sessionId: null, messageMap: new Map() });
const [completeMessageDetail, setCompleteMessageDetail] = useState<
Map<number | null, Map<number, Message>>
>(new Map());
const updateCompleteMessageDetail = (
sessionId: number | null,
messageMap: Map<number, Message>
) => {
setCompleteMessageDetail((prevState) => {
const newState = new Map(prevState);
newState.set(sessionId, messageMap);
return newState;
});
};
const currentMessageMap = (
messageDetail: Map<number | null, Map<number, Message>>
) => {
return (
messageDetail.get(chatSessionIdRef.current) || new Map<number, Message>()
);
};
const currentSessionId = (): number => {
return chatSessionIdRef.current!;
};
const upsertToCompleteMessageMap = ({
messages,
@ -416,7 +448,7 @@ export function ChatPage({
}) => {
// deep copy
const frozenCompleteMessageMap =
completeMessageMapOverride || completeMessageDetail.messageMap;
completeMessageMapOverride || currentMessageMap(completeMessageDetail);
const newCompleteMessageMap = structuredClone(frozenCompleteMessageMap);
if (newCompleteMessageMap.size === 0) {
@ -466,30 +498,134 @@ export function ChatPage({
)!.latestChildMessageId = messages[0].messageId;
}
}
const newCompleteMessageDetail = {
sessionId: chatSessionId || completeMessageDetail.sessionId,
sessionId: chatSessionId || currentSessionId(),
messageMap: newCompleteMessageMap,
};
setCompleteMessageDetail(newCompleteMessageDetail);
updateCompleteMessageDetail(
chatSessionId || currentSessionId(),
newCompleteMessageMap
);
return newCompleteMessageDetail;
};
const messageHistory = buildLatestMessageChain(
completeMessageDetail.messageMap
currentMessageMap(completeMessageDetail)
);
const [submittedMessage, setSubmittedMessage] = useState("");
const [chatState, setChatState] = useState<ChatState>("input");
interface RegenerationState {
regenerating: boolean;
finalMessageIndex: number;
}
const [regenerationState, setRegenerationState] =
useState<RegenerationState | null>(null);
const [chatState, setChatState] = useState<Map<number | null, ChatState>>(
new Map([[chatSessionIdRef.current, "input"]])
);
const [abortController, setAbortController] =
useState<AbortController | null>(null);
const [scrollHeight, setScrollHeight] = useState<Map<number | null, number>>(
new Map([[chatSessionIdRef.current, 0]])
);
const currentScrollHeight = () => {
return scrollHeight.get(currentSessionId());
};
const retrieveCurrentScrollHeight = (): number | null => {
return scrollHeight.get(currentSessionId()) || null;
};
const [regenerationState, setRegenerationState] = useState<
Map<number | null, RegenerationState | null>
>(new Map([[null, null]]));
const [abortControllers, setAbortControllers] = useState<
Map<number | null, AbortController>
>(new Map());
// Updates "null" session values to new session id for
// regeneration, chat, and abort controller state, messagehistory
const updateStatesWithNewSessionId = (newSessionId: number) => {
const updateState = (
setState: Dispatch<SetStateAction<Map<number | null, any>>>,
defaultValue?: any
) => {
setState((prevState) => {
const newState = new Map(prevState);
const existingState = newState.get(null);
if (existingState !== undefined) {
newState.set(newSessionId, existingState);
newState.delete(null);
} else if (defaultValue !== undefined) {
newState.set(newSessionId, defaultValue);
}
return newState;
});
};
updateState(setRegenerationState);
updateState(setChatState);
updateState(setAbortControllers);
// Update completeMessageDetail
setCompleteMessageDetail((prevState) => {
const newState = new Map(prevState);
const existingMessages = newState.get(null);
if (existingMessages) {
newState.set(newSessionId, existingMessages);
newState.delete(null);
}
return newState;
});
// Update chatSessionIdRef
chatSessionIdRef.current = newSessionId;
};
const updateChatState = (newState: ChatState, sessionId?: number | null) => {
setChatState((prevState) => {
const newChatState = new Map(prevState);
newChatState.set(
sessionId !== undefined ? sessionId : currentSessionId(),
newState
);
return newChatState;
});
};
const currentChatState = (): ChatState => {
return chatState.get(currentSessionId()) || "input";
};
const currentChatAnswering = () => {
return (
currentChatState() == "toolBuilding" ||
currentChatState() == "streaming" ||
currentChatState() == "loading"
);
};
const updateRegenerationState = (
newState: RegenerationState | null,
sessionId?: number | null
) => {
setRegenerationState((prevState) => {
const newRegenerationState = new Map(prevState);
newRegenerationState.set(
sessionId !== undefined ? sessionId : currentSessionId(),
newState
);
return newRegenerationState;
});
};
const resetRegenerationState = (sessionId?: number | null) => {
updateRegenerationState(null, sessionId);
};
const currentRegenerationState = (): RegenerationState | null => {
return regenerationState.get(currentSessionId()) || null;
};
const currentSessionChatState = currentChatState();
const currentSessionRegenerationState = currentRegenerationState();
// uploaded files
const [currentMessageFiles, setCurrentMessageFiles] = useState<
@ -746,7 +882,9 @@ export function ChatPage({
modelOverRide?: LlmOverride;
regenerationRequest?: RegenerationRequest | null;
} = {}) => {
if (chatState != "input") {
let frozenSessionId = currentSessionId();
if (currentChatState() != "input") {
setPopup({
message: "Please wait for the response to complete",
type: "error",
@ -754,16 +892,13 @@ export function ChatPage({
return;
}
setRegenerationState(
updateRegenerationState(
regenerationRequest
? { regenerating: true, finalMessageIndex: messageIdToResend || 0 }
: null
);
setChatState("loading");
const controller = new AbortController();
setAbortController(controller);
updateChatState("loading");
setAlternativeGeneratingAssistant(alternativeAssistantOverride);
clientScrollToBottom();
@ -780,13 +915,21 @@ export function ChatPage({
} else {
currChatSessionId = chatSessionIdRef.current as number;
}
chatSessionIdRef.current = currChatSessionId;
frozenSessionId = currChatSessionId;
updateStatesWithNewSessionId(currChatSessionId);
const controller = new AbortController();
setAbortControllers((prev) =>
new Map(prev).set(currChatSessionId, controller)
);
const messageToResend = messageHistory.find(
(message) => message.messageId === messageIdToResend
);
const messageMap = completeMessageDetail.messageMap;
const messageMap = currentMessageMap(completeMessageDetail);
const messageToResendParent =
messageToResend?.parentMessageId !== null &&
messageToResend?.parentMessageId !== undefined
@ -802,8 +945,8 @@ export function ChatPage({
"Failed to re-send message - please refresh the page and try again.",
type: "error",
});
setRegenerationState(null);
setChatState("input");
resetRegenerationState(currentSessionId());
updateChatState("input", frozenSessionId);
return;
}
let currMessage = messageToResend ? messageToResend.message : message;
@ -851,11 +994,12 @@ export function ChatPage({
user_message_id: number;
assistant_message_id: number;
frozenMessageMap: Map<number, Message>;
frozenSessionId: number | null;
} = null;
try {
const mapKeys = Array.from(completeMessageDetail.messageMap.keys());
const mapKeys = Array.from(
currentMessageMap(completeMessageDetail).keys()
);
const systemMessage = Math.min(...mapKeys);
const lastSuccessfulMessageId =
@ -956,32 +1100,31 @@ export function ChatPage({
});
}
const {
messageMap: currentFrozenMessageMap,
sessionId: currentFrozenSessionId,
} = upsertToCompleteMessageMap({
messages: messageUpdates,
chatSessionId: currChatSessionId,
});
const { messageMap: currentFrozenMessageMap } =
upsertToCompleteMessageMap({
messages: messageUpdates,
chatSessionId: currChatSessionId,
});
const frozenMessageMap = currentFrozenMessageMap;
const frozenSessionId = currentFrozenSessionId;
initialFetchDetails = {
frozenMessageMap,
frozenSessionId,
assistant_message_id,
user_message_id,
};
setRegenerationState(null);
resetRegenerationState();
} else {
const { user_message_id, frozenMessageMap, frozenSessionId } =
initialFetchDetails;
setChatState((chatState) => {
if (chatState == "loading") {
return "streaming";
const { user_message_id, frozenMessageMap } = initialFetchDetails;
setChatState((prevState) => {
if (prevState.get(chatSessionIdRef.current!) === "loading") {
return new Map(prevState).set(
chatSessionIdRef.current!,
"streaming"
);
}
return chatState;
return prevState;
});
if (Object.hasOwn(packet, "answer_piece")) {
@ -1006,9 +1149,9 @@ export function ChatPage({
!toolCalls[0].tool_result ||
toolCalls[0].tool_result == undefined
) {
setChatState("toolBuilding");
updateChatState("toolBuilding", frozenSessionId);
} else {
setChatState("streaming");
updateChatState("streaming", frozenSessionId);
}
// This will be consolidated in upcoming tool calls udpate,
@ -1123,11 +1266,12 @@ export function ChatPage({
initialFetchDetails?.user_message_id || TEMP_USER_MESSAGE_ID,
},
],
completeMessageMapOverride: completeMessageDetail.messageMap,
completeMessageMapOverride: currentMessageMap(completeMessageDetail),
});
}
setRegenerationState(null);
setChatState("input");
resetRegenerationState(currentSessionId());
updateChatState("input");
if (isNewSession) {
if (finalMessage) {
setSelectedMessageForDocDisplay(finalMessage.message_id);
@ -1194,8 +1338,8 @@ export function ChatPage({
const onAssistantChange = (assistant: Persona | null) => {
if (assistant && assistant.id !== liveAssistant.id) {
// Abort the ongoing stream if it exists
if (chatState != "input") {
stopGeneration();
if (currentSessionChatState != "input") {
stopGenerating();
resetInputBar();
}
@ -1308,7 +1452,7 @@ export function ChatPage({
});
useScrollonStream({
chatState,
chatState: currentSessionChatState,
scrollableDivRef,
scrollDist,
endDivRef,
@ -1496,7 +1640,7 @@ export function ChatPage({
<div className="w-full relative">
<HistorySidebar
explicitlyUntoggle={explicitlyUntoggle}
stopGenerating={stopGeneration}
stopGenerating={stopGenerating}
reset={() => setMessage("")}
page="chat"
ref={innerSidebarElementRef}
@ -1570,7 +1714,7 @@ export function ChatPage({
{messageHistory.length === 0 &&
!isFetchingChatMessages &&
chatState == "input" && (
currentSessionChatState == "input" && (
<ChatIntro
availableSources={finalAvailableSources}
selectedPersona={liveAssistant}
@ -1584,17 +1728,17 @@ export function ChatPage({
}
>
{messageHistory.map((message, i) => {
const messageMap =
completeMessageDetail.messageMap;
const messageReactComponentKey = `${i}-${completeMessageDetail.sessionId}`;
const messageMap = currentMessageMap(
completeMessageDetail
);
const messageReactComponentKey = `${i}-${currentSessionId()}`;
const parentMessage = message.parentMessageId
? messageMap.get(message.parentMessageId)
: null;
if (
regenerationState &&
regenerationState.regenerating &&
message.messageId >
regenerationState.finalMessageIndex
currentSessionRegenerationState?.regenerating &&
message.messageId >=
currentSessionRegenerationState?.finalMessageIndex!
) {
return <></>;
}
@ -1603,7 +1747,7 @@ export function ChatPage({
return (
<div key={messageReactComponentKey}>
<HumanMessage
stopGenerating={stopGeneration}
stopGenerating={stopGenerating}
content={message.message}
files={message.files}
messageId={message.messageId}
@ -1636,11 +1780,10 @@ export function ChatPage({
newCompleteMessageMap.get(
message.parentMessageId!
)!.latestChildMessageId = messageId;
setCompleteMessageDetail({
sessionId:
completeMessageDetail.sessionId,
messageMap: newCompleteMessageMap,
});
updateCompleteMessageDetail(
currentSessionId(),
newCompleteMessageMap
);
setSelectedMessageForDocDisplay(
messageId
);
@ -1670,11 +1813,9 @@ export function ChatPage({
: null;
if (
regenerationState &&
regenerationState.regenerating &&
// chatState == "loading" &&
message.messageId >
regenerationState.finalMessageIndex - 1
currentSessionRegenerationState?.regenerating &&
currentSessionChatState == "loading" &&
message.messageId == messageHistory.length - 1
) {
return <></>;
}
@ -1703,11 +1844,12 @@ export function ChatPage({
newCompleteMessageMap.get(
message.parentMessageId!
)!.latestChildMessageId = messageId;
setCompleteMessageDetail({
sessionId:
completeMessageDetail.sessionId,
messageMap: newCompleteMessageMap,
});
updateCompleteMessageDetail(
currentSessionId(),
newCompleteMessageMap
);
setSelectedMessageForDocDisplay(
messageId
);
@ -1742,7 +1884,10 @@ export function ChatPage({
}
isComplete={
i !== messageHistory.length - 1 ||
chatState == "input"
(currentSessionChatState !=
"streaming" &&
currentSessionChatState !=
"toolBuilding")
}
hasDocs={
(message.documents &&
@ -1750,7 +1895,7 @@ export function ChatPage({
}
handleFeedback={
i === messageHistory.length - 1 &&
chatState != "input"
currentSessionChatState != "input"
? undefined
: (feedbackType) =>
setCurrentFeedback([
@ -1760,7 +1905,7 @@ export function ChatPage({
}
handleSearchQueryEdit={
i === messageHistory.length - 1 &&
chatState == "input"
currentSessionChatState == "input"
? (newQuery) => {
if (!previousMessage) {
setPopup({
@ -1770,7 +1915,6 @@ export function ChatPage({
});
return;
}
if (
previousMessage.messageId ===
null
@ -1867,21 +2011,24 @@ export function ChatPage({
);
}
})}
{chatState == "loading" &&
!regenerationState?.regenerating &&
{currentSessionChatState == "loading" &&
!currentSessionRegenerationState?.regenerating &&
messageHistory[messageHistory.length - 1]?.type !=
"user" && (
<HumanMessage
key={-2}
messageId={-1}
content={submittedMessage}
/>
)}
{chatState == "loading" && (
{currentSessionChatState == "loading" && (
<div
key={`${messageHistory.length}-${chatSessionIdRef.current}`}
>
<AIMessage
key={-3}
currentPersona={liveAssistant}
alternativeAssistant={
alternativeGeneratingAssistant ??
@ -1910,6 +2057,7 @@ export function ChatPage({
messageHistory.length === 0 &&
!isFetchingChatMessages && (
<div
key={-4}
className={`
mx-auto
px-4
@ -1961,10 +2109,9 @@ export function ChatPage({
</button>
</div>
)}
<ChatInputBar
chatState={chatState}
stopGenerating={stopGeneration}
chatState={currentSessionChatState}
stopGenerating={stopGenerating}
openModelSettings={() => setSettingsToggled(true)}
inputPrompts={userInputPrompts}
showDocs={() => setDocumentSelection(true)}

View File

@ -32,9 +32,8 @@ import { AssistantIcon } from "@/components/assistants/AssistantIcon";
import { Tooltip } from "@/components/tooltip/Tooltip";
import { Hoverable } from "@/components/Hoverable";
import { SettingsContext } from "@/components/settings/SettingsProvider";
import { StopCircle } from "@phosphor-icons/react/dist/ssr";
import { Square } from "@phosphor-icons/react";
import { ChatState } from "../types";
const MAX_INPUT_HEIGHT = 200;
export function ChatInputBar({

View File

@ -231,14 +231,9 @@ export const AIMessage = ({
}
return content;
};
content = trimIncompleteCodeSection(content);
}
const danswerSearchToolEnabledForPersona = currentPersona.tools.some(
(tool) => tool.in_code_tool_id === SEARCH_TOOL_NAME
);
let filteredDocs: FilteredDanswerDocument[] = [];
if (docs) {
@ -760,24 +755,24 @@ export const HumanMessage = ({
<textarea
ref={textareaRef}
className={`
m-0
w-full
h-auto
shrink
border-0
rounded-lg
overflow-y-hidden
bg-background-emphasis
whitespace-normal
break-word
overscroll-contain
outline-none
placeholder-gray-400
resize-none
pl-4
overflow-y-auto
pr-12
py-4`}
m-0
w-full
h-auto
shrink
border-0
rounded-lg
overflow-y-hidden
bg-background-emphasis
whitespace-normal
break-word
overscroll-contain
outline-none
placeholder-gray-400
resize-none
pl-4
overflow-y-auto
pr-12
py-4`}
aria-multiline
role="textarea"
value={editedContent}
@ -901,7 +896,7 @@ export const HumanMessage = ({
) : (
<div className="h-[27px]" />
)}
<p className="ml-auto rounded-lg p-1">{content}</p>
<div className="ml-auto rounded-lg p-1">{content}</div>
</>
)}
</div>

View File

@ -32,7 +32,6 @@ export function ChatSessionDisplay({
isSelected,
skipGradient,
closeSidebar,
stopGenerating = () => null,
showShareModal,
showDeleteModal,
}: {
@ -43,7 +42,6 @@ export function ChatSessionDisplay({
// if not set, the gradient will still be applied and cause weirdness
skipGradient?: boolean;
closeSidebar?: () => void;
stopGenerating?: () => void;
showShareModal?: (chatSession: ChatSession) => void;
showDeleteModal?: (chatSession: ChatSession) => void;
}) {
@ -100,7 +98,6 @@ export function ChatSessionDisplay({
className="flex my-1 group relative"
key={chatSession.id}
onClick={() => {
stopGenerating();
if (settings?.isMobile && closeSidebar) {
closeSidebar();
}

View File

@ -176,7 +176,6 @@ export const HistorySidebar = forwardRef<HTMLDivElement, HistorySidebarProps>(
)}
<div className="border-b border-border pb-4 mx-3" />
<PagesTab
stopGenerating={stopGenerating}
newFolderId={newFolderId}
showDeleteModal={showDeleteModal}
showShareModal={showShareModal}

View File

@ -17,12 +17,10 @@ export function PagesTab({
folders,
openedFolders,
closeSidebar,
stopGenerating,
newFolderId,
showShareModal,
showDeleteModal,
}: {
stopGenerating: () => void;
page: pageType;
existingChats?: ChatSession[];
currentChatId?: number;
@ -126,7 +124,6 @@ export function PagesTab({
return (
<div key={`${chat.id}-${chat.name}`}>
<ChatSessionDisplay
stopGenerating={stopGenerating}
showDeleteModal={showDeleteModal}
showShareModal={showShareModal}
closeSidebar={closeSidebar}

View File

@ -1,2 +1,6 @@
export type FeedbackType = "like" | "dislike";
export type ChatState = "input" | "loading" | "streaming" | "toolBuilding";
export interface RegenerationState {
regenerating: boolean;
finalMessageIndex: number;
}

View File

@ -659,9 +659,9 @@ export const MagnifyingIcon = ({
>
<path
fill="currentColor"
fill-rule="evenodd"
fillRule="evenodd"
d="M9.965 11.026a5 5 0 1 1 1.06-1.06l2.755 2.754a.75.75 0 1 1-1.06 1.06zM10.5 7a3.5 3.5 0 1 1-7 0a3.5 3.5 0 0 1 7 0"
clip-rule="evenodd"
clipRule="evenodd"
/>
</svg>
);
@ -682,9 +682,9 @@ export const ToggleDown = ({
>
<path
fill="currentColor"
fill-rule="evenodd"
fillRule="evenodd"
d="M4.22 6.22a.75.75 0 0 1 1.06 0L8 8.94l2.72-2.72a.75.75 0 1 1 1.06 1.06l-3.25 3.25a.75.75 0 0 1-1.06 0L4.22 7.28a.75.75 0 0 1 0-1.06"
clip-rule="evenodd"
clipRule="evenodd"
/>
</svg>
);
@ -705,9 +705,9 @@ export const ToggleUp = ({
>
<path
fill="currentColor"
fill-rule="evenodd"
fillRule="evenodd"
d="M11.78 9.78a.75.75 0 0 1-1.06 0L8 7.06L5.28 9.78a.75.75 0 0 1-1.06-1.06l3.25-3.25a.75.75 0 0 1 1.06 0l3.25 3.25a.75.75 0 0 1 0 1.06"
clip-rule="evenodd"
clipRule="evenodd"
/>
</svg>
);
@ -773,7 +773,7 @@ export const StarFeedback = ({
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
stroke-width="1.5"
strokeWidth="1.5"
d="m12.495 18.587l4.092 2.15a1.044 1.044 0 0 0 1.514-1.106l-.783-4.552a1.045 1.045 0 0 1 .303-.929l3.31-3.226a1.043 1.043 0 0 0-.575-1.785l-4.572-.657A1.044 1.044 0 0 1 15 7.907l-2.088-4.175a1.044 1.044 0 0 0-1.88 0L8.947 7.907a1.044 1.044 0 0 1-.783.575l-4.51.657a1.044 1.044 0 0 0-.584 1.785l3.309 3.226a1.044 1.044 0 0 1 .303.93l-.783 4.55a1.044 1.044 0 0 0 1.513 1.107l4.093-2.15a1.043 1.043 0 0 1 .991 0"
/>
</svg>
@ -798,7 +798,7 @@ export const DislikeFeedback = ({
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
stroke-width="1.5"
strokeWidth="1.5"
>
<path d="M5.75 2.75H4.568c-.98 0-1.775.795-1.775 1.776v8.284c0 .98.795 1.775 1.775 1.775h1.184c.98 0 1.775-.794 1.775-1.775V4.526c0-.98-.795-1.776-1.775-1.776" />
<path d="m21.16 11.757l-1.42-7.101a2.368 2.368 0 0 0-2.367-1.906h-7.48a2.367 2.367 0 0 0-2.367 2.367v7.101a3.231 3.231 0 0 0 1.184 2.367l.982 5.918a.887.887 0 0 0 1.278.65l1.1-.543a3.551 3.551 0 0 0 1.87-4.048l-.496-1.965h5.396a2.368 2.368 0 0 0 2.32-2.84" />
@ -825,7 +825,7 @@ export const LikeFeedback = ({
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
stroke-width="1.5"
strokeWidth="1.5"
>
<path d="M5.75 9.415H4.568c-.98 0-1.775.794-1.775 1.775v8.284c0 .98.795 1.776 1.775 1.776h1.184c.98 0 1.775-.795 1.775-1.776V11.19c0-.98-.795-1.775-1.775-1.775" />
<path d="m21.16 12.243l-1.42 7.101a2.367 2.367 0 0 1-2.367 1.906h-7.48a2.367 2.367 0 0 1-2.367-2.367v-7.101A3.231 3.231 0 0 1 8.71 9.415l.982-5.918a.888.888 0 0 1 1.278-.65l1.1.544a3.55 3.55 0 0 1 1.87 4.047l-.496 1.965h5.396a2.367 2.367 0 0 1 2.32 2.84" />
@ -1834,9 +1834,9 @@ export const FilledLikeIcon = ({
>
<path
fill="currentColor"
fill-rule="evenodd"
fillRule="evenodd"
d="M4.41 12.961a2.5 2.5 0 0 0 1.076.244h5.346a2.5 2.5 0 0 0 2.47-2.114l.626-4.003a2 2 0 0 0-1.976-2.31H8.67V2.422a1.625 1.625 0 0 0-3.044-.794l-2.077 3.71a1.5 1.5 0 0 0-.191.733v5.442a1.5 1.5 0 0 0 .854 1.354l.2.095Zm-3.366-7.44a.996.996 0 0 0-.997.996v5.112a.997.997 0 0 0 .997.997h.496a.5.5 0 0 0 .5-.5V6.02a.5.5 0 0 0-.5-.5h-.496Z"
clip-rule="evenodd"
clipRule="evenodd"
/>
</svg>
);
@ -1857,9 +1857,9 @@ export const StopGeneratingIcon = ({
>
<path
fill="currentColor"
fill-rule="evenodd"
fillRule="evenodd"
d="M1.5 0A1.5 1.5 0 0 0 0 1.5v11A1.5 1.5 0 0 0 1.5 14h11a1.5 1.5 0 0 0 1.5-1.5v-11A1.5 1.5 0 0 0 12.5 0z"
clip-rule="evenodd"
clipRule="evenodd"
/>
</svg>
);
@ -2604,7 +2604,7 @@ export const SwapIcon = ({
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
stroke-width="1.5"
strokeWidth="1.5"
>
<path d="M3.53 11.47v2.118a4.235 4.235 0 0 0 4.235 4.236H20.47M3.53 6.176h12.705a4.235 4.235 0 0 1 4.236 4.236v2.117" />
<path d="m17.294 14.647l3.177 3.176L17.294 21M6.706 9.353L3.529 6.176L6.706 3" />
@ -2655,7 +2655,7 @@ export const PinIcon = ({
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
stroke-width="1.5"
strokeWidth="1.5"
d="m17.942 6.076l2.442 2.442a1.22 1.22 0 0 1-.147 1.855l-1.757.232a1.697 1.697 0 0 0-.94.452c-.72.696-1.453 1.428-2.674 2.637c-.21.212-.358.478-.427.769l-.94 3.772a1.22 1.22 0 0 1-1.978.379l-3.04-3.052l-3.052-3.04a1.221 1.221 0 0 1 .379-1.978l3.747-.964a1.8 1.8 0 0 0 .77-.44c1.379-1.355 1.88-1.855 2.66-2.698c.233-.25.383-.565.428-.903l.232-1.783a1.221 1.221 0 0 1 1.856-.146zm-9.51 9.498L3.256 20.75"
/>
</svg>
@ -2680,7 +2680,7 @@ export const TwoRightArrowIcons = ({
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
stroke-width="1.5"
strokeWidth="1.5"
d="m5.36 19l5.763-5.763a1.738 1.738 0 0 0 0-2.474L5.36 5m7 14l5.763-5.763a1.738 1.738 0 0 0 0-2.474L12.36 5"
/>
</svg>

View File

@ -126,6 +126,7 @@ export async function fetchChatData(searchParams: {
`Failed to fetch chat sessions - ${chatSessionsResponse?.text()}`
);
}
// Larger ID -> created later
chatSessions.sort((a, b) => (a.id > b.id ? -1 : 1));