mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-04 21:29:12 +00:00
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:
@@ -31,7 +31,7 @@ input.inputBox:focus {
|
||||
}
|
||||
|
||||
div.inputBox:focus {
|
||||
box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2);
|
||||
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
div.chatBodyFull {
|
||||
@@ -94,6 +94,9 @@ div.agentIndicator {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
div.chatTitleWrapper {
|
||||
grid-template-columns: auto 1fr;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
div.inputBox {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import styles from "./chat.module.css";
|
||||
import React, { Suspense, useEffect, useState } from "react";
|
||||
|
||||
import SidePanel from "../components/sidePanel/chatHistorySidePanel";
|
||||
import SidePanel, { ChatSessionActionMenu } from "../components/sidePanel/chatHistorySidePanel";
|
||||
import ChatHistory from "../components/chatHistory/chatHistory";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import Loading from "../components/loading/loading";
|
||||
@@ -17,6 +17,8 @@ import { useIPLocationData, useIsMobileWidth, welcomeConsole } from "../common/u
|
||||
import ChatInputArea, { ChatOptions } from "../components/chatInputArea/chatInputArea";
|
||||
import { useAuthenticatedData } from "../common/auth";
|
||||
import { AgentData } from "../agents/page";
|
||||
import { DotsThreeVertical } from "@phosphor-icons/react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
interface ChatBodyDataProps {
|
||||
chatOptionsData: ChatOptions | null;
|
||||
@@ -104,7 +106,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`${styles.inputBox} p-1 md:px-2 shadow-md bg-background align-middle items-center justify-center dark:bg-neutral-700 dark:border-0 dark:shadow-sm rounded-t-2xl rounded-b-none md:rounded-xl`}
|
||||
className={`${styles.inputBox} p-1 md:px-2 shadow-md bg-background align-middle items-center justify-center dark:bg-neutral-700 dark:border-0 dark:shadow-sm rounded-t-2xl rounded-b-none md:rounded-xl h-fit`}
|
||||
>
|
||||
<ChatInputArea
|
||||
agentColor={agentMetadata?.color}
|
||||
@@ -133,6 +135,7 @@ export default function Chat() {
|
||||
const [processQuerySignal, setProcessQuerySignal] = useState(false);
|
||||
const [uploadedFiles, setUploadedFiles] = useState<string[]>([]);
|
||||
const [image64, setImage64] = useState<string>("");
|
||||
|
||||
const locationData = useIPLocationData();
|
||||
const authenticatedData = useAuthenticatedData();
|
||||
const isMobileWidth = useIsMobileWidth();
|
||||
@@ -235,7 +238,7 @@ export default function Chat() {
|
||||
const chatAPI = "/api/chat?client=web";
|
||||
const chatAPIBody = {
|
||||
q: queryToProcess,
|
||||
conversation_id: parseInt(conversationId),
|
||||
conversation_id: conversationId,
|
||||
stream: true,
|
||||
...(locationData && {
|
||||
region: locationData.region,
|
||||
@@ -297,17 +300,22 @@ export default function Chat() {
|
||||
</div>
|
||||
<div className={styles.chatBox}>
|
||||
<div className={styles.chatBoxBody}>
|
||||
{!isMobileWidth && (
|
||||
{!isMobileWidth && conversationId && (
|
||||
<div
|
||||
className={`text-nowrap text-ellipsis overflow-hidden max-w-screen-md grid items-top font-bold mr-8`}
|
||||
className={`${styles.chatTitleWrapper} text-nowrap text-ellipsis overflow-hidden max-w-screen-md grid items-top font-bold mr-8 pt-6 col-auto h-fit`}
|
||||
>
|
||||
{title && (
|
||||
<h2
|
||||
className={`text-lg text-ellipsis whitespace-nowrap overflow-x-hidden pt-6`}
|
||||
className={`text-lg text-ellipsis whitespace-nowrap overflow-x-hidden`}
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
)}
|
||||
<ChatSessionActionMenu
|
||||
conversationId={conversationId}
|
||||
setTitle={setTitle}
|
||||
sizing="md"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<Suspense fallback={<Loading />}>
|
||||
|
||||
@@ -177,7 +177,6 @@ export function modifyFileFilterForConversation(
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log("ADDEDFILES DATA: ", data);
|
||||
setAddedFiles(data);
|
||||
})
|
||||
.catch((err) => {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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}`}
|
||||
|
||||
@@ -225,7 +225,7 @@ export default function SharedChat() {
|
||||
const chatAPI = "/api/chat?client=web";
|
||||
const chatAPIBody = {
|
||||
q: queryToProcess,
|
||||
conversation_id: parseInt(conversationId),
|
||||
conversation_id: conversationId,
|
||||
stream: true,
|
||||
...(locationData && {
|
||||
region: locationData.region,
|
||||
|
||||
Reference in New Issue
Block a user