mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-09 21:29:11 +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:
@@ -152,7 +152,7 @@
|
|||||||
const chatApi = `${hostURL}/api/chat?client=desktop`;
|
const chatApi = `${hostURL}/api/chat?client=desktop`;
|
||||||
const chatApiBody = {
|
const chatApiBody = {
|
||||||
q: query,
|
q: query,
|
||||||
conversation_id: parseInt(conversationID),
|
conversation_id: conversationID,
|
||||||
stream: true,
|
stream: true,
|
||||||
...(!!city && { city: city }),
|
...(!!city && { city: city }),
|
||||||
...(!!region && { region: region }),
|
...(!!region && { region: region }),
|
||||||
|
|||||||
@@ -405,7 +405,7 @@
|
|||||||
const chatApi = `${hostURL}/api/chat?client=desktop`;
|
const chatApi = `${hostURL}/api/chat?client=desktop`;
|
||||||
const chatApiBody = {
|
const chatApiBody = {
|
||||||
q: query,
|
q: query,
|
||||||
conversation_id: parseInt(conversationID),
|
conversation_id: conversationID,
|
||||||
stream: true,
|
stream: true,
|
||||||
...(!!city && { city: city }),
|
...(!!city && { city: city }),
|
||||||
...(!!region && { region: region }),
|
...(!!region && { region: region }),
|
||||||
|
|||||||
@@ -1055,7 +1055,7 @@ export class KhojChatView extends KhojPaneView {
|
|||||||
q: query,
|
q: query,
|
||||||
n: this.setting.resultsCount,
|
n: this.setting.resultsCount,
|
||||||
stream: true,
|
stream: true,
|
||||||
...(!!conversationId && { conversation_id: parseInt(conversationId) }),
|
...(!!conversationId && { conversation_id: conversationId }),
|
||||||
...(!!this.location && {
|
...(!!this.location && {
|
||||||
city: this.location.city,
|
city: this.location.city,
|
||||||
region: this.location.region,
|
region: this.location.region,
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ input.inputBox:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
div.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 {
|
div.chatBodyFull {
|
||||||
@@ -94,6 +94,9 @@ div.agentIndicator {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.chatTitleWrapper {
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
@media screen and (max-width: 768px) {
|
@media screen and (max-width: 768px) {
|
||||||
div.inputBox {
|
div.inputBox {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import styles from "./chat.module.css";
|
import styles from "./chat.module.css";
|
||||||
import React, { Suspense, useEffect, useState } from "react";
|
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 ChatHistory from "../components/chatHistory/chatHistory";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import Loading from "../components/loading/loading";
|
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 ChatInputArea, { ChatOptions } from "../components/chatInputArea/chatInputArea";
|
||||||
import { useAuthenticatedData } from "../common/auth";
|
import { useAuthenticatedData } from "../common/auth";
|
||||||
import { AgentData } from "../agents/page";
|
import { AgentData } from "../agents/page";
|
||||||
|
import { DotsThreeVertical } from "@phosphor-icons/react";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
|
||||||
interface ChatBodyDataProps {
|
interface ChatBodyDataProps {
|
||||||
chatOptionsData: ChatOptions | null;
|
chatOptionsData: ChatOptions | null;
|
||||||
@@ -104,7 +106,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<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
|
<ChatInputArea
|
||||||
agentColor={agentMetadata?.color}
|
agentColor={agentMetadata?.color}
|
||||||
@@ -133,6 +135,7 @@ export default function Chat() {
|
|||||||
const [processQuerySignal, setProcessQuerySignal] = useState(false);
|
const [processQuerySignal, setProcessQuerySignal] = useState(false);
|
||||||
const [uploadedFiles, setUploadedFiles] = useState<string[]>([]);
|
const [uploadedFiles, setUploadedFiles] = useState<string[]>([]);
|
||||||
const [image64, setImage64] = useState<string>("");
|
const [image64, setImage64] = useState<string>("");
|
||||||
|
|
||||||
const locationData = useIPLocationData();
|
const locationData = useIPLocationData();
|
||||||
const authenticatedData = useAuthenticatedData();
|
const authenticatedData = useAuthenticatedData();
|
||||||
const isMobileWidth = useIsMobileWidth();
|
const isMobileWidth = useIsMobileWidth();
|
||||||
@@ -235,7 +238,7 @@ export default function Chat() {
|
|||||||
const chatAPI = "/api/chat?client=web";
|
const chatAPI = "/api/chat?client=web";
|
||||||
const chatAPIBody = {
|
const chatAPIBody = {
|
||||||
q: queryToProcess,
|
q: queryToProcess,
|
||||||
conversation_id: parseInt(conversationId),
|
conversation_id: conversationId,
|
||||||
stream: true,
|
stream: true,
|
||||||
...(locationData && {
|
...(locationData && {
|
||||||
region: locationData.region,
|
region: locationData.region,
|
||||||
@@ -297,17 +300,22 @@ export default function Chat() {
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.chatBox}>
|
<div className={styles.chatBox}>
|
||||||
<div className={styles.chatBoxBody}>
|
<div className={styles.chatBoxBody}>
|
||||||
{!isMobileWidth && (
|
{!isMobileWidth && conversationId && (
|
||||||
<div
|
<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 && (
|
{title && (
|
||||||
<h2
|
<h2
|
||||||
className={`text-lg text-ellipsis whitespace-nowrap overflow-x-hidden pt-6`}
|
className={`text-lg text-ellipsis whitespace-nowrap overflow-x-hidden`}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
</h2>
|
</h2>
|
||||||
)}
|
)}
|
||||||
|
<ChatSessionActionMenu
|
||||||
|
conversationId={conversationId}
|
||||||
|
setTitle={setTitle}
|
||||||
|
sizing="md"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Suspense fallback={<Loading />}>
|
<Suspense fallback={<Loading />}>
|
||||||
|
|||||||
@@ -177,7 +177,6 @@ export function modifyFileFilterForConversation(
|
|||||||
})
|
})
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log("ADDEDFILES DATA: ", data);
|
|
||||||
setAddedFiles(data);
|
setAddedFiles(data);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
|
|||||||
@@ -144,7 +144,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
|
|||||||
let conversationFetchURL = "";
|
let conversationFetchURL = "";
|
||||||
|
|
||||||
if (props.conversationId) {
|
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) {
|
} else if (props.publicConversationSlug) {
|
||||||
conversationFetchURL = `/api/chat/share/history?client=web&public_conversation_slug=${props.publicConversationSlug}&n=${10 * nextPage}`;
|
conversationFetchURL = `/api/chat/share/history?client=web&public_conversation_slug=${props.publicConversationSlug}&n=${10 * nextPage}`;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -442,7 +442,7 @@ export default function ChatInputArea(props: ChatInputProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<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}
|
onDragOver={handleDragOver}
|
||||||
onDragLeave={handleDragLeave}
|
onDragLeave={handleDragLeave}
|
||||||
onDrop={handleDragAndDropFiles}
|
onDrop={handleDragAndDropFiles}
|
||||||
@@ -547,7 +547,6 @@ export default function ChatInputArea(props: ChatInputProps) {
|
|||||||
<ArrowUp className="w-6 h-6" weight="bold" />
|
<ArrowUp className="w-6 h-6" weight="bold" />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</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 markdownIt from "markdown-it";
|
||||||
import mditHljs from "markdown-it-highlightjs";
|
import mditHljs from "markdown-it-highlightjs";
|
||||||
import React, { useEffect, useRef, useState } from "react";
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
|
||||||
import "katex/dist/katex.min.css";
|
import "katex/dist/katex.min.css";
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ import {
|
|||||||
MagnifyingGlass,
|
MagnifyingGlass,
|
||||||
Pause,
|
Pause,
|
||||||
Palette,
|
Palette,
|
||||||
|
ClipboardText,
|
||||||
} from "@phosphor-icons/react";
|
} from "@phosphor-icons/react";
|
||||||
|
|
||||||
import DOMPurify from "dompurify";
|
import DOMPurify from "dompurify";
|
||||||
@@ -377,12 +379,9 @@ export default function ChatMessage(props: ChatMessageProps) {
|
|||||||
const preElements = messageRef.current.querySelectorAll("pre > .hljs");
|
const preElements = messageRef.current.querySelectorAll("pre > .hljs");
|
||||||
preElements.forEach((preElement) => {
|
preElements.forEach((preElement) => {
|
||||||
const copyButton = document.createElement("button");
|
const copyButton = document.createElement("button");
|
||||||
const copyImage = document.createElement("img");
|
const copyIcon = <ClipboardText size={24} weight="bold" />;
|
||||||
copyImage.src = "/static/copy-button.svg";
|
createRoot(copyButton).render(copyIcon);
|
||||||
copyImage.alt = "Copy";
|
|
||||||
copyImage.width = 24;
|
|
||||||
copyImage.height = 24;
|
|
||||||
copyButton.appendChild(copyImage);
|
|
||||||
copyButton.className = `hljs ${styles.codeCopyButton}`;
|
copyButton.className = `hljs ${styles.codeCopyButton}`;
|
||||||
copyButton.addEventListener("click", () => {
|
copyButton.addEventListener("click", () => {
|
||||||
let textContent = preElement.textContent || "";
|
let textContent = preElement.textContent || "";
|
||||||
@@ -392,7 +391,6 @@ export default function ChatMessage(props: ChatMessageProps) {
|
|||||||
textContent = textContent.replace(/^Copy/, "");
|
textContent = textContent.replace(/^Copy/, "");
|
||||||
textContent = textContent.trim();
|
textContent = textContent.trim();
|
||||||
navigator.clipboard.writeText(textContent);
|
navigator.clipboard.writeText(textContent);
|
||||||
copyImage.src = "/static/copy-button-success.svg";
|
|
||||||
});
|
});
|
||||||
preElement.prepend(copyButton);
|
preElement.prepend(copyButton);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -182,9 +182,12 @@ function FilesMenu(props: FilesMenuProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!files) return;
|
if (!files) return;
|
||||||
|
|
||||||
|
const uniqueFiles = Array.from(new Set(files));
|
||||||
|
|
||||||
// First, sort lexically
|
// First, sort lexically
|
||||||
files.sort();
|
uniqueFiles.sort();
|
||||||
let sortedFiles = files;
|
|
||||||
|
let sortedFiles = uniqueFiles;
|
||||||
|
|
||||||
if (addedFiles) {
|
if (addedFiles) {
|
||||||
sortedFiles = addedFiles.concat(
|
sortedFiles = addedFiles.concat(
|
||||||
@@ -458,12 +461,13 @@ function SessionsAndFiles(props: SessionsAndFilesProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ChatSessionActionMenuProps {
|
export interface ChatSessionActionMenuProps {
|
||||||
conversationId: string;
|
conversationId: string;
|
||||||
setTitle: (title: string) => void;
|
setTitle: (title: string) => void;
|
||||||
|
sizing?: "sm" | "md" | "lg";
|
||||||
}
|
}
|
||||||
|
|
||||||
function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
|
export function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
|
||||||
const [renamedTitle, setRenamedTitle] = useState("");
|
const [renamedTitle, setRenamedTitle] = useState("");
|
||||||
const [isRenaming, setIsRenaming] = useState(false);
|
const [isRenaming, setIsRenaming] = useState(false);
|
||||||
const [isSharing, setIsSharing] = 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 (
|
return (
|
||||||
<DropdownMenu onOpenChange={(open) => setIsOpen(open)} open={isOpen}>
|
<DropdownMenu onOpenChange={(open) => setIsOpen(open)} open={isOpen}>
|
||||||
<DropdownMenuTrigger>
|
<DropdownMenuTrigger>
|
||||||
<DotsThreeVertical className="h-4 w-4" />
|
<DotsThreeVertical className={`${size}`} />
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
@@ -608,7 +627,7 @@ function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
|
|||||||
variant={"ghost"}
|
variant={"ghost"}
|
||||||
onClick={() => setIsRenaming(true)}
|
onClick={() => setIsRenaming(true)}
|
||||||
>
|
>
|
||||||
<Pencil className="mr-2 h-4 w-4" />
|
<Pencil className={`mr-2 ${size}`} />
|
||||||
Rename
|
Rename
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
@@ -618,7 +637,7 @@ function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
|
|||||||
variant={"ghost"}
|
variant={"ghost"}
|
||||||
onClick={() => setIsSharing(true)}
|
onClick={() => setIsSharing(true)}
|
||||||
>
|
>
|
||||||
<Share className="mr-2 h-4 w-4" />
|
<Share className={`mr-2 ${size}`} />
|
||||||
Share
|
Share
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
@@ -628,7 +647,7 @@ function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
|
|||||||
variant={"ghost"}
|
variant={"ghost"}
|
||||||
onClick={() => setIsDeleting(true)}
|
onClick={() => setIsDeleting(true)}
|
||||||
>
|
>
|
||||||
<Trash className="mr-2 h-4 w-4" />
|
<Trash className={`mr-2 ${size}`} />
|
||||||
Delete
|
Delete
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
@@ -640,15 +659,14 @@ function ChatSessionActionMenu(props: ChatSessionActionMenuProps) {
|
|||||||
function ChatSession(props: ChatHistory) {
|
function ChatSession(props: ChatHistory) {
|
||||||
const [isHovered, setIsHovered] = useState(false);
|
const [isHovered, setIsHovered] = useState(false);
|
||||||
const [title, setTitle] = useState(props.slug || "New Conversation 🌱");
|
const [title, setTitle] = useState(props.slug || "New Conversation 🌱");
|
||||||
var currConversationId = parseInt(
|
var currConversationId =
|
||||||
new URLSearchParams(window.location.search).get("conversationId") || "-1",
|
new URLSearchParams(window.location.search).get("conversationId") || "-1";
|
||||||
);
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onMouseEnter={() => setIsHovered(true)}
|
onMouseEnter={() => setIsHovered(true)}
|
||||||
onMouseLeave={() => setIsHovered(false)}
|
onMouseLeave={() => setIsHovered(false)}
|
||||||
key={props.conversation_id}
|
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
|
<Link
|
||||||
href={`/chat?conversationId=${props.conversation_id}`}
|
href={`/chat?conversationId=${props.conversation_id}`}
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ export default function SharedChat() {
|
|||||||
const chatAPI = "/api/chat?client=web";
|
const chatAPI = "/api/chat?client=web";
|
||||||
const chatAPIBody = {
|
const chatAPIBody = {
|
||||||
q: queryToProcess,
|
q: queryToProcess,
|
||||||
conversation_id: parseInt(conversationId),
|
conversation_id: conversationId,
|
||||||
stream: true,
|
stream: true,
|
||||||
...(locationData && {
|
...(locationData && {
|
||||||
region: locationData.region,
|
region: locationData.region,
|
||||||
|
|||||||
@@ -663,7 +663,7 @@ class ConversationAdapters:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_conversation_by_user(
|
def get_conversation_by_user(
|
||||||
user: KhojUser, client_application: ClientApplication = None, conversation_id: int = None
|
user: KhojUser, client_application: ClientApplication = None, conversation_id: str = None
|
||||||
) -> Optional[Conversation]:
|
) -> Optional[Conversation]:
|
||||||
if conversation_id:
|
if conversation_id:
|
||||||
conversation = (
|
conversation = (
|
||||||
@@ -689,7 +689,7 @@ class ConversationAdapters:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def aset_conversation_title(
|
async def aset_conversation_title(
|
||||||
user: KhojUser, client_application: ClientApplication, conversation_id: int, title: str
|
user: KhojUser, client_application: ClientApplication, conversation_id: str, title: str
|
||||||
):
|
):
|
||||||
conversation = await Conversation.objects.filter(
|
conversation = await Conversation.objects.filter(
|
||||||
user=user, client=client_application, id=conversation_id
|
user=user, client=client_application, id=conversation_id
|
||||||
@@ -701,7 +701,7 @@ class ConversationAdapters:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_conversation_by_id(conversation_id: int):
|
def get_conversation_by_id(conversation_id: str):
|
||||||
return Conversation.objects.filter(id=conversation_id).first()
|
return Conversation.objects.filter(id=conversation_id).first()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -730,7 +730,7 @@ class ConversationAdapters:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def aget_conversation_by_user(
|
async def aget_conversation_by_user(
|
||||||
user: KhojUser, client_application: ClientApplication = None, conversation_id: int = None, title: str = None
|
user: KhojUser, client_application: ClientApplication = None, conversation_id: str = None, title: str = None
|
||||||
) -> Optional[Conversation]:
|
) -> Optional[Conversation]:
|
||||||
query = Conversation.objects.filter(user=user, client=client_application).prefetch_related("agent")
|
query = Conversation.objects.filter(user=user, client=client_application).prefetch_related("agent")
|
||||||
|
|
||||||
@@ -747,7 +747,7 @@ class ConversationAdapters:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
async def adelete_conversation_by_user(
|
async def adelete_conversation_by_user(
|
||||||
user: KhojUser, client_application: ClientApplication = None, conversation_id: int = None
|
user: KhojUser, client_application: ClientApplication = None, conversation_id: str = None
|
||||||
):
|
):
|
||||||
if conversation_id:
|
if conversation_id:
|
||||||
return await Conversation.objects.filter(user=user, client=client_application, id=conversation_id).adelete()
|
return await Conversation.objects.filter(user=user, client=client_application, id=conversation_id).adelete()
|
||||||
@@ -900,7 +900,7 @@ class ConversationAdapters:
|
|||||||
user: KhojUser,
|
user: KhojUser,
|
||||||
conversation_log: dict,
|
conversation_log: dict,
|
||||||
client_application: ClientApplication = None,
|
client_application: ClientApplication = None,
|
||||||
conversation_id: int = None,
|
conversation_id: str = None,
|
||||||
user_message: str = None,
|
user_message: str = None,
|
||||||
):
|
):
|
||||||
slug = user_message.strip()[:200] if user_message else None
|
slug = user_message.strip()[:200] if user_message else None
|
||||||
@@ -1042,7 +1042,7 @@ class ConversationAdapters:
|
|||||||
return new_config
|
return new_config
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def add_files_to_filter(user: KhojUser, conversation_id: int, files: List[str]):
|
def add_files_to_filter(user: KhojUser, conversation_id: str, files: List[str]):
|
||||||
conversation = ConversationAdapters.get_conversation_by_user(user, conversation_id=conversation_id)
|
conversation = ConversationAdapters.get_conversation_by_user(user, conversation_id=conversation_id)
|
||||||
file_list = EntryAdapters.get_all_filenames_by_source(user, "computer")
|
file_list = EntryAdapters.get_all_filenames_by_source(user, "computer")
|
||||||
for filename in files:
|
for filename in files:
|
||||||
@@ -1056,7 +1056,7 @@ class ConversationAdapters:
|
|||||||
return conversation.file_filters
|
return conversation.file_filters
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def remove_files_from_filter(user: KhojUser, conversation_id: int, files: List[str]):
|
def remove_files_from_filter(user: KhojUser, conversation_id: str, files: List[str]):
|
||||||
conversation = ConversationAdapters.get_conversation_by_user(user, conversation_id=conversation_id)
|
conversation = ConversationAdapters.get_conversation_by_user(user, conversation_id=conversation_id)
|
||||||
for filename in files:
|
for filename in files:
|
||||||
if filename in conversation.file_filters:
|
if filename in conversation.file_filters:
|
||||||
|
|||||||
36
src/khoj/database/migrations/0063_conversation_temp_id.py
Normal file
36
src/khoj/database/migrations/0063_conversation_temp_id.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# Generated by Django 5.0.8 on 2024-09-19 15:53
|
||||||
|
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def create_uuid(apps, schema_editor):
|
||||||
|
Conversation = apps.get_model("database", "Conversation")
|
||||||
|
for conversation in Conversation.objects.all():
|
||||||
|
conversation.temp_id = uuid.uuid4()
|
||||||
|
conversation.save()
|
||||||
|
|
||||||
|
|
||||||
|
def remove_uuid(apps, schema_editor):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("database", "0062_merge_20240913_0222"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="conversation",
|
||||||
|
name="temp_id",
|
||||||
|
field=models.UUIDField(default=uuid.uuid4, editable=False),
|
||||||
|
),
|
||||||
|
migrations.RunPython(create_uuid, reverse_code=remove_uuid),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="conversation",
|
||||||
|
name="temp_id",
|
||||||
|
field=models.UUIDField(default=uuid.uuid4, editable=False, unique=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
# Generated by Django 5.0.8 on 2024-09-19 15:59
|
||||||
|
|
||||||
|
import json
|
||||||
|
import pickle
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
def reverse_remove_bigint_id(apps, schema_editor):
|
||||||
|
Conversation = apps.get_model("database", "Conversation")
|
||||||
|
index = 1
|
||||||
|
for conversation in Conversation.objects.all():
|
||||||
|
conversation.id = index
|
||||||
|
conversation.save()
|
||||||
|
index += 1
|
||||||
|
|
||||||
|
|
||||||
|
def update_conversation_id_in_job_state(apps, schema_editor):
|
||||||
|
try:
|
||||||
|
DjangoJob = apps.get_model("django_apscheduler", "DjangoJob")
|
||||||
|
Conversation = apps.get_model("database", "Conversation")
|
||||||
|
|
||||||
|
for job in DjangoJob.objects.all():
|
||||||
|
job_state = pickle.loads(job.job_state)
|
||||||
|
kwargs = job_state.get("kwargs")
|
||||||
|
conversation_id = kwargs.get("conversation_id") if kwargs else None
|
||||||
|
automation_metadata = json.loads(job_state.get("name", "{}"))
|
||||||
|
|
||||||
|
if not conversation_id:
|
||||||
|
job.delete()
|
||||||
|
|
||||||
|
if conversation_id:
|
||||||
|
try:
|
||||||
|
conversation = Conversation.objects.get(id=conversation_id)
|
||||||
|
automation_metadata["conversation_id"] = str(conversation.temp_id)
|
||||||
|
name = json.dumps(automation_metadata)
|
||||||
|
job_state["name"] = name
|
||||||
|
job_state["kwargs"]["conversation_id"] = str(conversation.temp_id)
|
||||||
|
job.job_state = pickle.dumps(job_state)
|
||||||
|
job.save()
|
||||||
|
except Conversation.DoesNotExist:
|
||||||
|
pass
|
||||||
|
except LookupError as e:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def no_op(apps, schema_editor):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def disable_triggers(apps, schema_editor):
|
||||||
|
schema_editor.execute('ALTER TABLE "database_conversation" DISABLE TRIGGER ALL;')
|
||||||
|
|
||||||
|
|
||||||
|
def enable_triggers(apps, schema_editor):
|
||||||
|
schema_editor.execute('ALTER TABLE "database_conversation" ENABLE TRIGGER ALL;')
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
dependencies = [
|
||||||
|
("database", "0063_conversation_temp_id"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(no_op, reverse_code=enable_triggers),
|
||||||
|
migrations.RunPython(update_conversation_id_in_job_state, reverse_code=no_op),
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name="conversation",
|
||||||
|
name="id",
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="conversation",
|
||||||
|
old_name="temp_id",
|
||||||
|
new_name="id",
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="conversation",
|
||||||
|
name="id",
|
||||||
|
field=models.UUIDField(
|
||||||
|
db_index=True, default=uuid.uuid4, editable=False, primary_key=True, serialize=False, unique=True
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RunPython(no_op, reverse_code=reverse_remove_bigint_id),
|
||||||
|
migrations.RunPython(no_op, reverse_code=disable_triggers),
|
||||||
|
]
|
||||||
@@ -350,6 +350,7 @@ class Conversation(BaseModel):
|
|||||||
title = models.CharField(max_length=200, default=None, null=True, blank=True)
|
title = models.CharField(max_length=200, default=None, null=True, blank=True)
|
||||||
agent = models.ForeignKey(Agent, on_delete=models.SET_NULL, default=None, null=True, blank=True)
|
agent = models.ForeignKey(Agent, on_delete=models.SET_NULL, default=None, null=True, blank=True)
|
||||||
file_filters = models.JSONField(default=list)
|
file_filters = models.JSONField(default=list)
|
||||||
|
id = models.UUIDField(default=uuid.uuid4, editable=False, unique=True, primary_key=True, db_index=True)
|
||||||
|
|
||||||
|
|
||||||
class PublicConversation(BaseModel):
|
class PublicConversation(BaseModel):
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ def save_to_conversation_log(
|
|||||||
inferred_queries: List[str] = [],
|
inferred_queries: List[str] = [],
|
||||||
intent_type: str = "remember",
|
intent_type: str = "remember",
|
||||||
client_application: ClientApplication = None,
|
client_application: ClientApplication = None,
|
||||||
conversation_id: int = None,
|
conversation_id: str = None,
|
||||||
automation_id: str = None,
|
automation_id: str = None,
|
||||||
uploaded_image_url: str = None,
|
uploaded_image_url: str = None,
|
||||||
):
|
):
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ async def extract_references_and_questions(
|
|||||||
q: str,
|
q: str,
|
||||||
n: int,
|
n: int,
|
||||||
d: float,
|
d: float,
|
||||||
conversation_id: int,
|
conversation_id: str,
|
||||||
conversation_commands: List[ConversationCommand] = [ConversationCommand.Default],
|
conversation_commands: List[ConversationCommand] = [ConversationCommand.Default],
|
||||||
location_data: LocationData = None,
|
location_data: LocationData = None,
|
||||||
send_status_func: Optional[Callable] = None,
|
send_status_func: Optional[Callable] = None,
|
||||||
|
|||||||
@@ -77,9 +77,7 @@ from khoj.routers.email import send_query_feedback
|
|||||||
@api_chat.get("/conversation/file-filters/{conversation_id}", response_class=Response)
|
@api_chat.get("/conversation/file-filters/{conversation_id}", response_class=Response)
|
||||||
@requires(["authenticated"])
|
@requires(["authenticated"])
|
||||||
def get_file_filter(request: Request, conversation_id: str) -> Response:
|
def get_file_filter(request: Request, conversation_id: str) -> Response:
|
||||||
conversation = ConversationAdapters.get_conversation_by_user(
|
conversation = ConversationAdapters.get_conversation_by_user(request.user.object, conversation_id=conversation_id)
|
||||||
request.user.object, conversation_id=int(conversation_id)
|
|
||||||
)
|
|
||||||
if not conversation:
|
if not conversation:
|
||||||
return Response(content=json.dumps({"status": "error", "message": "Conversation not found"}), status_code=404)
|
return Response(content=json.dumps({"status": "error", "message": "Conversation not found"}), status_code=404)
|
||||||
|
|
||||||
@@ -95,7 +93,7 @@ def get_file_filter(request: Request, conversation_id: str) -> Response:
|
|||||||
@api_chat.delete("/conversation/file-filters/bulk", response_class=Response)
|
@api_chat.delete("/conversation/file-filters/bulk", response_class=Response)
|
||||||
@requires(["authenticated"])
|
@requires(["authenticated"])
|
||||||
def remove_files_filter(request: Request, filter: FilesFilterRequest) -> Response:
|
def remove_files_filter(request: Request, filter: FilesFilterRequest) -> Response:
|
||||||
conversation_id = int(filter.conversation_id)
|
conversation_id = filter.conversation_id
|
||||||
files_filter = filter.filenames
|
files_filter = filter.filenames
|
||||||
file_filters = ConversationAdapters.remove_files_from_filter(request.user.object, conversation_id, files_filter)
|
file_filters = ConversationAdapters.remove_files_from_filter(request.user.object, conversation_id, files_filter)
|
||||||
return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
|
return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
|
||||||
@@ -105,7 +103,7 @@ def remove_files_filter(request: Request, filter: FilesFilterRequest) -> Respons
|
|||||||
@requires(["authenticated"])
|
@requires(["authenticated"])
|
||||||
def add_files_filter(request: Request, filter: FilesFilterRequest):
|
def add_files_filter(request: Request, filter: FilesFilterRequest):
|
||||||
try:
|
try:
|
||||||
conversation_id = int(filter.conversation_id)
|
conversation_id = filter.conversation_id
|
||||||
files_filter = filter.filenames
|
files_filter = filter.filenames
|
||||||
file_filters = ConversationAdapters.add_files_to_filter(request.user.object, conversation_id, files_filter)
|
file_filters = ConversationAdapters.add_files_to_filter(request.user.object, conversation_id, files_filter)
|
||||||
return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
|
return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
|
||||||
@@ -118,7 +116,7 @@ def add_files_filter(request: Request, filter: FilesFilterRequest):
|
|||||||
@requires(["authenticated"])
|
@requires(["authenticated"])
|
||||||
def add_file_filter(request: Request, filter: FileFilterRequest):
|
def add_file_filter(request: Request, filter: FileFilterRequest):
|
||||||
try:
|
try:
|
||||||
conversation_id = int(filter.conversation_id)
|
conversation_id = filter.conversation_id
|
||||||
files_filter = [filter.filename]
|
files_filter = [filter.filename]
|
||||||
file_filters = ConversationAdapters.add_files_to_filter(request.user.object, conversation_id, files_filter)
|
file_filters = ConversationAdapters.add_files_to_filter(request.user.object, conversation_id, files_filter)
|
||||||
return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
|
return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
|
||||||
@@ -130,7 +128,7 @@ def add_file_filter(request: Request, filter: FileFilterRequest):
|
|||||||
@api_chat.delete("/conversation/file-filters", response_class=Response)
|
@api_chat.delete("/conversation/file-filters", response_class=Response)
|
||||||
@requires(["authenticated"])
|
@requires(["authenticated"])
|
||||||
def remove_file_filter(request: Request, filter: FileFilterRequest) -> Response:
|
def remove_file_filter(request: Request, filter: FileFilterRequest) -> Response:
|
||||||
conversation_id = int(filter.conversation_id)
|
conversation_id = filter.conversation_id
|
||||||
files_filter = [filter.filename]
|
files_filter = [filter.filename]
|
||||||
file_filters = ConversationAdapters.remove_files_from_filter(request.user.object, conversation_id, files_filter)
|
file_filters = ConversationAdapters.remove_files_from_filter(request.user.object, conversation_id, files_filter)
|
||||||
return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
|
return Response(content=json.dumps(file_filters), media_type="application/json", status_code=200)
|
||||||
@@ -189,7 +187,7 @@ async def chat_starters(
|
|||||||
def chat_history(
|
def chat_history(
|
||||||
request: Request,
|
request: Request,
|
||||||
common: CommonQueryParams,
|
common: CommonQueryParams,
|
||||||
conversation_id: Optional[int] = None,
|
conversation_id: Optional[str] = None,
|
||||||
n: Optional[int] = None,
|
n: Optional[int] = None,
|
||||||
):
|
):
|
||||||
user = request.user.object
|
user = request.user.object
|
||||||
@@ -312,7 +310,7 @@ def get_shared_chat(
|
|||||||
async def clear_chat_history(
|
async def clear_chat_history(
|
||||||
request: Request,
|
request: Request,
|
||||||
common: CommonQueryParams,
|
common: CommonQueryParams,
|
||||||
conversation_id: Optional[int] = None,
|
conversation_id: Optional[str] = None,
|
||||||
):
|
):
|
||||||
user = request.user.object
|
user = request.user.object
|
||||||
|
|
||||||
@@ -375,7 +373,7 @@ def fork_public_conversation(
|
|||||||
def duplicate_chat_history_public_conversation(
|
def duplicate_chat_history_public_conversation(
|
||||||
request: Request,
|
request: Request,
|
||||||
common: CommonQueryParams,
|
common: CommonQueryParams,
|
||||||
conversation_id: int,
|
conversation_id: str,
|
||||||
):
|
):
|
||||||
user = request.user.object
|
user = request.user.object
|
||||||
domain = request.headers.get("host")
|
domain = request.headers.get("host")
|
||||||
@@ -423,7 +421,7 @@ def chat_sessions(
|
|||||||
|
|
||||||
session_values = [
|
session_values = [
|
||||||
{
|
{
|
||||||
"conversation_id": session[0],
|
"conversation_id": str(session[0]),
|
||||||
"slug": session[2] or session[1],
|
"slug": session[2] or session[1],
|
||||||
"agent_name": session[4],
|
"agent_name": session[4],
|
||||||
"agent_avatar": session[5],
|
"agent_avatar": session[5],
|
||||||
@@ -455,7 +453,7 @@ async def create_chat_session(
|
|||||||
# Create new Conversation Session
|
# Create new Conversation Session
|
||||||
conversation = await ConversationAdapters.acreate_conversation_session(user, request.user.client_app, agent_slug)
|
conversation = await ConversationAdapters.acreate_conversation_session(user, request.user.client_app, agent_slug)
|
||||||
|
|
||||||
response = {"conversation_id": conversation.id}
|
response = {"conversation_id": str(conversation.id)}
|
||||||
|
|
||||||
conversation_metadata = {
|
conversation_metadata = {
|
||||||
"agent": agent_slug,
|
"agent": agent_slug,
|
||||||
@@ -497,7 +495,7 @@ async def set_conversation_title(
|
|||||||
request: Request,
|
request: Request,
|
||||||
common: CommonQueryParams,
|
common: CommonQueryParams,
|
||||||
title: str,
|
title: str,
|
||||||
conversation_id: Optional[int] = None,
|
conversation_id: Optional[str] = None,
|
||||||
) -> Response:
|
) -> Response:
|
||||||
user = request.user.object
|
user = request.user.object
|
||||||
title = title.strip()[:200]
|
title = title.strip()[:200]
|
||||||
@@ -527,7 +525,7 @@ class ChatRequestBody(BaseModel):
|
|||||||
d: Optional[float] = None
|
d: Optional[float] = None
|
||||||
stream: Optional[bool] = False
|
stream: Optional[bool] = False
|
||||||
title: Optional[str] = None
|
title: Optional[str] = None
|
||||||
conversation_id: Optional[int] = None
|
conversation_id: Optional[str] = None
|
||||||
city: Optional[str] = None
|
city: Optional[str] = None
|
||||||
region: Optional[str] = None
|
region: Optional[str] = None
|
||||||
country: Optional[str] = None
|
country: Optional[str] = None
|
||||||
@@ -1016,7 +1014,7 @@ async def get_chat(
|
|||||||
d: float = None,
|
d: float = None,
|
||||||
stream: Optional[bool] = False,
|
stream: Optional[bool] = False,
|
||||||
title: Optional[str] = None,
|
title: Optional[str] = None,
|
||||||
conversation_id: Optional[int] = None,
|
conversation_id: Optional[str] = None,
|
||||||
city: Optional[str] = None,
|
city: Optional[str] = None,
|
||||||
region: Optional[str] = None,
|
region: Optional[str] = None,
|
||||||
country: Optional[str] = None,
|
country: Optional[str] = None,
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ from typing import (
|
|||||||
Tuple,
|
Tuple,
|
||||||
Union,
|
Union,
|
||||||
)
|
)
|
||||||
from urllib.parse import parse_qs, urljoin, urlparse
|
from urllib.parse import parse_qs, quote, urljoin, urlparse
|
||||||
|
|
||||||
import cron_descriptor
|
import cron_descriptor
|
||||||
import pytz
|
import pytz
|
||||||
@@ -799,7 +799,7 @@ def generate_chat_response(
|
|||||||
conversation_commands: List[ConversationCommand] = [ConversationCommand.Default],
|
conversation_commands: List[ConversationCommand] = [ConversationCommand.Default],
|
||||||
user: KhojUser = None,
|
user: KhojUser = None,
|
||||||
client_application: ClientApplication = None,
|
client_application: ClientApplication = None,
|
||||||
conversation_id: int = None,
|
conversation_id: str = None,
|
||||||
location_data: LocationData = None,
|
location_data: LocationData = None,
|
||||||
user_name: Optional[str] = None,
|
user_name: Optional[str] = None,
|
||||||
uploaded_image_url: Optional[str] = None,
|
uploaded_image_url: Optional[str] = None,
|
||||||
@@ -1102,7 +1102,7 @@ def scheduled_chat(
|
|||||||
user: KhojUser,
|
user: KhojUser,
|
||||||
calling_url: URL,
|
calling_url: URL,
|
||||||
job_id: str = None,
|
job_id: str = None,
|
||||||
conversation_id: int = None,
|
conversation_id: str = None,
|
||||||
):
|
):
|
||||||
logger.info(f"Processing scheduled_chat: {query_to_run}")
|
logger.info(f"Processing scheduled_chat: {query_to_run}")
|
||||||
if job_id:
|
if job_id:
|
||||||
@@ -1131,7 +1131,8 @@ def scheduled_chat(
|
|||||||
|
|
||||||
# Replace the original conversation_id with the conversation_id
|
# Replace the original conversation_id with the conversation_id
|
||||||
if conversation_id:
|
if conversation_id:
|
||||||
query_dict["conversation_id"] = [conversation_id]
|
# encode the conversation_id to avoid any issues with special characters
|
||||||
|
query_dict["conversation_id"] = [quote(conversation_id)]
|
||||||
|
|
||||||
# Restructure the original query_dict into a valid JSON payload for the chat API
|
# Restructure the original query_dict into a valid JSON payload for the chat API
|
||||||
json_payload = {key: values[0] for key, values in query_dict.items()}
|
json_payload = {key: values[0] for key, values in query_dict.items()}
|
||||||
@@ -1185,7 +1186,7 @@ def scheduled_chat(
|
|||||||
|
|
||||||
|
|
||||||
async def create_automation(
|
async def create_automation(
|
||||||
q: str, timezone: str, user: KhojUser, calling_url: URL, meta_log: dict = {}, conversation_id: int = None
|
q: str, timezone: str, user: KhojUser, calling_url: URL, meta_log: dict = {}, conversation_id: str = None
|
||||||
):
|
):
|
||||||
crontime, query_to_run, subject = await schedule_query(q, meta_log)
|
crontime, query_to_run, subject = await schedule_query(q, meta_log)
|
||||||
job = await schedule_automation(query_to_run, subject, crontime, timezone, q, user, calling_url, conversation_id)
|
job = await schedule_automation(query_to_run, subject, crontime, timezone, q, user, calling_url, conversation_id)
|
||||||
@@ -1200,7 +1201,7 @@ async def schedule_automation(
|
|||||||
scheduling_request: str,
|
scheduling_request: str,
|
||||||
user: KhojUser,
|
user: KhojUser,
|
||||||
calling_url: URL,
|
calling_url: URL,
|
||||||
conversation_id: int,
|
conversation_id: str,
|
||||||
):
|
):
|
||||||
# Disable minute level automation recurrence
|
# Disable minute level automation recurrence
|
||||||
minute_value = crontime.split(" ")[0]
|
minute_value = crontime.split(" ")[0]
|
||||||
@@ -1218,7 +1219,7 @@ async def schedule_automation(
|
|||||||
"scheduling_request": scheduling_request,
|
"scheduling_request": scheduling_request,
|
||||||
"subject": subject,
|
"subject": subject,
|
||||||
"crontime": crontime,
|
"crontime": crontime,
|
||||||
"conversation_id": conversation_id,
|
"conversation_id": str(conversation_id),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
query_id = hashlib.md5(f"{query_to_run}_{crontime}".encode("utf-8")).hexdigest()
|
query_id = hashlib.md5(f"{query_to_run}_{crontime}".encode("utf-8")).hexdigest()
|
||||||
|
|||||||
Reference in New Issue
Block a user