Convert the default conversation id to a uuid, plus other fixes (#918)

* Update the conversation_id primary key field to be a uuid

- update associated API endpoints
- this is to improve the overall application health, by obfuscating some information about the internal database
- conversation_id type is now implicitly a string, rather than an int
- ensure automations are also migrated in place, such that the conversation_ids they're pointing to are now mapped to the new IDs

* Update client-side API calls to correctly query with a string field

* Allow modifying of conversation properties from the chat title

* Improve drag and drop file experience for chat input area

* Use a phosphor icon for the copy to clipboard experience for code snippets

* Update conversation_id parameter to be a str type

* If django_apscheduler is not in the environment, skip the migration script

* Fix create automation flow by storing conversation id as string

The new UUID used for conversation id can't be directly serialized.
Convert to string for serializing it for later execution

---------

Co-authored-by: Debanjum Singh Solanky <debanjum@gmail.com>
This commit is contained in:
sabaimran
2024-09-24 14:12:50 -07:00
committed by GitHub
parent 0c936cecc0
commit 06777e1660
19 changed files with 213 additions and 66 deletions

View File

@@ -144,7 +144,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
let conversationFetchURL = "";
if (props.conversationId) {
conversationFetchURL = `/api/chat/history?client=web&conversation_id=${props.conversationId}&n=${10 * nextPage}`;
conversationFetchURL = `/api/chat/history?client=web&conversation_id=${encodeURIComponent(props.conversationId)}&n=${10 * nextPage}`;
} else if (props.publicConversationSlug) {
conversationFetchURL = `/api/chat/share/history?client=web&public_conversation_slug=${props.publicConversationSlug}&n=${10 * nextPage}`;
} else {

View File

@@ -442,7 +442,7 @@ export default function ChatInputArea(props: ChatInputProps) {
</div>
)}
<div
className={`${styles.actualInputArea} items-center justify-between dark:bg-neutral-700 relative`}
className={`${styles.actualInputArea} items-center justify-between dark:bg-neutral-700 relative ${isDragAndDropping && "animate-pulse"}`}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDragAndDropFiles}
@@ -547,7 +547,6 @@ export default function ChatInputArea(props: ChatInputProps) {
<ArrowUp className="w-6 h-6" weight="bold" />
</Button>
</div>
{isDragAndDropping && <div className="text-muted-foreground">Drop file to upload</div>}
</>
);
}

View File

@@ -5,6 +5,7 @@ import styles from "./chatMessage.module.css";
import markdownIt from "markdown-it";
import mditHljs from "markdown-it-highlightjs";
import React, { useEffect, useRef, useState } from "react";
import { createRoot } from "react-dom/client";
import "katex/dist/katex.min.css";
@@ -23,6 +24,7 @@ import {
MagnifyingGlass,
Pause,
Palette,
ClipboardText,
} from "@phosphor-icons/react";
import DOMPurify from "dompurify";
@@ -377,12 +379,9 @@ export default function ChatMessage(props: ChatMessageProps) {
const preElements = messageRef.current.querySelectorAll("pre > .hljs");
preElements.forEach((preElement) => {
const copyButton = document.createElement("button");
const copyImage = document.createElement("img");
copyImage.src = "/static/copy-button.svg";
copyImage.alt = "Copy";
copyImage.width = 24;
copyImage.height = 24;
copyButton.appendChild(copyImage);
const copyIcon = <ClipboardText size={24} weight="bold" />;
createRoot(copyButton).render(copyIcon);
copyButton.className = `hljs ${styles.codeCopyButton}`;
copyButton.addEventListener("click", () => {
let textContent = preElement.textContent || "";
@@ -392,7 +391,6 @@ export default function ChatMessage(props: ChatMessageProps) {
textContent = textContent.replace(/^Copy/, "");
textContent = textContent.trim();
navigator.clipboard.writeText(textContent);
copyImage.src = "/static/copy-button-success.svg";
});
preElement.prepend(copyButton);
});

View File

@@ -182,9 +182,12 @@ function FilesMenu(props: FilesMenuProps) {
useEffect(() => {
if (!files) return;
const uniqueFiles = Array.from(new Set(files));
// First, sort lexically
files.sort();
let sortedFiles = files;
uniqueFiles.sort();
let sortedFiles = uniqueFiles;
if (addedFiles) {
sortedFiles = addedFiles.concat(
@@ -458,12 +461,13 @@ function SessionsAndFiles(props: SessionsAndFilesProps) {
);
}
interface ChatSessionActionMenuProps {
export interface ChatSessionActionMenuProps {
conversationId: string;
setTitle: (title: string) => void;
sizing?: "sm" | "md" | "lg";
}
function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
export function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
const [renamedTitle, setRenamedTitle] = useState("");
const [isRenaming, setIsRenaming] = useState(false);
const [isSharing, setIsSharing] = useState(false);
@@ -596,10 +600,25 @@ function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
);
}
function sizeClass() {
switch (props.sizing) {
case "sm":
return "h-4 w-4";
case "md":
return "h-6 w-6";
case "lg":
return "h-8 w-8";
default:
return "h-4 w-4";
}
}
const size = sizeClass();
return (
<DropdownMenu onOpenChange={(open) => setIsOpen(open)} open={isOpen}>
<DropdownMenuTrigger>
<DotsThreeVertical className="h-4 w-4" />
<DotsThreeVertical className={`${size}`} />
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem>
@@ -608,7 +627,7 @@ function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
variant={"ghost"}
onClick={() => setIsRenaming(true)}
>
<Pencil className="mr-2 h-4 w-4" />
<Pencil className={`mr-2 ${size}`} />
Rename
</Button>
</DropdownMenuItem>
@@ -618,7 +637,7 @@ function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
variant={"ghost"}
onClick={() => setIsSharing(true)}
>
<Share className="mr-2 h-4 w-4" />
<Share className={`mr-2 ${size}`} />
Share
</Button>
</DropdownMenuItem>
@@ -628,7 +647,7 @@ function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
variant={"ghost"}
onClick={() => setIsDeleting(true)}
>
<Trash className="mr-2 h-4 w-4" />
<Trash className={`mr-2 ${size}`} />
Delete
</Button>
</DropdownMenuItem>
@@ -640,15 +659,14 @@ 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 =
new URLSearchParams(window.location.search).get("conversationId") || "-1";
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 === props.conversation_id && currConversationId != "-1" ? "dark:bg-neutral-800 bg-white" : ""}`}
>
<Link
href={`/chat?conversationId=${props.conversation_id}`}