Add a unique_id field for identifiying conversations (#914)

* Add a unique_id field to the conversation object

- This helps us keep track of the unique identity of the conversation without expose the internal id
- Create three staged migrations in order to first add the field, then add unique values to pre-fill, and then set the unique constraint. Without this, it tries to initialize all the existing conversations with the same ID.

* Parse and utilize the unique_id field in the query parameters of the front-end view

- Handle the unique_id field when creating a new conversation from the home page
- Parse the id field with a lightweight parameter called v in the chat page
- Share page should not be affected, as it uses the public slug

* Fix suggested card category
This commit is contained in:
sabaimran
2024-09-16 12:19:16 -07:00
committed by GitHub
parent e6bc7a2ba2
commit ece2ec2d90
11 changed files with 169 additions and 20 deletions

View File

@@ -32,7 +32,8 @@ interface ChatBodyDataProps {
function ChatBodyData(props: ChatBodyDataProps) {
const searchParams = useSearchParams();
const conversationId = searchParams.get("conversationId");
const conversationUniqueId = searchParams.get("v");
const [conversationId, setConversationId] = useState<string | null>("");
const [message, setMessage] = useState("");
const [image, setImage] = useState<string | null>(null);
const [processingMessage, setProcessingMessage] = useState(false);
@@ -60,6 +61,11 @@ function ChatBodyData(props: ChatBodyDataProps) {
setProcessingMessage(true);
setQueryToProcess(storedMessage);
}
const conversationId = localStorage.getItem("conversationId");
if (conversationId) {
setConversationId(conversationId);
}
}, [setQueryToProcess]);
useEffect(() => {
@@ -69,6 +75,30 @@ function ChatBodyData(props: ChatBodyDataProps) {
}
}, [message, setQueryToProcess]);
useEffect(() => {
if (!conversationUniqueId) {
return;
}
fetch(
`/api/chat/metadata?conversation_unique_id=${encodeURIComponent(conversationUniqueId)}`,
)
.then((response) => {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json();
})
.then((data) => {
setConversationId(data.conversationId);
})
.catch((err) => {
console.error(err);
setConversationId(null);
return;
});
});
useEffect(() => {
if (conversationId) {
onConversationIdChange?.(conversationId);
@@ -87,11 +117,15 @@ function ChatBodyData(props: ChatBodyDataProps) {
}
}, [props.streamedMessages]);
if (!conversationId) {
if (!conversationUniqueId || conversationId === null) {
window.location.href = "/";
return;
}
if (!conversationId) {
return <Loading />;
}
return (
<>
<div className={false ? styles.chatBody : styles.chatBodyFull}>

View File

@@ -186,6 +186,11 @@ export function modifyFileFilterForConversation(
});
}
interface NewConversationMetadata {
conversationId: string;
conversationUniqueId: string;
}
export async function createNewConversation(slug: string) {
try {
const response = await fetch(`/api/chat/sessions?client=web&agent_slug=${slug}`, {
@@ -194,9 +199,11 @@ export async function createNewConversation(slug: string) {
if (!response.ok)
throw new Error(`Failed to fetch chat sessions with status: ${response.status}`);
const data = await response.json();
const conversationID = data.conversation_id;
if (!conversationID) throw new Error("Conversation ID not found in response");
return conversationID;
const uniqueId = data.unique_id;
const conversationId = data.conversation_id;
if (!uniqueId) throw new Error("Unique ID not found in response");
if (!conversationId) throw new Error("Conversation ID not found in response");
return { conversationId, conversationUniqueId: uniqueId } as NewConversationMetadata;
} catch (error) {
console.error("Error creating new conversation:", error);
throw error;

View File

@@ -67,7 +67,9 @@ interface ChatHistory {
compressed: boolean;
created: string;
updated: string;
unique_id: string;
showSidePanel: (isEnabled: boolean) => void;
selectedConversationId: string | null;
}
import {
@@ -398,6 +400,7 @@ interface SessionsAndFilesProps {
conversationId: string | null;
uploadedFiles: string[];
isMobileWidth: boolean;
selectedConversationId: string | null;
}
function SessionsAndFiles(props: SessionsAndFilesProps) {
@@ -435,6 +438,10 @@ function SessionsAndFiles(props: SessionsAndFilesProps) {
agent_avatar={chatHistory.agent_avatar}
agent_name={chatHistory.agent_name}
showSidePanel={props.setEnabled}
unique_id={chatHistory.unique_id}
selectedConversationId={
props.selectedConversationId
}
/>
),
)}
@@ -446,6 +453,7 @@ function SessionsAndFiles(props: SessionsAndFilesProps) {
<ChatSessionsModal
data={props.organizedData}
showSidePanel={props.setEnabled}
selectedConversationId={props.selectedConversationId}
/>
)}
</div>
@@ -640,20 +648,18 @@ function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
function ChatSession(props: ChatHistory) {
const [isHovered, setIsHovered] = useState(false);
const [title, setTitle] = useState(props.slug || "New Conversation 🌱");
var currConversationId = parseInt(
new URLSearchParams(window.location.search).get("conversationId") || "-1",
);
var currConversationId =
props.conversation_id &&
props.selectedConversationId &&
parseInt(props.conversation_id) === parseInt(props.selectedConversationId);
return (
<div
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
key={props.conversation_id}
className={`${styles.session} ${props.compressed ? styles.compressed : "!max-w-full"} ${isHovered ? `${styles.sessionHover}` : ""} ${currConversationId === parseInt(props.conversation_id) && currConversationId != -1 ? "dark:bg-neutral-800 bg-white" : ""}`}
className={`${styles.session} ${props.compressed ? styles.compressed : "!max-w-full"} ${isHovered ? `${styles.sessionHover}` : ""} ${currConversationId ? "dark:bg-neutral-800 bg-white" : ""}`}
>
<Link
href={`/chat?conversationId=${props.conversation_id}`}
onClick={() => props.showSidePanel(false)}
>
<Link href={`/chat?v=${props.unique_id}`} onClick={() => props.showSidePanel(false)}>
<p className={styles.session}>{title}</p>
</Link>
<ChatSessionActionMenu conversationId={props.conversation_id} setTitle={setTitle} />
@@ -664,9 +670,14 @@ function ChatSession(props: ChatHistory) {
interface ChatSessionsModalProps {
data: GroupedChatHistory | null;
showSidePanel: (isEnabled: boolean) => void;
selectedConversationId: string | null;
}
function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) {
function ChatSessionsModal({
data,
showSidePanel,
selectedConversationId,
}: ChatSessionsModalProps) {
return (
<Dialog>
<DialogTrigger className="flex text-left text-medium text-gray-500 hover:text-gray-300 cursor-pointer my-4 text-sm p-[0.5rem]">
@@ -698,6 +709,8 @@ function ChatSessionsModal({ data, showSidePanel }: ChatSessionsModalProps) {
agent_avatar={chatHistory.agent_avatar}
agent_name={chatHistory.agent_name}
showSidePanel={showSidePanel}
unique_id={chatHistory.unique_id}
selectedConversationId={selectedConversationId}
/>
))}
</div>
@@ -819,6 +832,7 @@ export default function SidePanel(props: SidePanelProps) {
userProfile={authenticatedData}
conversationId={props.conversationId}
isMobileWidth={props.isMobileWidth}
selectedConversationId={props.conversationId}
/>
</div>
) : (
@@ -887,6 +901,7 @@ export default function SidePanel(props: SidePanelProps) {
userProfile={authenticatedData}
conversationId={props.conversationId}
isMobileWidth={props.isMobileWidth}
selectedConversationId={props.conversationId}
/>
</div>
)}

View File

@@ -408,7 +408,7 @@ export const suggestionsData: Suggestion[] = [
link: "",
},
{
type: SuggestionType.Code,
type: SuggestionType.Interviewing,
color: suggestionToColorMap[SuggestionType.Interviewing] || DEFAULT_COLOR,
description: "Provide tips for writing an effective resume.",
link: "",

View File

@@ -138,10 +138,13 @@ function ChatBodyData(props: ChatBodyDataProps) {
if (message && !processingMessage) {
setProcessingMessage(true);
try {
const newConversationId = await createNewConversation(selectedAgent || "khoj");
onConversationIdChange?.(newConversationId);
window.location.href = `/chat?conversationId=${newConversationId}`;
const newConversationMetadata = await createNewConversation(
selectedAgent || "khoj",
);
onConversationIdChange?.(newConversationMetadata.conversationId);
window.location.href = `/chat?v=${newConversationMetadata.conversationUniqueId}`;
localStorage.setItem("message", message);
localStorage.setItem("conversationId", newConversationMetadata.conversationId);
if (image) {
localStorage.setItem("image", image);
}