Add Vision Support (#889)

# Summary of Changes
* New UI to show preview of image uploads
* ChatML message changes to support gpt-4o vision based responses on images
* AWS S3 image uploads for persistent image context in conversations
* Database changes to have `vision_enabled` option in server admin panel while configuring models
* Render previously uploaded images in the chat history, show uploaded images for pending msgs
* Pass the uploaded_image_url through to subqueries
* Allow image to render upon first message from the homepage
* Add rendering support for images to shared chat as well
* Fix some UI/functionality bugs in the share page
* Convert user attached images for chat to webp format before upload
* Use placeholder to attached image for data source, response mode actors
* Update all clients to call /api/chat as a POST instead of GET request
* Fix copying chat messages with images to clipboard

TLDR; Add vision support for openai models on Khoj via the web UI!

---------

Co-authored-by: sabaimran <narmiabas@gmail.com>
Co-authored-by: Debanjum Singh Solanky <debanjum@gmail.com>
This commit is contained in:
Raghav Tirumale 2024-09-09 17:22:18 -05:00 committed by GitHub
parent b553bba1d8
commit 549686a7a4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 740 additions and 417 deletions

View file

@ -154,7 +154,7 @@
? `&region=${region}&city=${city}&country=${countryName}&timezone=${timezone}` ? `&region=${region}&city=${city}&country=${countryName}&timezone=${timezone}`
: ''; : '';
const response = await fetch(chatApi, { headers }); const response = await fetch(chatApi, { method: 'POST', headers });
try { try {
if (!response.ok) throw new Error(response.statusText); if (!response.ok) throw new Error(response.statusText);

View file

@ -407,7 +407,7 @@
? `&region=${region}&city=${city}&country=${countryName}&timezone=${timezone}` ? `&region=${region}&city=${city}&country=${countryName}&timezone=${timezone}`
: ''; : '';
const response = await fetch(chatApi, { headers }); const response = await fetch(chatApi, { method: 'POST', headers });
try { try {
if (!response.ok) throw new Error(response.statusText); if (!response.ok) throw new Error(response.statusText);

View file

@ -878,7 +878,7 @@ Call CALLBACK func with response and CBARGS."
(let ((params `(("q" ,query) ("n" ,khoj-results-count)))) (let ((params `(("q" ,query) ("n" ,khoj-results-count))))
(when session-id (push `("conversation_id" ,session-id) params)) (when session-id (push `("conversation_id" ,session-id) params))
(khoj--call-api-async "/api/chat" (khoj--call-api-async "/api/chat"
"GET" "POST"
params params
callback cbargs))) callback cbargs)))

View file

@ -1074,9 +1074,9 @@ export class KhojChatView extends KhojPaneView {
}; };
let response = await fetch(chatUrl, { let response = await fetch(chatUrl, {
method: "GET", method: "POST",
headers: { headers: {
"Content-Type": "text/plain", "Content-Type": "application/json",
"Authorization": `Bearer ${this.setting.khojApiKey}`, "Authorization": `Bearer ${this.setting.khojApiKey}`,
}, },
}) })

View file

@ -40,9 +40,9 @@ export default function RootLayout({
content="default-src 'self' https://assets.khoj.dev; content="default-src 'self' https://assets.khoj.dev;
media-src * blob:; media-src * blob:;
script-src 'self' https://assets.khoj.dev 'unsafe-inline' 'unsafe-eval'; script-src 'self' https://assets.khoj.dev 'unsafe-inline' 'unsafe-eval';
connect-src 'self' https://ipapi.co/json ws://localhost:42110; connect-src 'self' blob: https://ipapi.co/json ws://localhost:42110;
style-src 'self' https://assets.khoj.dev 'unsafe-inline' https://fonts.googleapis.com; style-src 'self' https://assets.khoj.dev 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https://*.khoj.dev https://*.googleusercontent.com https://*.google.com/ https://*.gstatic.com; img-src 'self' data: blob: https://*.khoj.dev https://*.googleusercontent.com https://*.google.com/ https://*.gstatic.com;
font-src 'self' https://assets.khoj.dev https://fonts.gstatic.com; font-src 'self' https://assets.khoj.dev https://fonts.gstatic.com;
child-src 'none'; child-src 'none';
object-src 'none';" object-src 'none';"

View file

@ -27,12 +27,14 @@ interface ChatBodyDataProps {
setUploadedFiles: (files: string[]) => void; setUploadedFiles: (files: string[]) => void;
isMobileWidth?: boolean; isMobileWidth?: boolean;
isLoggedIn: boolean; isLoggedIn: boolean;
setImage64: (image64: string) => void;
} }
function ChatBodyData(props: ChatBodyDataProps) { function ChatBodyData(props: ChatBodyDataProps) {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const conversationId = searchParams.get("conversationId"); const conversationId = searchParams.get("conversationId");
const [message, setMessage] = useState(""); const [message, setMessage] = useState("");
const [image, setImage] = useState<string | null>(null);
const [processingMessage, setProcessingMessage] = useState(false); const [processingMessage, setProcessingMessage] = useState(false);
const [agentMetadata, setAgentMetadata] = useState<AgentData | null>(null); const [agentMetadata, setAgentMetadata] = useState<AgentData | null>(null);
@ -40,6 +42,19 @@ function ChatBodyData(props: ChatBodyDataProps) {
const onConversationIdChange = props.onConversationIdChange; const onConversationIdChange = props.onConversationIdChange;
useEffect(() => { useEffect(() => {
if (image) {
props.setImage64(encodeURIComponent(image));
}
}, [image, props.setImage64]);
useEffect(() => {
const storedImage = localStorage.getItem("image");
if (storedImage) {
setImage(storedImage);
props.setImage64(encodeURIComponent(storedImage));
localStorage.removeItem("image");
}
const storedMessage = localStorage.getItem("message"); const storedMessage = localStorage.getItem("message");
if (storedMessage) { if (storedMessage) {
setProcessingMessage(true); setProcessingMessage(true);
@ -95,6 +110,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
agentColor={agentMetadata?.color} agentColor={agentMetadata?.color}
isLoggedIn={props.isLoggedIn} isLoggedIn={props.isLoggedIn}
sendMessage={(message) => setMessage(message)} sendMessage={(message) => setMessage(message)}
sendImage={(image) => setImage(image)}
sendDisabled={processingMessage} sendDisabled={processingMessage}
chatOptionsData={props.chatOptionsData} chatOptionsData={props.chatOptionsData}
conversationId={conversationId} conversationId={conversationId}
@ -116,6 +132,7 @@ export default function Chat() {
const [queryToProcess, setQueryToProcess] = useState<string>(""); const [queryToProcess, setQueryToProcess] = useState<string>("");
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 locationData = useIPLocationData(); const locationData = useIPLocationData();
const authenticatedData = useAuthenticatedData(); const authenticatedData = useAuthenticatedData();
const isMobileWidth = useIsMobileWidth(); const isMobileWidth = useIsMobileWidth();
@ -148,6 +165,7 @@ export default function Chat() {
completed: false, completed: false,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
rawQuery: queryToProcess || "", rawQuery: queryToProcess || "",
uploadedImageData: decodeURIComponent(image64),
}; };
setMessages((prevMessages) => [...prevMessages, newStreamMessage]); setMessages((prevMessages) => [...prevMessages, newStreamMessage]);
setProcessQuerySignal(true); setProcessQuerySignal(true);
@ -178,6 +196,7 @@ export default function Chat() {
if (done) { if (done) {
setQueryToProcess(""); setQueryToProcess("");
setProcessQuerySignal(false); setProcessQuerySignal(false);
setImage64("");
break; break;
} }
@ -218,7 +237,14 @@ export default function Chat() {
chatAPI += `&region=${locationData.region}&country=${locationData.country}&city=${locationData.city}&timezone=${locationData.timezone}`; chatAPI += `&region=${locationData.region}&country=${locationData.country}&city=${locationData.city}&timezone=${locationData.timezone}`;
} }
const response = await fetch(chatAPI); const response = await fetch(chatAPI, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: image64 ? JSON.stringify({ image: image64 }) : undefined,
});
try { try {
await readChatStream(response); await readChatStream(response);
} catch (err) { } catch (err) {
@ -282,6 +308,7 @@ export default function Chat() {
setUploadedFiles={setUploadedFiles} setUploadedFiles={setUploadedFiles}
isMobileWidth={isMobileWidth} isMobileWidth={isMobileWidth}
onConversationIdChange={handleConversationIdChange} onConversationIdChange={handleConversationIdChange}
setImage64={setImage64}
/> />
</Suspense> </Suspense>
</div> </div>

View file

@ -267,6 +267,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
created: message.timestamp, created: message.timestamp,
by: "you", by: "you",
automationId: "", automationId: "",
uploadedImageData: message.uploadedImageData,
}} }}
customClassName="fullHistory" customClassName="fullHistory"
borderLeftColor={`${data?.agent.color}-500`} borderLeftColor={`${data?.agent.color}-500`}
@ -309,6 +310,7 @@ export default function ChatHistory(props: ChatHistoryProps) {
created: new Date().getTime().toString(), created: new Date().getTime().toString(),
by: "you", by: "you",
automationId: "", automationId: "",
uploadedImageData: props.pendingMessage,
}} }}
customClassName="fullHistory" customClassName="fullHistory"
borderLeftColor={`${data?.agent.color}-500`} borderLeftColor={`${data?.agent.color}-500`}

View file

@ -16,6 +16,7 @@ import {
Microphone, Microphone,
Notebook, Notebook,
Paperclip, Paperclip,
X,
Question, Question,
Robot, Robot,
Shapes, Shapes,
@ -55,6 +56,7 @@ export interface ChatOptions {
interface ChatInputProps { interface ChatInputProps {
sendMessage: (message: string) => void; sendMessage: (message: string) => void;
sendImage: (image: string) => void;
sendDisabled: boolean; sendDisabled: boolean;
setUploadedFiles?: (files: string[]) => void; setUploadedFiles?: (files: string[]) => void;
conversationId?: string | null; conversationId?: string | null;
@ -75,6 +77,9 @@ export default function ChatInputArea(props: ChatInputProps) {
const [showLoginPrompt, setShowLoginPrompt] = useState(false); const [showLoginPrompt, setShowLoginPrompt] = useState(false);
const [recording, setRecording] = useState(false); const [recording, setRecording] = useState(false);
const [imageUploaded, setImageUploaded] = useState(false);
const [imagePath, setImagePath] = useState<string | null>(null);
const [imageData, setImageData] = useState<string | null>(null);
const [mediaRecorder, setMediaRecorder] = useState<MediaRecorder | null>(null); const [mediaRecorder, setMediaRecorder] = useState<MediaRecorder | null>(null);
const [progressValue, setProgressValue] = useState(0); const [progressValue, setProgressValue] = useState(0);
@ -97,7 +102,30 @@ export default function ChatInputArea(props: ChatInputProps) {
} }
}, [uploading]); }, [uploading]);
useEffect(() => {
async function fetchImageData() {
if (imagePath) {
const response = await fetch(imagePath);
const blob = await response.blob();
const reader = new FileReader();
reader.onload = function () {
const base64data = reader.result;
setImageData(base64data as string);
};
reader.readAsDataURL(blob);
}
setUploading(false);
}
setUploading(true);
fetchImageData();
}, [imagePath]);
function onSendMessage() { function onSendMessage() {
if (imageUploaded) {
setImageUploaded(false);
setImagePath(null);
props.sendImage(imageData || "");
}
if (!message.trim()) return; if (!message.trim()) return;
if (!props.isLoggedIn) { if (!props.isLoggedIn) {
@ -142,6 +170,17 @@ export default function ChatInputArea(props: ChatInputProps) {
setShowLoginPrompt(true); setShowLoginPrompt(true);
return; return;
} }
// check for image file
const image_endings = ["jpg", "jpeg", "png"];
for (let i = 0; i < files.length; i++) {
const file = files[i];
const file_extension = file.name.split(".").pop();
if (image_endings.includes(file_extension || "")) {
setImageUploaded(true);
setImagePath(URL.createObjectURL(file));
return;
}
}
uploadDataForIndexing( uploadDataForIndexing(
files, files,
@ -287,6 +326,11 @@ export default function ChatInputArea(props: ChatInputProps) {
setIsDragAndDropping(false); setIsDragAndDropping(false);
} }
function removeImageUpload() {
setImageUploaded(false);
setImagePath(null);
}
return ( return (
<> <>
{showLoginPrompt && loginRedirectMessage && ( {showLoginPrompt && loginRedirectMessage && (
@ -397,11 +441,24 @@ export default function ChatInputArea(props: ChatInputProps) {
</div> </div>
)} )}
<div <div
className={`${styles.actualInputArea} items-center justify-between dark:bg-neutral-700`} className={`${styles.actualInputArea} items-center justify-between dark:bg-neutral-700 relative`}
onDragOver={handleDragOver} onDragOver={handleDragOver}
onDragLeave={handleDragLeave} onDragLeave={handleDragLeave}
onDrop={handleDragAndDropFiles} onDrop={handleDragAndDropFiles}
> >
{imageUploaded && (
<div className="absolute bottom-[80px] left-0 right-0 dark:bg-neutral-700 bg-white pt-5 pb-5 w-full rounded-lg border dark:border-none grid grid-cols-2">
<div className="pl-4 pr-4">
<img src={imagePath || ""} alt="img" className="w-auto max-h-[100px]" />
</div>
<div className="pl-4 pr-4">
<X
className="w-6 h-6 float-right dark:hover:bg-[hsl(var(--background))] hover:bg-neutral-100 rounded-sm"
onClick={removeImageUpload}
/>
</div>
</div>
)}
<input <input
type="file" type="file"
multiple={true} multiple={true}
@ -427,6 +484,8 @@ export default function ChatInputArea(props: ChatInputProps) {
value={message} value={message}
onKeyDown={(e) => { onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) { if (e.key === "Enter" && !e.shiftKey) {
setImageUploaded(false);
setImagePath(null);
e.preventDefault(); e.preventDefault();
onSendMessage(); onSendMessage();
} }

View file

@ -53,6 +53,11 @@ div.chatMessageContainer h3 img {
width: 24px; width: 24px;
} }
div.you img {
height: 16rem;
width: auto;
}
div.you { div.you {
color: hsla(var(--secondary-foreground)); color: hsla(var(--secondary-foreground));
} }

View file

@ -111,6 +111,7 @@ export interface SingleChatMessage {
rawQuery?: string; rawQuery?: string;
intent?: Intent; intent?: Intent;
agent?: AgentData; agent?: AgentData;
uploadedImageData?: string;
} }
export interface StreamMessage { export interface StreamMessage {
@ -122,6 +123,7 @@ export interface StreamMessage {
rawQuery: string; rawQuery: string;
timestamp: string; timestamp: string;
agent?: AgentData; agent?: AgentData;
uploadedImageData?: string;
} }
export interface ChatHistoryData { export interface ChatHistoryData {
@ -203,6 +205,7 @@ interface ChatMessageProps {
borderLeftColor?: string; borderLeftColor?: string;
isLastMessage?: boolean; isLastMessage?: boolean;
agent?: AgentData; agent?: AgentData;
uploadedImageData?: string;
} }
interface TrainOfThoughtProps { interface TrainOfThoughtProps {
@ -273,6 +276,7 @@ export function TrainOfThought(props: TrainOfThoughtProps) {
export default function ChatMessage(props: ChatMessageProps) { export default function ChatMessage(props: ChatMessageProps) {
const [copySuccess, setCopySuccess] = useState<boolean>(false); const [copySuccess, setCopySuccess] = useState<boolean>(false);
const [isHovering, setIsHovering] = useState<boolean>(false); const [isHovering, setIsHovering] = useState<boolean>(false);
const [textRendered, setTextRendered] = useState<string>("");
const [markdownRendered, setMarkdownRendered] = useState<string>(""); const [markdownRendered, setMarkdownRendered] = useState<string>("");
const [isPlaying, setIsPlaying] = useState<boolean>(false); const [isPlaying, setIsPlaying] = useState<boolean>(false);
const [interrupted, setInterrupted] = useState<boolean>(false); const [interrupted, setInterrupted] = useState<boolean>(false);
@ -322,6 +326,10 @@ export default function ChatMessage(props: ChatMessageProps) {
.replace(/\\\[/g, "LEFTBRACKET") .replace(/\\\[/g, "LEFTBRACKET")
.replace(/\\\]/g, "RIGHTBRACKET"); .replace(/\\\]/g, "RIGHTBRACKET");
if (props.chatMessage.uploadedImageData) {
message = `![uploaded image](${props.chatMessage.uploadedImageData})\n\n${message}`;
}
if (props.chatMessage.intent && props.chatMessage.intent.type == "text-to-image") { if (props.chatMessage.intent && props.chatMessage.intent.type == "text-to-image") {
message = `![generated image](data:image/png;base64,${message})`; message = `![generated image](data:image/png;base64,${message})`;
} else if (props.chatMessage.intent && props.chatMessage.intent.type == "text-to-image2") { } else if (props.chatMessage.intent && props.chatMessage.intent.type == "text-to-image2") {
@ -340,6 +348,9 @@ export default function ChatMessage(props: ChatMessageProps) {
message += `\n\n**Inferred Query**\n\n${props.chatMessage.intent["inferred-queries"][0]}`; message += `\n\n**Inferred Query**\n\n${props.chatMessage.intent["inferred-queries"][0]}`;
} }
setTextRendered(message);
// Render the markdown
let markdownRendered = md.render(message); let markdownRendered = md.render(message);
// Replace placeholders with LaTeX delimiters // Replace placeholders with LaTeX delimiters
@ -542,7 +553,6 @@ export default function ChatMessage(props: ChatMessageProps) {
className={constructClasses(props.chatMessage)} className={constructClasses(props.chatMessage)}
onMouseLeave={(event) => setIsHovering(false)} onMouseLeave={(event) => setIsHovering(false)}
onMouseEnter={(event) => setIsHovering(true)} onMouseEnter={(event) => setIsHovering(true)}
onClick={props.chatMessage.by === "khoj" ? (event) => undefined : undefined}
> >
<div className={chatMessageWrapperClasses(props.chatMessage)}> <div className={chatMessageWrapperClasses(props.chatMessage)}>
<div <div
@ -595,7 +605,7 @@ export default function ChatMessage(props: ChatMessageProps) {
title="Copy" title="Copy"
className={`${styles.copyButton}`} className={`${styles.copyButton}`}
onClick={() => { onClick={() => {
navigator.clipboard.writeText(props.chatMessage.message); navigator.clipboard.writeText(textRendered);
setCopySuccess(true); setCopySuccess(true);
}} }}
> >

View file

@ -79,7 +79,7 @@ async function verifyStatement(
let verificationMessage = `${verificationPrecursor} ${message}`; let verificationMessage = `${verificationPrecursor} ${message}`;
const apiURL = `${chatURL}?q=${encodeURIComponent(verificationMessage)}&client=web&stream=true&conversation_id=${conversationId}`; const apiURL = `${chatURL}?q=${encodeURIComponent(verificationMessage)}&client=web&stream=true&conversation_id=${conversationId}`;
try { try {
const response = await fetch(apiURL); const response = await fetch(apiURL, { method: "POST" });
if (!response.body) throw new Error("No response body found"); if (!response.body) throw new Error("No response body found");
const reader = response.body?.getReader(); const reader = response.body?.getReader();

View file

@ -45,9 +45,9 @@ export default function RootLayout({
content="default-src 'self' https://assets.khoj.dev; content="default-src 'self' https://assets.khoj.dev;
media-src * blob:; media-src * blob:;
script-src 'self' https://assets.khoj.dev 'unsafe-inline' 'unsafe-eval'; script-src 'self' https://assets.khoj.dev 'unsafe-inline' 'unsafe-eval';
connect-src 'self' https://ipapi.co/json ws://localhost:42110; connect-src 'self' blob: https://ipapi.co/json ws://localhost:42110;
style-src 'self' https://assets.khoj.dev 'unsafe-inline' https://fonts.googleapis.com; style-src 'self' https://assets.khoj.dev 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https://*.khoj.dev https://*.googleusercontent.com https://*.google.com/ https://*.gstatic.com; img-src 'self' data: blob: https://*.khoj.dev https://*.googleusercontent.com https://*.google.com/ https://*.gstatic.com;
font-src 'self' https://assets.khoj.dev https://fonts.gstatic.com; font-src 'self' https://assets.khoj.dev https://fonts.gstatic.com;
child-src 'none'; child-src 'none';
object-src 'none';" object-src 'none';"

View file

@ -43,6 +43,7 @@ function FisherYatesShuffle(array: any[]) {
function ChatBodyData(props: ChatBodyDataProps) { function ChatBodyData(props: ChatBodyDataProps) {
const [message, setMessage] = useState(""); const [message, setMessage] = useState("");
const [image, setImage] = useState<string | null>(null);
const [processingMessage, setProcessingMessage] = useState(false); const [processingMessage, setProcessingMessage] = useState(false);
const [greeting, setGreeting] = useState(""); const [greeting, setGreeting] = useState("");
const [shuffledOptions, setShuffledOptions] = useState<Suggestion[]>([]); const [shuffledOptions, setShuffledOptions] = useState<Suggestion[]>([]);
@ -141,6 +142,9 @@ function ChatBodyData(props: ChatBodyDataProps) {
onConversationIdChange?.(newConversationId); onConversationIdChange?.(newConversationId);
window.location.href = `/chat?conversationId=${newConversationId}`; window.location.href = `/chat?conversationId=${newConversationId}`;
localStorage.setItem("message", message); localStorage.setItem("message", message);
if (image) {
localStorage.setItem("image", image);
}
} catch (error) { } catch (error) {
console.error("Error creating new conversation:", error); console.error("Error creating new conversation:", error);
setProcessingMessage(false); setProcessingMessage(false);
@ -230,6 +234,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
<ChatInputArea <ChatInputArea
isLoggedIn={props.isLoggedIn} isLoggedIn={props.isLoggedIn}
sendMessage={(message) => setMessage(message)} sendMessage={(message) => setMessage(message)}
sendImage={(image) => setImage(image)}
sendDisabled={processingMessage} sendDisabled={processingMessage}
chatOptionsData={props.chatOptionsData} chatOptionsData={props.chatOptionsData}
conversationId={null} conversationId={null}
@ -310,6 +315,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
<ChatInputArea <ChatInputArea
isLoggedIn={props.isLoggedIn} isLoggedIn={props.isLoggedIn}
sendMessage={(message) => setMessage(message)} sendMessage={(message) => setMessage(message)}
sendImage={(image) => setImage(image)}
sendDisabled={processingMessage} sendDisabled={processingMessage}
chatOptionsData={props.chatOptionsData} chatOptionsData={props.chatOptionsData}
conversationId={null} conversationId={null}

View file

@ -20,9 +20,9 @@ export default function RootLayout({
httpEquiv="Content-Security-Policy" httpEquiv="Content-Security-Policy"
content="default-src 'self' https://assets.khoj.dev; content="default-src 'self' https://assets.khoj.dev;
script-src 'self' https://assets.khoj.dev 'unsafe-inline' 'unsafe-eval'; script-src 'self' https://assets.khoj.dev 'unsafe-inline' 'unsafe-eval';
connect-src 'self' https://ipapi.co/json ws://localhost:42110; connect-src 'self' blob: https://ipapi.co/json ws://localhost:42110;
style-src 'self' https://assets.khoj.dev 'unsafe-inline' https://fonts.googleapis.com; style-src 'self' https://assets.khoj.dev 'unsafe-inline' https://fonts.googleapis.com;
img-src 'self' data: https://*.khoj.dev https://*.googleusercontent.com https://*.google.com/ https://*.gstatic.com; img-src 'self' data: blob: https://*.khoj.dev https://*.googleusercontent.com https://*.google.com/ https://*.gstatic.com;
font-src 'self' https://assets.khoj.dev https://fonts.gstatic.com; font-src 'self' https://assets.khoj.dev https://fonts.gstatic.com;
child-src 'none'; child-src 'none';
object-src 'none';" object-src 'none';"

View file

@ -28,16 +28,23 @@ interface ChatBodyDataProps {
isLoggedIn: boolean; isLoggedIn: boolean;
conversationId?: string; conversationId?: string;
setQueryToProcess: (query: string) => void; setQueryToProcess: (query: string) => void;
setImage64: (image64: string) => void;
} }
function ChatBodyData(props: ChatBodyDataProps) { function ChatBodyData(props: ChatBodyDataProps) {
const [message, setMessage] = useState(""); const [message, setMessage] = useState("");
const [image, setImage] = useState<string | null>(null);
const [processingMessage, setProcessingMessage] = useState(false); const [processingMessage, setProcessingMessage] = useState(false);
const [agentMetadata, setAgentMetadata] = useState<AgentData | null>(null); const [agentMetadata, setAgentMetadata] = useState<AgentData | null>(null);
const setQueryToProcess = props.setQueryToProcess; const setQueryToProcess = props.setQueryToProcess;
const streamedMessages = props.streamedMessages; const streamedMessages = props.streamedMessages;
useEffect(() => {
if (image) {
props.setImage64(encodeURIComponent(image));
}
}, [image, props.setImage64]);
useEffect(() => { useEffect(() => {
if (message) { if (message) {
setProcessingMessage(true); setProcessingMessage(true);
@ -74,11 +81,12 @@ function ChatBodyData(props: ChatBodyDataProps) {
/> />
</div> </div>
<div <div
className={`${styles.inputBox} shadow-md bg-background align-middle items-center justify-center px-3`} 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`}
> >
<ChatInputArea <ChatInputArea
isLoggedIn={props.isLoggedIn} isLoggedIn={props.isLoggedIn}
sendMessage={(message) => setMessage(message)} sendMessage={(message) => setMessage(message)}
sendImage={(image) => setImage(image)}
sendDisabled={processingMessage} sendDisabled={processingMessage}
chatOptionsData={props.chatOptionsData} chatOptionsData={props.chatOptionsData}
conversationId={props.conversationId} conversationId={props.conversationId}
@ -101,6 +109,7 @@ export default function SharedChat() {
const [processQuerySignal, setProcessQuerySignal] = useState(false); const [processQuerySignal, setProcessQuerySignal] = useState(false);
const [uploadedFiles, setUploadedFiles] = useState<string[]>([]); const [uploadedFiles, setUploadedFiles] = useState<string[]>([]);
const [paramSlug, setParamSlug] = useState<string | undefined>(undefined); const [paramSlug, setParamSlug] = useState<string | undefined>(undefined);
const [image64, setImage64] = useState<string>("");
const locationData = useIPLocationData(); const locationData = useIPLocationData();
const authenticatedData = useAuthenticatedData(); const authenticatedData = useAuthenticatedData();
@ -156,6 +165,7 @@ export default function SharedChat() {
completed: false, completed: false,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
rawQuery: queryToProcess || "", rawQuery: queryToProcess || "",
uploadedImageData: decodeURIComponent(image64),
}; };
setMessages((prevMessages) => [...prevMessages, newStreamMessage]); setMessages((prevMessages) => [...prevMessages, newStreamMessage]);
setProcessQuerySignal(true); setProcessQuerySignal(true);
@ -182,6 +192,7 @@ export default function SharedChat() {
if (done) { if (done) {
setQueryToProcess(""); setQueryToProcess("");
setProcessQuerySignal(false); setProcessQuerySignal(false);
setImage64("");
break; break;
} }
@ -216,33 +227,21 @@ export default function SharedChat() {
chatAPI += `&region=${locationData.region}&country=${locationData.country}&city=${locationData.city}&timezone=${locationData.timezone}`; chatAPI += `&region=${locationData.region}&country=${locationData.country}&city=${locationData.city}&timezone=${locationData.timezone}`;
} }
const response = await fetch(chatAPI); const response = await fetch(chatAPI, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: image64 ? JSON.stringify({ image: image64 }) : undefined,
});
try { try {
await readChatStream(response); await readChatStream(response);
} catch (err) { } catch (error) {
console.log(err); console.error(error);
} }
} }
useEffect(() => {
(async () => {
if (conversationId) {
// Add a new object to the state
const newStreamMessage: StreamMessage = {
rawResponse: "",
trainOfThought: [],
context: [],
onlineContext: {},
completed: false,
timestamp: new Date().toISOString(),
rawQuery: queryToProcess || "",
};
setProcessQuerySignal(true);
setMessages((prevMessages) => [...prevMessages, newStreamMessage]);
}
})();
}, [conversationId, queryToProcess]);
if (isLoading) { if (isLoading) {
return <Loading />; return <Loading />;
} }
@ -275,6 +274,7 @@ export default function SharedChat() {
setTitle={setTitle} setTitle={setTitle}
setUploadedFiles={setUploadedFiles} setUploadedFiles={setUploadedFiles}
isMobileWidth={isMobileWidth} isMobileWidth={isMobileWidth}
setImage64={setImage64}
/> />
</Suspense> </Suspense>
</div> </div>

View file

@ -12,15 +12,11 @@ div.main {
div.inputBox { div.inputBox {
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
border-radius: 16px;
margin-bottom: 20px; margin-bottom: 20px;
gap: 12px; gap: 12px;
padding-left: 20px;
padding-right: 20px;
align-content: center; align-content: center;
} }
input.inputBox { input.inputBox {
border: none; border: none;
} }
@ -31,7 +27,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,7 +90,6 @@ div.agentIndicator {
padding: 10px; padding: 10px;
} }
@media (max-width: 768px) { @media (max-width: 768px) {
div.chatBody { div.chatBody {
grid-template-columns: 0fr 1fr; grid-template-columns: 0fr 1fr;
@ -124,5 +119,4 @@ div.agentIndicator {
gap: 0; gap: 0;
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
} }

View file

@ -47,7 +47,7 @@
"eslint": "^8", "eslint": "^8",
"eslint-config-next": "14.2.3", "eslint-config-next": "14.2.3",
"input-otp": "^1.2.4", "input-otp": "^1.2.4",
"intl-tel-input": "^23.8.0", "intl-tel-input": "^23.8.1",
"katex": "^0.16.10", "katex": "^0.16.10",
"libphonenumber-js": "^1.11.4", "libphonenumber-js": "^1.11.4",
"lucide-react": "^0.397.0", "lucide-react": "^0.397.0",

View file

@ -28,38 +28,38 @@
"@babel/highlight" "^7.24.7" "@babel/highlight" "^7.24.7"
picocolors "^1.0.0" picocolors "^1.0.0"
"@babel/compat-data@^7.24.8": "@babel/compat-data@^7.25.2":
version "7.25.0" version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.0.tgz#6b226a5da3a686db3c30519750e071dce292ad95" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb"
integrity sha512-P4fwKI2mjEb3ZU5cnMJzvRsRKGBUcs8jvxIoRmr6ufAY9Xk2Bz7JubRTTivkw55c7WQJfTECeqYVa+HZ0FzREg== integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==
"@babel/core@^7.22.1": "@babel/core@^7.22.1":
version "7.24.9" version "7.25.2"
resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.9.tgz#dc07c9d307162c97fa9484ea997ade65841c7c82" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77"
integrity sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg== integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==
dependencies: dependencies:
"@ampproject/remapping" "^2.2.0" "@ampproject/remapping" "^2.2.0"
"@babel/code-frame" "^7.24.7" "@babel/code-frame" "^7.24.7"
"@babel/generator" "^7.24.9" "@babel/generator" "^7.25.0"
"@babel/helper-compilation-targets" "^7.24.8" "@babel/helper-compilation-targets" "^7.25.2"
"@babel/helper-module-transforms" "^7.24.9" "@babel/helper-module-transforms" "^7.25.2"
"@babel/helpers" "^7.24.8" "@babel/helpers" "^7.25.0"
"@babel/parser" "^7.24.8" "@babel/parser" "^7.25.0"
"@babel/template" "^7.24.7" "@babel/template" "^7.25.0"
"@babel/traverse" "^7.24.8" "@babel/traverse" "^7.25.2"
"@babel/types" "^7.24.9" "@babel/types" "^7.25.2"
convert-source-map "^2.0.0" convert-source-map "^2.0.0"
debug "^4.1.0" debug "^4.1.0"
gensync "^1.0.0-beta.2" gensync "^1.0.0-beta.2"
json5 "^2.2.3" json5 "^2.2.3"
semver "^6.3.1" semver "^6.3.1"
"@babel/generator@^7.24.9", "@babel/generator@^7.25.0": "@babel/generator@^7.25.0", "@babel/generator@^7.25.6":
version "7.25.0" version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.0.tgz#f858ddfa984350bc3d3b7f125073c9af6988f18e" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.6.tgz#0df1ad8cb32fe4d2b01d8bf437f153d19342a87c"
integrity sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw== integrity sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==
dependencies: dependencies:
"@babel/types" "^7.25.0" "@babel/types" "^7.25.6"
"@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/gen-mapping" "^0.3.5"
"@jridgewell/trace-mapping" "^0.3.25" "@jridgewell/trace-mapping" "^0.3.25"
jsesc "^2.5.1" jsesc "^2.5.1"
@ -71,28 +71,28 @@
dependencies: dependencies:
"@babel/types" "^7.24.7" "@babel/types" "^7.24.7"
"@babel/helper-compilation-targets@^7.24.8": "@babel/helper-compilation-targets@^7.25.2":
version "7.24.8" version "7.25.2"
resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz#b607c3161cd9d1744977d4f97139572fe778c271" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c"
integrity sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw== integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==
dependencies: dependencies:
"@babel/compat-data" "^7.24.8" "@babel/compat-data" "^7.25.2"
"@babel/helper-validator-option" "^7.24.8" "@babel/helper-validator-option" "^7.24.8"
browserslist "^4.23.1" browserslist "^4.23.1"
lru-cache "^5.1.1" lru-cache "^5.1.1"
semver "^6.3.1" semver "^6.3.1"
"@babel/helper-create-class-features-plugin@^7.25.0": "@babel/helper-create-class-features-plugin@^7.25.0":
version "7.25.0" version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz#a109bf9c3d58dfed83aaf42e85633c89f43a6253" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz#57eaf1af38be4224a9d9dd01ddde05b741f50e14"
integrity sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ== integrity sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ==
dependencies: dependencies:
"@babel/helper-annotate-as-pure" "^7.24.7" "@babel/helper-annotate-as-pure" "^7.24.7"
"@babel/helper-member-expression-to-functions" "^7.24.8" "@babel/helper-member-expression-to-functions" "^7.24.8"
"@babel/helper-optimise-call-expression" "^7.24.7" "@babel/helper-optimise-call-expression" "^7.24.7"
"@babel/helper-replace-supers" "^7.25.0" "@babel/helper-replace-supers" "^7.25.0"
"@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7"
"@babel/traverse" "^7.25.0" "@babel/traverse" "^7.25.4"
semver "^6.3.1" semver "^6.3.1"
"@babel/helper-member-expression-to-functions@^7.24.8": "@babel/helper-member-expression-to-functions@^7.24.8":
@ -111,15 +111,15 @@
"@babel/traverse" "^7.24.7" "@babel/traverse" "^7.24.7"
"@babel/types" "^7.24.7" "@babel/types" "^7.24.7"
"@babel/helper-module-transforms@^7.24.9": "@babel/helper-module-transforms@^7.25.2":
version "7.25.0" version "7.25.2"
resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.0.tgz#3ffc23c473a2769a7e40d3274495bd559fdd2ecc" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6"
integrity sha512-bIkOa2ZJYn7FHnepzr5iX9Kmz8FjIz4UKzJ9zhX3dnYuVW0xul9RuR3skBfoLu+FPTQw90EHW9rJsSZhyLQ3fQ== integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==
dependencies: dependencies:
"@babel/helper-module-imports" "^7.24.7" "@babel/helper-module-imports" "^7.24.7"
"@babel/helper-simple-access" "^7.24.7" "@babel/helper-simple-access" "^7.24.7"
"@babel/helper-validator-identifier" "^7.24.7" "@babel/helper-validator-identifier" "^7.24.7"
"@babel/traverse" "^7.25.0" "@babel/traverse" "^7.25.2"
"@babel/helper-optimise-call-expression@^7.24.7": "@babel/helper-optimise-call-expression@^7.24.7":
version "7.24.7" version "7.24.7"
@ -128,7 +128,7 @@
dependencies: dependencies:
"@babel/types" "^7.24.7" "@babel/types" "^7.24.7"
"@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.24.8": "@babel/helper-plugin-utils@^7.24.8":
version "7.24.8" version "7.24.8"
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878"
integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg==
@ -173,13 +173,13 @@
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d"
integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==
"@babel/helpers@^7.24.8": "@babel/helpers@^7.25.0":
version "7.25.0" version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.0.tgz#e69beb7841cb93a6505531ede34f34e6a073650a" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.6.tgz#57ee60141829ba2e102f30711ffe3afab357cc60"
integrity sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw== integrity sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==
dependencies: dependencies:
"@babel/template" "^7.25.0" "@babel/template" "^7.25.0"
"@babel/types" "^7.25.0" "@babel/types" "^7.25.6"
"@babel/highlight@^7.24.7": "@babel/highlight@^7.24.7":
version "7.24.7" version "7.24.7"
@ -191,22 +191,24 @@
js-tokens "^4.0.0" js-tokens "^4.0.0"
picocolors "^1.0.0" picocolors "^1.0.0"
"@babel/parser@^7.22.6", "@babel/parser@^7.24.8", "@babel/parser@^7.25.0": "@babel/parser@^7.22.6", "@babel/parser@^7.25.0", "@babel/parser@^7.25.6":
version "7.25.0" version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.0.tgz#9fdc9237504d797b6e7b8f66e78ea7f570d256ad" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.6.tgz#85660c5ef388cbbf6e3d2a694ee97a38f18afe2f"
integrity sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA== integrity sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==
dependencies:
"@babel/types" "^7.25.6"
"@babel/plugin-syntax-typescript@^7.24.7": "@babel/plugin-syntax-typescript@^7.24.7":
version "7.24.7" version "7.25.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz#58d458271b4d3b6bb27ee6ac9525acbb259bad1c" resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz#04db9ce5a9043d9c635e75ae7969a2cd50ca97ff"
integrity sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA== integrity sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg==
dependencies: dependencies:
"@babel/helper-plugin-utils" "^7.24.7" "@babel/helper-plugin-utils" "^7.24.8"
"@babel/plugin-transform-typescript@^7.22.5": "@babel/plugin-transform-typescript@^7.22.5":
version "7.25.0" version "7.25.2"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.0.tgz#56f47fb87b86a97caa9c7770920a1967d40ac86e" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.2.tgz#237c5d10de6d493be31637c6b9fa30b6c5461add"
integrity sha512-LZicxFzHIw+Sa3pzgMgSz6gdpsdkfiMObHUzhSIrwKF0+/rP/nuR49u79pSS+zIFJ1FeGeqQD2Dq4QGFbOVvSw== integrity sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A==
dependencies: dependencies:
"@babel/helper-annotate-as-pure" "^7.24.7" "@babel/helper-annotate-as-pure" "^7.24.7"
"@babel/helper-create-class-features-plugin" "^7.25.0" "@babel/helper-create-class-features-plugin" "^7.25.0"
@ -215,13 +217,13 @@
"@babel/plugin-syntax-typescript" "^7.24.7" "@babel/plugin-syntax-typescript" "^7.24.7"
"@babel/runtime@^7.13.10": "@babel/runtime@^7.13.10":
version "7.25.0" version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2"
integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==
dependencies: dependencies:
regenerator-runtime "^0.14.0" regenerator-runtime "^0.14.0"
"@babel/template@^7.24.7", "@babel/template@^7.25.0": "@babel/template@^7.25.0":
version "7.25.0" version "7.25.0"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a"
integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==
@ -230,23 +232,23 @@
"@babel/parser" "^7.25.0" "@babel/parser" "^7.25.0"
"@babel/types" "^7.25.0" "@babel/types" "^7.25.0"
"@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0": "@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0", "@babel/traverse@^7.25.2", "@babel/traverse@^7.25.4":
version "7.25.0" version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.0.tgz#e8533c0a57ed97921d1e5b20dd3db63d3efaebf5" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.6.tgz#04fad980e444f182ecf1520504941940a90fea41"
integrity sha512-ubALThHQy4GCf6mbb+5ZRNmLLCI7bJ3f8Q6LHBSRlSKSWj5a7dSUzJBLv3VuIhFrFPgjF4IzPF567YG/HSCdZA== integrity sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==
dependencies: dependencies:
"@babel/code-frame" "^7.24.7" "@babel/code-frame" "^7.24.7"
"@babel/generator" "^7.25.0" "@babel/generator" "^7.25.6"
"@babel/parser" "^7.25.0" "@babel/parser" "^7.25.6"
"@babel/template" "^7.25.0" "@babel/template" "^7.25.0"
"@babel/types" "^7.25.0" "@babel/types" "^7.25.6"
debug "^4.3.1" debug "^4.3.1"
globals "^11.1.0" globals "^11.1.0"
"@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.24.9", "@babel/types@^7.25.0": "@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.6":
version "7.25.0" version "7.25.6"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.0.tgz#e6e3656c581f28da8452ed4f69e38008ec0ba41b" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.6.tgz#893942ddb858f32ae7a004ec9d3a76b3463ef8e6"
integrity sha512-LcnxQSsd9aXOIgmmSpvZ/1yo46ra2ESYyqLcryaBZOghxy5qqOBjvCWP5JfkI8yl9rlxRgdLTTMCQQRcN2hdCg== integrity sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==
dependencies: dependencies:
"@babel/helper-string-parser" "^7.24.8" "@babel/helper-string-parser" "^7.24.8"
"@babel/helper-validator-identifier" "^7.24.7" "@babel/helper-validator-identifier" "^7.24.7"
@ -285,19 +287,19 @@
integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==
"@floating-ui/core@^1.6.0": "@floating-ui/core@^1.6.0":
version "1.6.5" version "1.6.7"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.5.tgz#102335cac0d22035b04d70ca5ff092d2d1a26f2b" resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.7.tgz#7602367795a390ff0662efd1c7ae8ca74e75fb12"
integrity sha512-8GrTWmoFhm5BsMZOTHeGD2/0FLKLQQHvO/ZmQga4tKempYRLz8aqJGqXVuQgisnMObq2YZ2SgkwctN1LOOxcqA== integrity sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g==
dependencies: dependencies:
"@floating-ui/utils" "^0.2.5" "@floating-ui/utils" "^0.2.7"
"@floating-ui/dom@^1.0.0": "@floating-ui/dom@^1.0.0":
version "1.6.8" version "1.6.10"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.8.tgz#45e20532b6d8a061b356a4fb336022cf2609754d" resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.10.tgz#b74c32f34a50336c86dcf1f1c845cf3a39e26d6f"
integrity sha512-kx62rP19VZ767Q653wsP1XZCGIirkE09E0QUGNYTM/ttbbQHqcGPdSfWFxUyyNLc/W6aoJRBajOSXhP6GXjC0Q== integrity sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A==
dependencies: dependencies:
"@floating-ui/core" "^1.6.0" "@floating-ui/core" "^1.6.0"
"@floating-ui/utils" "^0.2.5" "@floating-ui/utils" "^0.2.7"
"@floating-ui/react-dom@^2.0.0": "@floating-ui/react-dom@^2.0.0":
version "2.1.1" version "2.1.1"
@ -306,10 +308,10 @@
dependencies: dependencies:
"@floating-ui/dom" "^1.0.0" "@floating-ui/dom" "^1.0.0"
"@floating-ui/utils@^0.2.5": "@floating-ui/utils@^0.2.7":
version "0.2.5" version "0.2.7"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.5.tgz#105c37d9d9620ce69b7f692a20c821bf1ad2cbf9" resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.7.tgz#d0ece53ce99ab5a8e37ebdfe5e32452a2bfc073e"
integrity sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ== integrity sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA==
"@hookform/resolvers@^3.9.0": "@hookform/resolvers@^3.9.0":
version "3.9.0" version "3.9.0"
@ -457,6 +459,11 @@
"@nodelib/fs.scandir" "2.1.5" "@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0" fastq "^1.6.0"
"@nolyfill/is-core-module@1.0.39":
version "1.0.39"
resolved "https://registry.yarnpkg.com/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz#3dc35ba0f1e66b403c00b39344f870298ebb1c8e"
integrity sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==
"@phosphor-icons/react@^2.1.7": "@phosphor-icons/react@^2.1.7":
version "2.1.7" version "2.1.7"
resolved "https://registry.yarnpkg.com/@phosphor-icons/react/-/react-2.1.7.tgz#b11a4b25849b7e3849970b688d9fe91e5d4fd8d7" resolved "https://registry.yarnpkg.com/@phosphor-icons/react/-/react-2.1.7.tgz#b11a4b25849b7e3849970b688d9fe91e5d4fd8d7"
@ -1220,9 +1227,9 @@
integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg== integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg==
"@radix-ui/themes@^3.1.1": "@radix-ui/themes@^3.1.1":
version "3.1.1" version "3.1.3"
resolved "https://registry.yarnpkg.com/@radix-ui/themes/-/themes-3.1.1.tgz#a444f181975b8550f548c989ef3a432908c51075" resolved "https://registry.yarnpkg.com/@radix-ui/themes/-/themes-3.1.3.tgz#9b4287d4e60c9a175e0e453ba620d693fadf6f7e"
integrity sha512-G+j+x+7kyqQXnn+ftlNPgk1DdZ8h/vVZnLsG4hZB0Mxw4fdKCh1tThQuXDSBNWhFt/vTG79BMzRMiflovENrmA== integrity sha512-GJt4r7Vh0w4yiGuqOKLvrZXLEAxUWfEUUtdj17rCfi+P/zpKUc7TsXro8GftA2Y1tm78Cx9x1HKt23dHc/WqIA==
dependencies: dependencies:
"@radix-ui/colors" "3.0.0" "@radix-ui/colors" "3.0.0"
"@radix-ui/primitive" "1.1.0" "@radix-ui/primitive" "1.1.0"
@ -1260,6 +1267,11 @@
classnames "2.3.2" classnames "2.3.2"
react-remove-scroll-bar "2.3.4" react-remove-scroll-bar "2.3.4"
"@rtsao/scc@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@rtsao/scc/-/scc-1.1.0.tgz#927dd2fae9bc3361403ac2c7a00c32ddce9ad7e8"
integrity sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==
"@rushstack/eslint-patch@^1.3.3": "@rushstack/eslint-patch@^1.3.3":
version "1.10.4" version "1.10.4"
resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz#427d5549943a9c6fce808e39ea64dbe60d4047f1" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz#427d5549943a9c6fce808e39ea64dbe60d4047f1"
@ -1338,11 +1350,11 @@
integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg== integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==
"@types/node@^20": "@types/node@^20":
version "20.14.12" version "20.16.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.12.tgz#129d7c3a822cb49fc7ff661235f19cfefd422b49" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.16.5.tgz#d43c7f973b32ffdf9aa7bd4f80e1072310fd7a53"
integrity sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ== integrity sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA==
dependencies: dependencies:
undici-types "~5.26.4" undici-types "~6.19.2"
"@types/prop-types@*": "@types/prop-types@*":
version "15.7.12" version "15.7.12"
@ -1357,9 +1369,9 @@
"@types/react" "*" "@types/react" "*"
"@types/react@*", "@types/react@^18": "@types/react@*", "@types/react@^18":
version "18.3.3" version "18.3.5"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.3.tgz#9679020895318b0915d7a3ab004d92d33375c45f" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.5.tgz#5f524c2ad2089c0ff372bbdabc77ca2c4dbadf8f"
integrity sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw== integrity sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA==
dependencies: dependencies:
"@types/prop-types" "*" "@types/prop-types" "*"
csstype "^3.0.2" csstype "^3.0.2"
@ -1533,7 +1545,7 @@ array-buffer-byte-length@^1.0.0, array-buffer-byte-length@^1.0.1:
call-bind "^1.0.5" call-bind "^1.0.5"
is-array-buffer "^3.0.4" is-array-buffer "^3.0.4"
array-includes@^3.1.6, array-includes@^3.1.7, array-includes@^3.1.8: array-includes@^3.1.6, array-includes@^3.1.8:
version "3.1.8" version "3.1.8"
resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d"
integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==
@ -1562,7 +1574,7 @@ array.prototype.findlast@^1.2.5:
es-object-atoms "^1.0.0" es-object-atoms "^1.0.0"
es-shim-unscopables "^1.0.2" es-shim-unscopables "^1.0.2"
array.prototype.findlastindex@^1.2.3: array.prototype.findlastindex@^1.2.5:
version "1.2.5" version "1.2.5"
resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d" resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d"
integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ== integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==
@ -1632,15 +1644,15 @@ ast-types@^0.16.1:
tslib "^2.0.1" tslib "^2.0.1"
autoprefixer@^10.4.19: autoprefixer@^10.4.19:
version "10.4.19" version "10.4.20"
resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.19.tgz#ad25a856e82ee9d7898c59583c1afeb3fa65f89f" resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.20.tgz#5caec14d43976ef42e32dcb4bd62878e96be5b3b"
integrity sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew== integrity sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==
dependencies: dependencies:
browserslist "^4.23.0" browserslist "^4.23.3"
caniuse-lite "^1.0.30001599" caniuse-lite "^1.0.30001646"
fraction.js "^4.3.7" fraction.js "^4.3.7"
normalize-range "^0.1.2" normalize-range "^0.1.2"
picocolors "^1.0.0" picocolors "^1.0.1"
postcss-value-parser "^4.2.0" postcss-value-parser "^4.2.0"
available-typed-arrays@^1.0.7: available-typed-arrays@^1.0.7:
@ -1650,17 +1662,15 @@ available-typed-arrays@^1.0.7:
dependencies: dependencies:
possible-typed-array-names "^1.0.0" possible-typed-array-names "^1.0.0"
axe-core@^4.9.1: axe-core@^4.10.0:
version "4.9.1" version "4.10.0"
resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.9.1.tgz#fcd0f4496dad09e0c899b44f6c4bb7848da912ae" resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59"
integrity sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw== integrity sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==
axobject-query@~3.1.1: axobject-query@^4.1.0:
version "3.1.1" version "4.1.0"
resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.1.1.tgz#3b6e5c6d4e43ca7ba51c5babf99d22a9c68485e1" resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee"
integrity sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg== integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==
dependencies:
deep-equal "^2.0.5"
balanced-match@^1.0.0: balanced-match@^1.0.0:
version "1.0.2" version "1.0.2"
@ -1708,14 +1718,14 @@ braces@^3.0.3, braces@~3.0.2:
dependencies: dependencies:
fill-range "^7.1.1" fill-range "^7.1.1"
browserslist@^4.23.0, browserslist@^4.23.1: browserslist@^4.23.1, browserslist@^4.23.3:
version "4.23.2" version "4.23.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.2.tgz#244fe803641f1c19c28c48c4b6ec9736eb3d32ed" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800"
integrity sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA== integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==
dependencies: dependencies:
caniuse-lite "^1.0.30001640" caniuse-lite "^1.0.30001646"
electron-to-chromium "^1.4.820" electron-to-chromium "^1.5.4"
node-releases "^2.0.14" node-releases "^2.0.18"
update-browserslist-db "^1.1.0" update-browserslist-db "^1.1.0"
buffer@^6.0.3: buffer@^6.0.3:
@ -1754,10 +1764,10 @@ camelcase-css@^2.0.1:
resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5"
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001599, caniuse-lite@^1.0.30001640: caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001646:
version "1.0.30001643" version "1.0.30001658"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz#9c004caef315de9452ab970c3da71085f8241dbd" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001658.tgz#b5f7be8ac748a049ab06aa1cf7a1408d83f074ec"
integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg== integrity sha512-N2YVqWbJELVdrnsW5p+apoQyYt51aBMSsBZki1XZEfeBCexcM/sf4xiAHcXQBkuOwJBXtWF7aW1sYX6tKebPHw==
chalk@5.2.0: chalk@5.2.0:
version "5.2.0" version "5.2.0"
@ -2003,12 +2013,12 @@ data-view-byte-offset@^1.0.0:
es-errors "^1.3.0" es-errors "^1.3.0"
is-data-view "^1.0.1" is-data-view "^1.0.1"
debug@4, debug@^4, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@~4.3.4: debug@4, debug@^4, debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5, debug@~4.3.6:
version "4.3.5" version "4.3.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52"
integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==
dependencies: dependencies:
ms "2.1.2" ms "^2.1.3"
debug@^3.2.7: debug@^3.2.7:
version "3.2.7" version "3.2.7"
@ -2122,15 +2132,15 @@ eastasianwidth@^0.2.0:
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
electron-to-chromium@^1.4.820: electron-to-chromium@^1.5.4:
version "1.5.2" version "1.5.17"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.2.tgz#6126ad229ce45e781ec54ca40db0504787f23d19" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.17.tgz#292da718d3d96961d022e49bb843e0c4ea10be70"
integrity sha512-kc4r3U3V3WLaaZqThjYz/Y6z8tJe+7K0bbjUVo3i+LWIypVdMx5nXCkwRe6SWbY6ILqLdc1rKcKmr3HoH7wjSQ== integrity sha512-Q6Q+04tjC2KJ8qsSOSgovvhWcv5t+SmpH6/YfAWmhpE5/r+zw6KQy1/yWVFFNyEBvy68twTTXr2d5eLfCq7QIw==
emoji-regex@^10.3.0: emoji-regex@^10.3.0:
version "10.3.0" version "10.4.0"
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.3.0.tgz#76998b9268409eb3dae3de989254d456e70cfe23" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.4.0.tgz#03553afea80b3975749cfcb36f776ca268e413d4"
integrity sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw== integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==
emoji-regex@^8.0.0: emoji-regex@^8.0.0:
version "8.0.0" version "8.0.0"
@ -2142,7 +2152,7 @@ emoji-regex@^9.2.2:
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72"
integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==
enhanced-resolve@^5.12.0: enhanced-resolve@^5.15.0:
version "5.17.1" version "5.17.1"
resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15"
integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==
@ -2299,9 +2309,9 @@ es-to-primitive@^1.2.1:
is-symbol "^1.0.2" is-symbol "^1.0.2"
escalade@^3.1.2: escalade@^3.1.2:
version "3.1.2" version "3.2.0"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5"
integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==
escape-string-regexp@^1.0.5: escape-string-regexp@^1.0.5:
version "1.0.5" version "1.0.5"
@ -2343,59 +2353,61 @@ eslint-import-resolver-node@^0.3.6, eslint-import-resolver-node@^0.3.9:
resolve "^1.22.4" resolve "^1.22.4"
eslint-import-resolver-typescript@^3.5.2: eslint-import-resolver-typescript@^3.5.2:
version "3.6.1" version "3.6.3"
resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz#7b983680edd3f1c5bce1a5829ae0bc2d57fe9efa" resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.3.tgz#bb8e388f6afc0f940ce5d2c5fd4a3d147f038d9e"
integrity sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg== integrity sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA==
dependencies: dependencies:
debug "^4.3.4" "@nolyfill/is-core-module" "1.0.39"
enhanced-resolve "^5.12.0" debug "^4.3.5"
eslint-module-utils "^2.7.4" enhanced-resolve "^5.15.0"
fast-glob "^3.3.1" eslint-module-utils "^2.8.1"
get-tsconfig "^4.5.0" fast-glob "^3.3.2"
is-core-module "^2.11.0" get-tsconfig "^4.7.5"
is-bun-module "^1.0.2"
is-glob "^4.0.3" is-glob "^4.0.3"
eslint-module-utils@^2.7.4, eslint-module-utils@^2.8.0: eslint-module-utils@^2.8.1, eslint-module-utils@^2.9.0:
version "2.8.1" version "2.11.0"
resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz#52f2404300c3bd33deece9d7372fb337cc1d7c34" resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.11.0.tgz#b99b211ca4318243f09661fae088f373ad5243c4"
integrity sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q== integrity sha512-gbBE5Hitek/oG6MUVj6sFuzEjA/ClzNflVrLovHi/JgLdC7fiN5gLAY1WIPW1a0V5I999MnsrvVrCOGmmVqDBQ==
dependencies: dependencies:
debug "^3.2.7" debug "^3.2.7"
eslint-plugin-import@^2.28.1: eslint-plugin-import@^2.28.1:
version "2.29.1" version "2.30.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz#d45b37b5ef5901d639c15270d74d46d161150643" resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz#21ceea0fc462657195989dd780e50c92fe95f449"
integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw== integrity sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==
dependencies: dependencies:
array-includes "^3.1.7" "@rtsao/scc" "^1.1.0"
array.prototype.findlastindex "^1.2.3" array-includes "^3.1.8"
array.prototype.findlastindex "^1.2.5"
array.prototype.flat "^1.3.2" array.prototype.flat "^1.3.2"
array.prototype.flatmap "^1.3.2" array.prototype.flatmap "^1.3.2"
debug "^3.2.7" debug "^3.2.7"
doctrine "^2.1.0" doctrine "^2.1.0"
eslint-import-resolver-node "^0.3.9" eslint-import-resolver-node "^0.3.9"
eslint-module-utils "^2.8.0" eslint-module-utils "^2.9.0"
hasown "^2.0.0" hasown "^2.0.2"
is-core-module "^2.13.1" is-core-module "^2.15.1"
is-glob "^4.0.3" is-glob "^4.0.3"
minimatch "^3.1.2" minimatch "^3.1.2"
object.fromentries "^2.0.7" object.fromentries "^2.0.8"
object.groupby "^1.0.1" object.groupby "^1.0.3"
object.values "^1.1.7" object.values "^1.2.0"
semver "^6.3.1" semver "^6.3.1"
tsconfig-paths "^3.15.0" tsconfig-paths "^3.15.0"
eslint-plugin-jsx-a11y@^6.7.1: eslint-plugin-jsx-a11y@^6.7.1:
version "6.9.0" version "6.10.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.9.0.tgz#67ab8ff460d4d3d6a0b4a570e9c1670a0a8245c8" resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.0.tgz#36fb9dead91cafd085ddbe3829602fb10ef28339"
integrity sha512-nOFOCaJG2pYqORjK19lqPqxMO/JpvdCZdPtNdxY3kvom3jTvkAbOvQvD8wuD0G8BYR0IGAGYDlzqWJOh/ybn2g== integrity sha512-ySOHvXX8eSN6zz8Bywacm7CvGNhUtdjvqfQDVe6020TUK34Cywkw7m0KsCCk1Qtm9G1FayfTN1/7mMYnYO2Bhg==
dependencies: dependencies:
aria-query "~5.1.3" aria-query "~5.1.3"
array-includes "^3.1.8" array-includes "^3.1.8"
array.prototype.flatmap "^1.3.2" array.prototype.flatmap "^1.3.2"
ast-types-flow "^0.0.8" ast-types-flow "^0.0.8"
axe-core "^4.9.1" axe-core "^4.10.0"
axobject-query "~3.1.1" axobject-query "^4.1.0"
damerau-levenshtein "^1.0.8" damerau-levenshtein "^1.0.8"
emoji-regex "^9.2.2" emoji-regex "^9.2.2"
es-iterator-helpers "^1.0.19" es-iterator-helpers "^1.0.19"
@ -2421,9 +2433,9 @@ eslint-plugin-prettier@^5.1.3:
integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ== integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==
eslint-plugin-react@^7.33.2: eslint-plugin-react@^7.33.2:
version "7.35.0" version "7.35.2"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz#00b1e4559896710e58af6358898f2ff917ea4c41" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.35.2.tgz#d32500d3ec268656d5071918bfec78cfd8b070ed"
integrity sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA== integrity sha512-Rbj2R9zwP2GYNcIak4xoAMV57hrBh3hTaR0k7hVjwCQgryE/pw5px4b13EYjduOI0hfXyZhwBxaGpOTbWSGzKQ==
dependencies: dependencies:
array-includes "^3.1.8" array-includes "^3.1.8"
array.prototype.findlast "^1.2.5" array.prototype.findlast "^1.2.5"
@ -2584,7 +2596,7 @@ fast-diff@^1.1.2:
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0" resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.3.0.tgz#ece407fa550a64d638536cd727e129c61616e0f0"
integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw== integrity sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==
fast-glob@^3.2.12, fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.1, fast-glob@^3.3.2: fast-glob@^3.2.12, fast-glob@^3.2.9, fast-glob@^3.3.0, fast-glob@^3.3.2:
version "3.3.2" version "3.3.2"
resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129"
integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
@ -2664,9 +2676,9 @@ for-each@^0.3.3:
is-callable "^1.1.3" is-callable "^1.1.3"
foreground-child@^3.1.0: foreground-child@^3.1.0:
version "3.2.1" version "3.3.0"
resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.2.1.tgz#767004ccf3a5b30df39bed90718bab43fe0a59f7" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77"
integrity sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA== integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==
dependencies: dependencies:
cross-spawn "^7.0.0" cross-spawn "^7.0.0"
signal-exit "^4.0.1" signal-exit "^4.0.1"
@ -2707,7 +2719,7 @@ function-bind@^1.1.2:
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c"
integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==
function.prototype.name@^1.1.5, function.prototype.name@^1.1.6: function.prototype.name@^1.1.6:
version "1.1.6" version "1.1.6"
resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd"
integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==
@ -2767,10 +2779,10 @@ get-symbol-description@^1.0.2:
es-errors "^1.3.0" es-errors "^1.3.0"
get-intrinsic "^1.2.4" get-intrinsic "^1.2.4"
get-tsconfig@^4.5.0: get-tsconfig@^4.7.5:
version "4.7.6" version "4.8.0"
resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.6.tgz#118fd5b7b9bae234cc7705a00cd771d7eb65d62a" resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.8.0.tgz#125dc13a316f61650a12b20c97c11b8fd996fedd"
integrity sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA== integrity sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw==
dependencies: dependencies:
resolve-pkg-maps "^1.0.0" resolve-pkg-maps "^1.0.0"
@ -2942,9 +2954,9 @@ human-signals@^5.0.0:
integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==
husky@^9.0.11: husky@^9.0.11:
version "9.1.3" version "9.1.5"
resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.3.tgz#46cddff01f9a551f87b39accc67860bce5d00680" resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.5.tgz#2b6edede53ee1adbbd3a3da490628a23f5243b83"
integrity sha512-ET3TQmQgdIu0pt+jKkpo5oGyg/4MQZpG6xcam5J5JyNJV+CBT23OBpCF15bKHKycRyMH9k6ONy8g2HdGIsSkMQ== integrity sha512-rowAVRUBfI0b4+niA4SJMhfQwc107VLkBUgEYYAOQAbqDCnra1nYh83hF/MDmhYs9t9n1E3DuKOrs2LYNC+0Ag==
ieee754@^1.2.1: ieee754@^1.2.1:
version "1.2.1" version "1.2.1"
@ -2957,9 +2969,9 @@ ignore-by-default@^1.0.1:
integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==
ignore@^5.2.0: ignore@^5.2.0:
version "5.3.1" version "5.3.2"
resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5"
integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==
import-fresh@^3.2.1, import-fresh@^3.3.0: import-fresh@^3.2.1, import-fresh@^3.3.0:
version "3.3.0" version "3.3.0"
@ -3001,10 +3013,10 @@ internal-slot@^1.0.4, internal-slot@^1.0.7:
hasown "^2.0.0" hasown "^2.0.0"
side-channel "^1.0.4" side-channel "^1.0.4"
intl-tel-input@^23.8.0: intl-tel-input@^23.8.1:
version "23.8.0" version "23.9.3"
resolved "https://registry.yarnpkg.com/intl-tel-input/-/intl-tel-input-23.8.0.tgz#37bec095605516aa72529b3da11335b253b65b2f" resolved "https://registry.yarnpkg.com/intl-tel-input/-/intl-tel-input-23.9.3.tgz#3870c78c16655bdc13e18cae557efcaa43dce719"
integrity sha512-lx8Sz5LfVyIXyjWbfjno89o4qHfIuWulctGaWbP2RKKnHvagdt9gdibCsv9uEH7izb/yjB6Nst0sRo988/lhpw== integrity sha512-vu/Hm825wPlkg2cOtWWgG5fRw2+M7G0sUhQKdZgP4UMjd+UKmCNBcr9gdz6OnXh9kmW5GDMCy7ArVdYY0yjSxQ==
invariant@^2.2.4: invariant@^2.2.4:
version "2.2.4" version "2.2.4"
@ -3063,15 +3075,22 @@ is-boolean-object@^1.1.0:
call-bind "^1.0.2" call-bind "^1.0.2"
has-tostringtag "^1.0.0" has-tostringtag "^1.0.0"
is-bun-module@^1.0.2:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-bun-module/-/is-bun-module-1.1.0.tgz#a66b9830869437f6cdad440ba49ab6e4dc837269"
integrity sha512-4mTAVPlrXpaN3jtF0lsnPCMGnq4+qZjVIKq0HCpfcqf8OC1SM5oATCIAPM5V5FN05qp2NNnFndphmdZS9CV3hA==
dependencies:
semver "^7.6.3"
is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
version "1.2.7" version "1.2.7"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
is-core-module@^2.11.0, is-core-module@^2.13.0, is-core-module@^2.13.1: is-core-module@^2.13.0, is-core-module@^2.15.1:
version "2.15.0" version "2.15.1"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37"
integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==
dependencies: dependencies:
hasown "^2.0.2" hasown "^2.0.2"
@ -3387,16 +3406,16 @@ levn@^0.4.1:
type-check "~0.4.0" type-check "~0.4.0"
libphonenumber-js@^1.11.4: libphonenumber-js@^1.11.4:
version "1.11.4" version "1.11.7"
resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.4.tgz#e63fe553f45661b30bb10bb8c82c9cf2b22ec32a" resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.7.tgz#efe4fcf816e1982925e9c800d0013b0ee99b8283"
integrity sha512-F/R50HQuWWYcmU/esP5jrH5LiWYaN7DpN0a/99U8+mnGGtnx8kmRE+649dQh3v+CowXXZc8vpkf5AmYkO0AQ7Q== integrity sha512-x2xON4/Qg2bRIS11KIN9yCNYUjhtiEjNyptjX0mX+pyKHecxuJVLIpfX1lq9ZD6CrC/rB+y4GBi18c6CEcUR+A==
lilconfig@^2.1.0: lilconfig@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52"
integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ== integrity sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==
lilconfig@^3.0.0, lilconfig@~3.1.1: lilconfig@^3.0.0, lilconfig@~3.1.2:
version "3.1.2" version "3.1.2"
resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.2.tgz#e4a7c3cb549e3a606c8dcc32e5ae1005e62c05cb" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.2.tgz#e4a7c3cb549e3a606c8dcc32e5ae1005e62c05cb"
integrity sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow== integrity sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==
@ -3414,30 +3433,30 @@ linkify-it@^5.0.0:
uc.micro "^2.0.0" uc.micro "^2.0.0"
lint-staged@^15.2.7: lint-staged@^15.2.7:
version "15.2.7" version "15.2.10"
resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.7.tgz#97867e29ed632820c0fb90be06cd9ed384025649" resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.10.tgz#92ac222f802ba911897dcf23671da5bb80643cd2"
integrity sha512-+FdVbbCZ+yoh7E/RosSdqKJyUM2OEjTciH0TFNkawKgvFp1zbGlEC39RADg+xKBG1R4mhoH2j85myBQZ5wR+lw== integrity sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg==
dependencies: dependencies:
chalk "~5.3.0" chalk "~5.3.0"
commander "~12.1.0" commander "~12.1.0"
debug "~4.3.4" debug "~4.3.6"
execa "~8.0.1" execa "~8.0.1"
lilconfig "~3.1.1" lilconfig "~3.1.2"
listr2 "~8.2.1" listr2 "~8.2.4"
micromatch "~4.0.7" micromatch "~4.0.8"
pidtree "~0.6.0" pidtree "~0.6.0"
string-argv "~0.3.2" string-argv "~0.3.2"
yaml "~2.4.2" yaml "~2.5.0"
listr2@~8.2.1: listr2@~8.2.4:
version "8.2.3" version "8.2.4"
resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.2.3.tgz#c494bb89b34329cf900e4e0ae8aeef9081d7d7a5" resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.2.4.tgz#486b51cbdb41889108cb7e2c90eeb44519f5a77f"
integrity sha512-Lllokma2mtoniUOS94CcOErHWAug5iu7HOmDrvWgpw8jyQH2fomgB+7lZS4HWZxytUuQwkGOwe49FvwVaA85Xw== integrity sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==
dependencies: dependencies:
cli-truncate "^4.0.0" cli-truncate "^4.0.0"
colorette "^2.0.20" colorette "^2.0.20"
eventemitter3 "^5.0.1" eventemitter3 "^5.0.1"
log-update "^6.0.0" log-update "^6.1.0"
rfdc "^1.4.1" rfdc "^1.4.1"
wrap-ansi "^9.0.0" wrap-ansi "^9.0.0"
@ -3481,7 +3500,7 @@ log-symbols@^5.1.0:
chalk "^5.0.0" chalk "^5.0.0"
is-unicode-supported "^1.1.0" is-unicode-supported "^1.1.0"
log-update@^6.0.0: log-update@^6.1.0:
version "6.1.0" version "6.1.0"
resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.1.0.tgz#1a04ff38166f94647ae1af562f4bd6a15b1b7cd4" resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.1.0.tgz#1a04ff38166f94647ae1af562f4bd6a15b1b7cd4"
integrity sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w== integrity sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==
@ -3550,10 +3569,10 @@ merge2@^1.3.0, merge2@^1.4.1:
resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
micromatch@^4.0.4, micromatch@^4.0.5, micromatch@~4.0.7: micromatch@^4.0.4, micromatch@^4.0.5, micromatch@~4.0.8:
version "4.0.7" version "4.0.8"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202"
integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==
dependencies: dependencies:
braces "^3.0.3" braces "^3.0.3"
picomatch "^2.3.1" picomatch "^2.3.1"
@ -3616,12 +3635,7 @@ mkdirp@^2.1.6:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19"
integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A==
ms@2.1.2: ms@^2.1.1, ms@^2.1.3:
version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
ms@^2.1.1:
version "2.1.3" version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
@ -3682,7 +3696,7 @@ node-fetch@^3.3.0:
fetch-blob "^3.1.4" fetch-blob "^3.1.4"
formdata-polyfill "^4.0.10" formdata-polyfill "^4.0.10"
node-releases@^2.0.14: node-releases@^2.0.18:
version "2.0.18" version "2.0.18"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f"
integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==
@ -3767,7 +3781,7 @@ object.entries@^1.1.8:
define-properties "^1.2.1" define-properties "^1.2.1"
es-object-atoms "^1.0.0" es-object-atoms "^1.0.0"
object.fromentries@^2.0.7, object.fromentries@^2.0.8: object.fromentries@^2.0.8:
version "2.0.8" version "2.0.8"
resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65"
integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==
@ -3777,7 +3791,7 @@ object.fromentries@^2.0.7, object.fromentries@^2.0.8:
es-abstract "^1.23.2" es-abstract "^1.23.2"
es-object-atoms "^1.0.0" es-object-atoms "^1.0.0"
object.groupby@^1.0.1: object.groupby@^1.0.3:
version "1.0.3" version "1.0.3"
resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e"
integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==
@ -3786,7 +3800,7 @@ object.groupby@^1.0.1:
define-properties "^1.2.1" define-properties "^1.2.1"
es-abstract "^1.23.2" es-abstract "^1.23.2"
object.values@^1.1.6, object.values@^1.1.7, object.values@^1.2.0: object.values@^1.1.6, object.values@^1.2.0:
version "1.2.0" version "1.2.0"
resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b"
integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==
@ -3930,9 +3944,9 @@ path-type@^4.0.0:
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
picocolors@^1.0.0, picocolors@^1.0.1: picocolors@^1.0.0, picocolors@^1.0.1:
version "1.0.1" version "1.1.0"
resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59"
integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
version "2.3.1" version "2.3.1"
@ -3991,9 +4005,9 @@ postcss-nested@^6.0.1:
postcss-selector-parser "^6.1.1" postcss-selector-parser "^6.1.1"
postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.1.1: postcss-selector-parser@^6.0.11, postcss-selector-parser@^6.1.1:
version "6.1.1" version "6.1.2"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz#5be94b277b8955904476a2400260002ce6c56e38" resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de"
integrity sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg== integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==
dependencies: dependencies:
cssesc "^3.0.0" cssesc "^3.0.0"
util-deprecate "^1.0.2" util-deprecate "^1.0.2"
@ -4013,9 +4027,9 @@ postcss@8.4.31:
source-map-js "^1.0.2" source-map-js "^1.0.2"
postcss@^8.4.23, postcss@^8.4.38: postcss@^8.4.23, postcss@^8.4.38:
version "8.4.40" version "8.4.45"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.40.tgz#eb81f2a4dd7668ed869a6db25999e02e9ad909d8" resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.45.tgz#538d13d89a16ef71edbf75d895284ae06b79e603"
integrity sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q== integrity sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==
dependencies: dependencies:
nanoid "^3.3.7" nanoid "^3.3.7"
picocolors "^1.0.1" picocolors "^1.0.1"
@ -4084,9 +4098,9 @@ react-dom@^18:
scheduler "^0.23.2" scheduler "^0.23.2"
react-hook-form@^7.52.1: react-hook-form@^7.52.1:
version "7.52.1" version "7.53.0"
resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.52.1.tgz#ec2c96437b977f8b89ae2d541a70736c66284852" resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.53.0.tgz#3cf70951bf41fa95207b34486203ebefbd3a05ab"
integrity sha512-uNKIhaoICJ5KQALYZ4TOaOLElyM+xipord+Ha3crEFhTntdLvWZqVY49Wqd/0GiVCA/f9NjemLeiNPjG7Hpurg== integrity sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ==
react-is@^16.13.1: react-is@^16.13.1:
version "16.13.1" version "16.13.1"
@ -4313,7 +4327,7 @@ semver@^6.3.1:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4"
integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==
semver@^7.5.3, semver@^7.5.4: semver@^7.5.3, semver@^7.5.4, semver@^7.6.3:
version "7.6.3" version "7.6.3"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143"
integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==
@ -4647,9 +4661,9 @@ synckit@^0.9.1:
tslib "^2.6.2" tslib "^2.6.2"
tailwind-merge@^2.3.0: tailwind-merge@^2.3.0:
version "2.4.0" version "2.5.2"
resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.4.0.tgz#1345209dc1f484f15159c9180610130587703042" resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.5.2.tgz#000f05a703058f9f9f3829c644235f81d4c08a1f"
integrity sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A== integrity sha512-kjEBm+pvD+6eAwzJL2Bi+02/9LFLal1Gs61+QB7HvTfQQ0aXwC5LGT8PEt1gS0CWKktKe6ysPTAy3cBC5MeiIg==
tailwindcss-animate@^1.0.7: tailwindcss-animate@^1.0.7:
version "1.0.7" version "1.0.7"
@ -4657,9 +4671,9 @@ tailwindcss-animate@^1.0.7:
integrity sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA== integrity sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==
tailwindcss@^3.4.6: tailwindcss@^3.4.6:
version "3.4.7" version "3.4.10"
resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.7.tgz#6092f18767f5933f59375b9afe558e592fc77201" resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.10.tgz#70442d9aeb78758d1f911af29af8255ecdb8ffef"
integrity sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ== integrity sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w==
dependencies: dependencies:
"@alloc/quick-lru" "^5.2.0" "@alloc/quick-lru" "^5.2.0"
arg "^5.0.2" arg "^5.0.2"
@ -4768,9 +4782,9 @@ tsconfig-paths@^4.2.0:
strip-bom "^3.0.0" strip-bom "^3.0.0"
tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.2: tslib@^2.0.0, tslib@^2.0.1, tslib@^2.1.0, tslib@^2.4.0, tslib@^2.6.2:
version "2.6.3" version "2.7.0"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01"
integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==
type-check@^0.4.0, type-check@~0.4.0: type-check@^0.4.0, type-check@~0.4.0:
version "0.4.0" version "0.4.0"
@ -4853,10 +4867,10 @@ undefsafe@^2.0.5:
resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.5.tgz#38733b9327bdcd226db889fb723a6efd162e6e2c"
integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==
undici-types@~5.26.4: undici-types@~6.19.2:
version "5.26.5" version "6.19.8"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==
universalify@^2.0.0: universalify@^2.0.0:
version "2.0.1" version "2.0.1"
@ -4904,9 +4918,9 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2:
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
vaul@^0.9.1: vaul@^0.9.1:
version "0.9.1" version "0.9.2"
resolved "https://registry.yarnpkg.com/vaul/-/vaul-0.9.1.tgz#3640198e04636b209b1f907fcf3079bec6ecc66b" resolved "https://registry.yarnpkg.com/vaul/-/vaul-0.9.2.tgz#fe7ad8a0acf0863b9bc7e956da27a8ce6169ab7c"
integrity sha512-fAhd7i4RNMinx+WEm6pF3nOl78DFkAazcN04ElLPFF9BMCNGbY/kou8UMhIcicm0rJCNePJP0Yyza60gGOD0Jw== integrity sha512-m2A7UgAU/JMWiwUhmARK8LMvEfXiudA4trJxfZF5AtH2uBTgN855msZ2yjPnUDfa7i5glocMYLSfML8wriBtBA==
dependencies: dependencies:
"@radix-ui/react-dialog" "^1.0.4" "@radix-ui/react-dialog" "^1.0.4"
@ -4934,12 +4948,12 @@ which-boxed-primitive@^1.0.2:
is-symbol "^1.0.3" is-symbol "^1.0.3"
which-builtin-type@^1.1.3: which-builtin-type@^1.1.3:
version "1.1.3" version "1.1.4"
resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.3.tgz#b1b8443707cc58b6e9bf98d32110ff0c2cbd029b" resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.4.tgz#592796260602fc3514a1b5ee7fa29319b72380c3"
integrity sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw== integrity sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==
dependencies: dependencies:
function.prototype.name "^1.1.5" function.prototype.name "^1.1.6"
has-tostringtag "^1.0.0" has-tostringtag "^1.0.2"
is-async-function "^2.0.0" is-async-function "^2.0.0"
is-date-object "^1.0.5" is-date-object "^1.0.5"
is-finalizationregistry "^1.0.2" is-finalizationregistry "^1.0.2"
@ -4948,10 +4962,10 @@ which-builtin-type@^1.1.3:
is-weakref "^1.0.2" is-weakref "^1.0.2"
isarray "^2.0.5" isarray "^2.0.5"
which-boxed-primitive "^1.0.2" which-boxed-primitive "^1.0.2"
which-collection "^1.0.1" which-collection "^1.0.2"
which-typed-array "^1.1.9" which-typed-array "^1.1.15"
which-collection@^1.0.1: which-collection@^1.0.1, which-collection@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0"
integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==
@ -4961,7 +4975,7 @@ which-collection@^1.0.1:
is-weakmap "^2.0.2" is-weakmap "^2.0.2"
is-weakset "^2.0.3" is-weakset "^2.0.3"
which-typed-array@^1.1.13, which-typed-array@^1.1.14, which-typed-array@^1.1.15, which-typed-array@^1.1.9: which-typed-array@^1.1.13, which-typed-array@^1.1.14, which-typed-array@^1.1.15:
version "1.1.15" version "1.1.15"
resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d"
integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA== integrity sha512-oV0jmFtUky6CXfkqehVvBP/LSWJ2sy4vWMioiENyJLePrBO/yKyV9OyJySfAKosh+RYkIl5zJCNZ8/4JncrpdA==
@ -5021,15 +5035,10 @@ yallist@^3.0.2:
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
yaml@^2.3.4: yaml@^2.3.4, yaml@~2.5.0:
version "2.5.0" version "2.5.1"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.0.tgz#c6165a721cf8000e91c36490a41d7be25176cf5d" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.1.tgz#c9772aacf62cb7494a95b0c4f1fb065b563db130"
integrity sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw== integrity sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q==
yaml@~2.4.2:
version "2.4.5"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.5.tgz#60630b206dd6d84df97003d33fc1ddf6296cca5e"
integrity sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==
yocto-queue@^0.1.0: yocto-queue@^0.1.0:
version "0.1.0" version "0.1.0"

View file

@ -757,6 +757,18 @@ class ConversationAdapters:
def has_any_conversation_config(user: KhojUser): def has_any_conversation_config(user: KhojUser):
return ChatModelOptions.objects.filter(user=user).exists() return ChatModelOptions.objects.filter(user=user).exists()
@staticmethod
def get_all_conversation_configs():
return ChatModelOptions.objects.all()
@staticmethod
def get_vision_enabled_config():
conversation_configurations = ConversationAdapters.get_all_conversation_configs()
for config in conversation_configurations:
if config.vision_enabled:
return config
return None
@staticmethod @staticmethod
def get_openai_conversation_config(): def get_openai_conversation_config():
return OpenAIProcessorConversationConfig.objects.filter().first() return OpenAIProcessorConversationConfig.objects.filter().first()

View file

@ -0,0 +1,17 @@
# Generated by Django 5.0.7 on 2024-08-14 19:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("database", "0055_alter_agent_style_icon"),
]
operations = [
migrations.AddField(
model_name="chatmodeloptions",
name="vision_enabled",
field=models.BooleanField(default=False),
),
]

View file

@ -0,0 +1,13 @@
# Generated by Django 5.0.7 on 2024-08-16 18:09
from typing import List
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("database", "0056_chatmodeloptions_vision_enabled"),
("database", "0056_searchmodelconfig_cross_encoder_model_config"),
]
operations: List[migrations.operations.base.Operation] = []

View file

@ -0,0 +1,14 @@
# Generated by Django 5.0.7 on 2024-09-05 18:28
from typing import List
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("database", "0057_merge_20240816_1409"),
("database", "0059_searchmodelconfig_bi_encoder_confidence_threshold"),
]
operations: List[str] = []

View file

@ -93,6 +93,7 @@ class ChatModelOptions(BaseModel):
tokenizer = models.CharField(max_length=200, default=None, null=True, blank=True) tokenizer = models.CharField(max_length=200, default=None, null=True, blank=True)
chat_model = models.CharField(max_length=200, default="bartowski/Meta-Llama-3.1-8B-Instruct-GGUF") chat_model = models.CharField(max_length=200, default="bartowski/Meta-Llama-3.1-8B-Instruct-GGUF")
model_type = models.CharField(max_length=200, choices=ModelType.choices, default=ModelType.OFFLINE) model_type = models.CharField(max_length=200, choices=ModelType.choices, default=ModelType.OFFLINE)
vision_enabled = models.BooleanField(default=False)
openai_config = models.ForeignKey( openai_config = models.ForeignKey(
OpenAIProcessorConversationConfig, on_delete=models.CASCADE, default=None, null=True, blank=True OpenAIProcessorConversationConfig, on_delete=models.CASCADE, default=None, null=True, blank=True
) )

View file

@ -123,6 +123,8 @@ def converse(
location_data: LocationData = None, location_data: LocationData = None,
user_name: str = None, user_name: str = None,
agent: Agent = None, agent: Agent = None,
image_url: Optional[str] = None,
vision_available: bool = False,
): ):
""" """
Converse with user using OpenAI's ChatGPT Converse with user using OpenAI's ChatGPT
@ -178,6 +180,8 @@ def converse(
model_name=model, model_name=model,
max_prompt_size=max_prompt_size, max_prompt_size=max_prompt_size,
tokenizer_name=tokenizer_name, tokenizer_name=tokenizer_name,
uploaded_image_url=image_url,
vision_enabled=vision_available,
) )
truncated_messages = "\n".join({f"{message.content[:70]}..." for message in messages}) truncated_messages = "\n".join({f"{message.content[:70]}..." for message in messages})
logger.debug(f"Conversation Context for GPT: {truncated_messages}") logger.debug(f"Conversation Context for GPT: {truncated_messages}")

View file

@ -101,12 +101,16 @@ def save_to_conversation_log(
client_application: ClientApplication = None, client_application: ClientApplication = None,
conversation_id: int = None, conversation_id: int = None,
automation_id: str = None, automation_id: str = None,
uploaded_image_url: str = None,
): ):
user_message_time = user_message_time or datetime.now().strftime("%Y-%m-%d %H:%M:%S") user_message_time = user_message_time or datetime.now().strftime("%Y-%m-%d %H:%M:%S")
updated_conversation = message_to_log( updated_conversation = message_to_log(
user_message=q, user_message=q,
chat_response=chat_response, chat_response=chat_response,
user_message_metadata={"created": user_message_time}, user_message_metadata={
"created": user_message_time,
"uploadedImageData": uploaded_image_url,
},
khoj_message_metadata={ khoj_message_metadata={
"context": compiled_references, "context": compiled_references,
"intent": {"inferred-queries": inferred_queries, "type": intent_type}, "intent": {"inferred-queries": inferred_queries, "type": intent_type},
@ -141,6 +145,8 @@ def generate_chatml_messages_with_context(
loaded_model: Optional[Llama] = None, loaded_model: Optional[Llama] = None,
max_prompt_size=None, max_prompt_size=None,
tokenizer_name=None, tokenizer_name=None,
uploaded_image_url=None,
vision_enabled=False,
): ):
"""Generate messages for ChatGPT with context from previous conversation""" """Generate messages for ChatGPT with context from previous conversation"""
# Set max prompt size from user config or based on pre-configured for model and machine specs # Set max prompt size from user config or based on pre-configured for model and machine specs
@ -150,28 +156,40 @@ def generate_chatml_messages_with_context(
else: else:
max_prompt_size = model_to_prompt_size.get(model_name, 2000) max_prompt_size = model_to_prompt_size.get(model_name, 2000)
# Format user and system messages to chatml format
def construct_structured_message(message, image_url):
if image_url and vision_enabled:
return [{"type": "text", "text": message}, {"type": "image_url", "image_url": {"url": image_url}}]
return message
# Scale lookback turns proportional to max prompt size supported by model # Scale lookback turns proportional to max prompt size supported by model
lookback_turns = max_prompt_size // 750 lookback_turns = max_prompt_size // 750
# Extract Chat History for Context # Extract Chat History for Context
chat_logs = [] chatml_messages: List[ChatMessage] = []
for chat in conversation_log.get("chat", []): for chat in conversation_log.get("chat", []):
chat_notes = f'\n\n Notes:\n{chat.get("context")}' if chat.get("context") else "\n" message_notes = f'\n\n Notes:\n{chat.get("context")}' if chat.get("context") else "\n"
chat_logs += [chat["message"] + chat_notes] role = "user" if chat["by"] == "you" else "assistant"
rest_backnforths: List[ChatMessage] = [] message_content = chat["message"] + message_notes
# Extract in reverse chronological order
for user_msg, assistant_msg in zip(chat_logs[-2::-2], chat_logs[::-2]): if chat.get("uploadedImageData") and vision_enabled:
if len(rest_backnforths) >= 2 * lookback_turns: message_content = construct_structured_message(message_content, chat.get("uploadedImageData"))
reconstructed_message = ChatMessage(content=message_content, role=role)
chatml_messages.insert(0, reconstructed_message)
if len(chatml_messages) >= 2 * lookback_turns:
break break
rest_backnforths += reciprocal_conversation_to_chatml([user_msg, assistant_msg])[::-1]
# Format user and system messages to chatml format
messages = [] messages = []
if not is_none_or_empty(user_message): if not is_none_or_empty(user_message):
messages.append(ChatMessage(content=user_message, role="user")) messages.append(
if len(rest_backnforths) > 0: ChatMessage(content=construct_structured_message(user_message, uploaded_image_url), role="user")
messages += rest_backnforths )
if len(chatml_messages) > 0:
messages += chatml_messages
if not is_none_or_empty(system_message): if not is_none_or_empty(system_message):
messages.append(ChatMessage(content=system_message, role="system")) messages.append(ChatMessage(content=system_message, role="system"))

View file

@ -56,6 +56,7 @@ async def search_online(
subscribed: bool = False, subscribed: bool = False,
send_status_func: Optional[Callable] = None, send_status_func: Optional[Callable] = None,
custom_filters: List[str] = [], custom_filters: List[str] = [],
uploaded_image_url: str = None,
): ):
query += " ".join(custom_filters) query += " ".join(custom_filters)
if not is_internet_connected(): if not is_internet_connected():
@ -64,7 +65,9 @@ async def search_online(
return return
# Breakdown the query into subqueries to get the correct answer # Breakdown the query into subqueries to get the correct answer
subqueries = await generate_online_subqueries(query, conversation_history, location, user) subqueries = await generate_online_subqueries(
query, conversation_history, location, user, uploaded_image_url=uploaded_image_url
)
response_dict = {} response_dict = {}
if subqueries: if subqueries:
@ -138,13 +141,14 @@ async def read_webpages(
user: KhojUser, user: KhojUser,
subscribed: bool = False, subscribed: bool = False,
send_status_func: Optional[Callable] = None, send_status_func: Optional[Callable] = None,
uploaded_image_url: str = None,
): ):
"Infer web pages to read from the query and extract relevant information from them" "Infer web pages to read from the query and extract relevant information from them"
logger.info(f"Inferring web pages to read") logger.info(f"Inferring web pages to read")
if send_status_func: if send_status_func:
async for event in send_status_func(f"**Inferring web pages to read**"): async for event in send_status_func(f"**Inferring web pages to read**"):
yield {ChatEvent.STATUS: event} yield {ChatEvent.STATUS: event}
urls = await infer_webpage_urls(query, conversation_history, location, user) urls = await infer_webpage_urls(query, conversation_history, location, user, uploaded_image_url)
logger.info(f"Reading web pages at: {urls}") logger.info(f"Reading web pages at: {urls}")
if send_status_func: if send_status_func:

View file

@ -1,4 +1,5 @@
import asyncio import asyncio
import base64
import json import json
import logging import logging
import time import time
@ -46,11 +47,13 @@ from khoj.routers.helpers import (
update_telemetry_state, update_telemetry_state,
validate_conversation_config, validate_conversation_config,
) )
from khoj.routers.storage import upload_image_to_bucket
from khoj.utils import state from khoj.utils import state
from khoj.utils.helpers import ( from khoj.utils.helpers import (
AsyncIteratorWrapper, AsyncIteratorWrapper,
ConversationCommand, ConversationCommand,
command_descriptions, command_descriptions,
convert_image_to_webp,
get_device, get_device,
is_none_or_empty, is_none_or_empty,
) )
@ -517,7 +520,11 @@ async def set_conversation_title(
) )
@api_chat.get("") class ImageUploadObject(BaseModel):
image: str
@api_chat.post("")
@requires(["authenticated"]) @requires(["authenticated"])
async def chat( async def chat(
request: Request, request: Request,
@ -532,6 +539,7 @@ async def chat(
region: Optional[str] = None, region: Optional[str] = None,
country: Optional[str] = None, country: Optional[str] = None,
timezone: Optional[str] = None, timezone: Optional[str] = None,
image: Optional[ImageUploadObject] = None,
rate_limiter_per_minute=Depends( rate_limiter_per_minute=Depends(
ApiUserRateLimiter(requests=60, subscribed_requests=60, window=60, slug="chat_minute") ApiUserRateLimiter(requests=60, subscribed_requests=60, window=60, slug="chat_minute")
), ),
@ -539,7 +547,7 @@ async def chat(
ApiUserRateLimiter(requests=600, subscribed_requests=600, window=60 * 60 * 24, slug="chat_day") ApiUserRateLimiter(requests=600, subscribed_requests=600, window=60 * 60 * 24, slug="chat_day")
), ),
): ):
async def event_generator(q: str): async def event_generator(q: str, image: ImageUploadObject):
start_time = time.perf_counter() start_time = time.perf_counter()
ttft = None ttft = None
chat_metadata: dict = {} chat_metadata: dict = {}
@ -550,6 +558,17 @@ async def chat(
q = unquote(q) q = unquote(q)
nonlocal conversation_id nonlocal conversation_id
uploaded_image_url = None
if image:
decoded_string = unquote(image.image)
base64_data = decoded_string.split(",", 1)[1]
image_bytes = base64.b64decode(base64_data)
webp_image_bytes = convert_image_to_webp(image_bytes)
try:
uploaded_image_url = upload_image_to_bucket(webp_image_bytes, request.user.object.id)
except:
uploaded_image_url = None
async def send_event(event_type: ChatEvent, data: str | dict): async def send_event(event_type: ChatEvent, data: str | dict):
nonlocal connection_alive, ttft nonlocal connection_alive, ttft
if not connection_alive or await request.is_disconnected(): if not connection_alive or await request.is_disconnected():
@ -637,7 +656,7 @@ async def chat(
if conversation_commands == [ConversationCommand.Default] or is_automated_task: if conversation_commands == [ConversationCommand.Default] or is_automated_task:
conversation_commands = await aget_relevant_information_sources( conversation_commands = await aget_relevant_information_sources(
q, meta_log, is_automated_task, subscribed=subscribed q, meta_log, is_automated_task, subscribed=subscribed, uploaded_image_url=uploaded_image_url
) )
conversation_commands_str = ", ".join([cmd.value for cmd in conversation_commands]) conversation_commands_str = ", ".join([cmd.value for cmd in conversation_commands])
async for result in send_event( async for result in send_event(
@ -645,7 +664,7 @@ async def chat(
): ):
yield result yield result
mode = await aget_relevant_output_modes(q, meta_log, is_automated_task) mode = await aget_relevant_output_modes(q, meta_log, is_automated_task, uploaded_image_url)
async for result in send_event(ChatEvent.STATUS, f"**Decided Response Mode:** {mode.value}"): async for result in send_event(ChatEvent.STATUS, f"**Decided Response Mode:** {mode.value}"):
yield result yield result
if mode not in conversation_commands: if mode not in conversation_commands:
@ -693,7 +712,9 @@ async def chat(
): ):
yield result yield result
response = await extract_relevant_summary(q, contextual_data, subscribed=subscribed) response = await extract_relevant_summary(
q, contextual_data, subscribed=subscribed, uploaded_image_url=uploaded_image_url
)
response_log = str(response) response_log = str(response)
async for result in send_llm_response(response_log): async for result in send_llm_response(response_log):
yield result yield result
@ -711,6 +732,7 @@ async def chat(
intent_type="summarize", intent_type="summarize",
client_application=request.user.client_app, client_application=request.user.client_app,
conversation_id=conversation_id, conversation_id=conversation_id,
uploaded_image_url=uploaded_image_url,
) )
return return
@ -753,6 +775,7 @@ async def chat(
conversation_id=conversation_id, conversation_id=conversation_id,
inferred_queries=[query_to_run], inferred_queries=[query_to_run],
automation_id=automation.id, automation_id=automation.id,
uploaded_image_url=uploaded_image_url,
) )
async for result in send_llm_response(llm_response): async for result in send_llm_response(llm_response):
yield result yield result
@ -807,6 +830,7 @@ async def chat(
subscribed, subscribed,
partial(send_event, ChatEvent.STATUS), partial(send_event, ChatEvent.STATUS),
custom_filters, custom_filters,
uploaded_image_url=uploaded_image_url,
): ):
if isinstance(result, dict) and ChatEvent.STATUS in result: if isinstance(result, dict) and ChatEvent.STATUS in result:
yield result[ChatEvent.STATUS] yield result[ChatEvent.STATUS]
@ -823,7 +847,13 @@ async def chat(
if ConversationCommand.Webpage in conversation_commands: if ConversationCommand.Webpage in conversation_commands:
try: try:
async for result in read_webpages( async for result in read_webpages(
defiltered_query, meta_log, location, user, subscribed, partial(send_event, ChatEvent.STATUS) defiltered_query,
meta_log,
location,
user,
subscribed,
partial(send_event, ChatEvent.STATUS),
uploaded_image_url=uploaded_image_url,
): ):
if isinstance(result, dict) and ChatEvent.STATUS in result: if isinstance(result, dict) and ChatEvent.STATUS in result:
yield result[ChatEvent.STATUS] yield result[ChatEvent.STATUS]
@ -869,6 +899,7 @@ async def chat(
online_results=online_results, online_results=online_results,
subscribed=subscribed, subscribed=subscribed,
send_status_func=partial(send_event, ChatEvent.STATUS), send_status_func=partial(send_event, ChatEvent.STATUS),
uploaded_image_url=uploaded_image_url,
): ):
if isinstance(result, dict) and ChatEvent.STATUS in result: if isinstance(result, dict) and ChatEvent.STATUS in result:
yield result[ChatEvent.STATUS] yield result[ChatEvent.STATUS]
@ -898,6 +929,7 @@ async def chat(
conversation_id=conversation_id, conversation_id=conversation_id,
compiled_references=compiled_references, compiled_references=compiled_references,
online_results=online_results, online_results=online_results,
uploaded_image_url=uploaded_image_url,
) )
content_obj = { content_obj = {
"intentType": intent_type, "intentType": intent_type,
@ -924,6 +956,7 @@ async def chat(
conversation_id, conversation_id,
location, location,
user_name, user_name,
uploaded_image_url,
) )
# Send Response # Send Response
@ -949,9 +982,9 @@ async def chat(
## Stream Text Response ## Stream Text Response
if stream: if stream:
return StreamingResponse(event_generator(q), media_type="text/plain") return StreamingResponse(event_generator(q, image=image), media_type="text/plain")
## Non-Streaming Text Response ## Non-Streaming Text Response
else: else:
response_iterator = event_generator(q) response_iterator = event_generator(q, image=image)
response_data = await read_chat_stream(response_iterator) response_data = await read_chat_stream(response_iterator)
return Response(content=json.dumps(response_data), media_type="application/json", status_code=200) return Response(content=json.dumps(response_data), media_type="application/json", status_code=200)

View file

@ -97,6 +97,7 @@ from khoj.utils.helpers import (
LRU, LRU,
ConversationCommand, ConversationCommand,
ImageIntentType, ImageIntentType,
convert_image_to_webp,
is_none_or_empty, is_none_or_empty,
is_valid_url, is_valid_url,
log_telemetry, log_telemetry,
@ -252,7 +253,9 @@ async def acreate_title_from_query(query: str) -> str:
return response.strip() return response.strip()
async def aget_relevant_information_sources(query: str, conversation_history: dict, is_task: bool, subscribed: bool): async def aget_relevant_information_sources(
query: str, conversation_history: dict, is_task: bool, subscribed: bool, uploaded_image_url: str = None
):
""" """
Given a query, determine which of the available tools the agent should use in order to answer appropriately. Given a query, determine which of the available tools the agent should use in order to answer appropriately.
""" """
@ -266,6 +269,9 @@ async def aget_relevant_information_sources(query: str, conversation_history: di
chat_history = construct_chat_history(conversation_history) chat_history = construct_chat_history(conversation_history)
if uploaded_image_url:
query = f"[placeholder for image attached to this message]\n{query}"
relevant_tools_prompt = prompts.pick_relevant_information_collection_tools.format( relevant_tools_prompt = prompts.pick_relevant_information_collection_tools.format(
query=query, query=query,
tools=tool_options_str, tools=tool_options_str,
@ -274,7 +280,9 @@ async def aget_relevant_information_sources(query: str, conversation_history: di
with timer("Chat actor: Infer information sources to refer", logger): with timer("Chat actor: Infer information sources to refer", logger):
response = await send_message_to_model_wrapper( response = await send_message_to_model_wrapper(
relevant_tools_prompt, response_type="json_object", subscribed=subscribed relevant_tools_prompt,
response_type="json_object",
subscribed=subscribed,
) )
try: try:
@ -302,7 +310,9 @@ async def aget_relevant_information_sources(query: str, conversation_history: di
return [ConversationCommand.Default] return [ConversationCommand.Default]
async def aget_relevant_output_modes(query: str, conversation_history: dict, is_task: bool = False): async def aget_relevant_output_modes(
query: str, conversation_history: dict, is_task: bool = False, uploaded_image_url: str = None
):
""" """
Given a query, determine which of the available tools the agent should use in order to answer appropriately. Given a query, determine which of the available tools the agent should use in order to answer appropriately.
""" """
@ -319,6 +329,9 @@ async def aget_relevant_output_modes(query: str, conversation_history: dict, is_
chat_history = construct_chat_history(conversation_history) chat_history = construct_chat_history(conversation_history)
if uploaded_image_url:
query = f"[placeholder for image attached to this message]\n{query}"
relevant_mode_prompt = prompts.pick_relevant_output_mode.format( relevant_mode_prompt = prompts.pick_relevant_output_mode.format(
query=query, query=query,
modes=mode_options_str, modes=mode_options_str,
@ -347,7 +360,7 @@ async def aget_relevant_output_modes(query: str, conversation_history: dict, is_
async def infer_webpage_urls( async def infer_webpage_urls(
q: str, conversation_history: dict, location_data: LocationData, user: KhojUser q: str, conversation_history: dict, location_data: LocationData, user: KhojUser, uploaded_image_url: str = None
) -> List[str]: ) -> List[str]:
""" """
Infer webpage links from the given query Infer webpage links from the given query
@ -366,7 +379,9 @@ async def infer_webpage_urls(
) )
with timer("Chat actor: Infer webpage urls to read", logger): with timer("Chat actor: Infer webpage urls to read", logger):
response = await send_message_to_model_wrapper(online_queries_prompt, response_type="json_object") response = await send_message_to_model_wrapper(
online_queries_prompt, uploaded_image_url=uploaded_image_url, response_type="json_object"
)
# Validate that the response is a non-empty, JSON-serializable list of URLs # Validate that the response is a non-empty, JSON-serializable list of URLs
try: try:
@ -381,7 +396,7 @@ async def infer_webpage_urls(
async def generate_online_subqueries( async def generate_online_subqueries(
q: str, conversation_history: dict, location_data: LocationData, user: KhojUser q: str, conversation_history: dict, location_data: LocationData, user: KhojUser, uploaded_image_url: str = None
) -> List[str]: ) -> List[str]:
""" """
Generate subqueries from the given query Generate subqueries from the given query
@ -400,7 +415,9 @@ async def generate_online_subqueries(
) )
with timer("Chat actor: Generate online search subqueries", logger): with timer("Chat actor: Generate online search subqueries", logger):
response = await send_message_to_model_wrapper(online_queries_prompt, response_type="json_object") response = await send_message_to_model_wrapper(
online_queries_prompt, uploaded_image_url=uploaded_image_url, response_type="json_object"
)
# Validate that the response is a non-empty, JSON-serializable list # Validate that the response is a non-empty, JSON-serializable list
try: try:
@ -419,7 +436,7 @@ async def generate_online_subqueries(
return [q] return [q]
async def schedule_query(q: str, conversation_history: dict) -> Tuple[str, ...]: async def schedule_query(q: str, conversation_history: dict, uploaded_image_url: str = None) -> Tuple[str, ...]:
""" """
Schedule the date, time to run the query. Assume the server timezone is UTC. Schedule the date, time to run the query. Assume the server timezone is UTC.
""" """
@ -430,7 +447,9 @@ async def schedule_query(q: str, conversation_history: dict) -> Tuple[str, ...]:
chat_history=chat_history, chat_history=chat_history,
) )
raw_response = await send_message_to_model_wrapper(crontime_prompt, response_type="json_object") raw_response = await send_message_to_model_wrapper(
crontime_prompt, uploaded_image_url=uploaded_image_url, response_type="json_object"
)
# Validate that the response is a non-empty, JSON-serializable list # Validate that the response is a non-empty, JSON-serializable list
try: try:
@ -468,7 +487,9 @@ async def extract_relevant_info(q: str, corpus: str, subscribed: bool) -> Union[
return response.strip() return response.strip()
async def extract_relevant_summary(q: str, corpus: str, subscribed: bool = False) -> Union[str, None]: async def extract_relevant_summary(
q: str, corpus: str, subscribed: bool = False, uploaded_image_url: str = None
) -> Union[str, None]:
""" """
Extract relevant information for a given query from the target corpus Extract relevant information for a given query from the target corpus
""" """
@ -489,6 +510,7 @@ async def extract_relevant_summary(q: str, corpus: str, subscribed: bool = False
prompts.system_prompt_extract_relevant_summary, prompts.system_prompt_extract_relevant_summary,
chat_model_option=chat_model, chat_model_option=chat_model,
subscribed=subscribed, subscribed=subscribed,
uploaded_image_url=uploaded_image_url,
) )
return response.strip() return response.strip()
@ -501,6 +523,7 @@ async def generate_better_image_prompt(
online_results: Optional[dict] = None, online_results: Optional[dict] = None,
model_type: Optional[str] = None, model_type: Optional[str] = None,
subscribed: bool = False, subscribed: bool = False,
uploaded_image_url: Optional[str] = None,
) -> str: ) -> str:
""" """
Generate a better image prompt from the given query Generate a better image prompt from the given query
@ -549,7 +572,7 @@ async def generate_better_image_prompt(
with timer("Chat actor: Generate contextual image prompt", logger): with timer("Chat actor: Generate contextual image prompt", logger):
response = await send_message_to_model_wrapper( response = await send_message_to_model_wrapper(
image_prompt, chat_model_option=chat_model, subscribed=subscribed image_prompt, chat_model_option=chat_model, subscribed=subscribed, uploaded_image_url=uploaded_image_url
) )
response = response.strip() response = response.strip()
if response.startswith(('"', "'")) and response.endswith(('"', "'")): if response.startswith(('"', "'")) and response.endswith(('"', "'")):
@ -564,11 +587,19 @@ async def send_message_to_model_wrapper(
response_type: str = "text", response_type: str = "text",
chat_model_option: ChatModelOptions = None, chat_model_option: ChatModelOptions = None,
subscribed: bool = False, subscribed: bool = False,
uploaded_image_url: str = None,
): ):
conversation_config: ChatModelOptions = ( conversation_config: ChatModelOptions = (
chat_model_option or await ConversationAdapters.aget_default_conversation_config() chat_model_option or await ConversationAdapters.aget_default_conversation_config()
) )
vision_available = conversation_config.vision_enabled
if not vision_available and uploaded_image_url:
vision_enabled_config = ConversationAdapters.get_vision_enabled_config()
if vision_enabled_config:
conversation_config = vision_enabled_config
vision_available = True
chat_model = conversation_config.chat_model chat_model = conversation_config.chat_model
max_tokens = ( max_tokens = (
conversation_config.subscribed_max_prompt_size conversation_config.subscribed_max_prompt_size
@ -576,6 +607,7 @@ async def send_message_to_model_wrapper(
else conversation_config.max_prompt_size else conversation_config.max_prompt_size
) )
tokenizer = conversation_config.tokenizer tokenizer = conversation_config.tokenizer
vision_available = conversation_config.vision_enabled
if conversation_config.model_type == "offline": if conversation_config.model_type == "offline":
if state.offline_chat_processor_config is None or state.offline_chat_processor_config.loaded_model is None: if state.offline_chat_processor_config is None or state.offline_chat_processor_config.loaded_model is None:
@ -589,6 +621,7 @@ async def send_message_to_model_wrapper(
loaded_model=loaded_model, loaded_model=loaded_model,
tokenizer_name=tokenizer, tokenizer_name=tokenizer,
max_prompt_size=max_tokens, max_prompt_size=max_tokens,
vision_enabled=vision_available,
) )
return send_message_to_model_offline( return send_message_to_model_offline(
@ -609,6 +642,8 @@ async def send_message_to_model_wrapper(
model_name=chat_model, model_name=chat_model,
max_prompt_size=max_tokens, max_prompt_size=max_tokens,
tokenizer_name=tokenizer, tokenizer_name=tokenizer,
vision_enabled=vision_available,
uploaded_image_url=uploaded_image_url,
) )
openai_response = send_message_to_model( openai_response = send_message_to_model(
@ -628,6 +663,7 @@ async def send_message_to_model_wrapper(
model_name=chat_model, model_name=chat_model,
max_prompt_size=max_tokens, max_prompt_size=max_tokens,
tokenizer_name=tokenizer, tokenizer_name=tokenizer,
vision_enabled=vision_available,
) )
return anthropic_send_message_to_model( return anthropic_send_message_to_model(
@ -651,6 +687,7 @@ def send_message_to_model_wrapper_sync(
chat_model = conversation_config.chat_model chat_model = conversation_config.chat_model
max_tokens = conversation_config.max_prompt_size max_tokens = conversation_config.max_prompt_size
vision_available = conversation_config.vision_enabled
if conversation_config.model_type == "offline": if conversation_config.model_type == "offline":
if state.offline_chat_processor_config is None or state.offline_chat_processor_config.loaded_model is None: if state.offline_chat_processor_config is None or state.offline_chat_processor_config.loaded_model is None:
@ -658,7 +695,11 @@ def send_message_to_model_wrapper_sync(
loaded_model = state.offline_chat_processor_config.loaded_model loaded_model = state.offline_chat_processor_config.loaded_model
truncated_messages = generate_chatml_messages_with_context( truncated_messages = generate_chatml_messages_with_context(
user_message=message, system_message=system_message, model_name=chat_model, loaded_model=loaded_model user_message=message,
system_message=system_message,
model_name=chat_model,
loaded_model=loaded_model,
vision_enabled=vision_available,
) )
return send_message_to_model_offline( return send_message_to_model_offline(
@ -672,7 +713,10 @@ def send_message_to_model_wrapper_sync(
elif conversation_config.model_type == "openai": elif conversation_config.model_type == "openai":
api_key = conversation_config.openai_config.api_key api_key = conversation_config.openai_config.api_key
truncated_messages = generate_chatml_messages_with_context( truncated_messages = generate_chatml_messages_with_context(
user_message=message, system_message=system_message, model_name=chat_model user_message=message,
system_message=system_message,
model_name=chat_model,
vision_enabled=vision_available,
) )
openai_response = send_message_to_model( openai_response = send_message_to_model(
@ -688,6 +732,7 @@ def send_message_to_model_wrapper_sync(
system_message=system_message, system_message=system_message,
model_name=chat_model, model_name=chat_model,
max_prompt_size=max_tokens, max_prompt_size=max_tokens,
vision_enabled=vision_available,
) )
return anthropic_send_message_to_model( return anthropic_send_message_to_model(
@ -712,6 +757,7 @@ def generate_chat_response(
conversation_id: int = None, conversation_id: int = 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,
) -> Tuple[Union[ThreadedGenerator, Iterator[str]], Dict[str, str]]: ) -> Tuple[Union[ThreadedGenerator, Iterator[str]], Dict[str, str]]:
# Initialize Variables # Initialize Variables
chat_response = None chat_response = None
@ -719,7 +765,6 @@ def generate_chat_response(
metadata = {} metadata = {}
agent = AgentAdapters.get_conversation_agent_by_id(conversation.agent.id) if conversation.agent else None agent = AgentAdapters.get_conversation_agent_by_id(conversation.agent.id) if conversation.agent else None
try: try:
partial_completion = partial( partial_completion = partial(
save_to_conversation_log, save_to_conversation_log,
@ -731,9 +776,17 @@ def generate_chat_response(
inferred_queries=inferred_queries, inferred_queries=inferred_queries,
client_application=client_application, client_application=client_application,
conversation_id=conversation_id, conversation_id=conversation_id,
uploaded_image_url=uploaded_image_url,
) )
conversation_config = ConversationAdapters.get_valid_conversation_config(user, conversation) conversation_config = ConversationAdapters.get_valid_conversation_config(user, conversation)
vision_available = conversation_config.vision_enabled
if not vision_available and uploaded_image_url:
vision_enabled_config = ConversationAdapters.get_vision_enabled_config()
if vision_enabled_config:
conversation_config = vision_enabled_config
vision_available = True
if conversation_config.model_type == "offline": if conversation_config.model_type == "offline":
loaded_model = state.offline_chat_processor_config.loaded_model loaded_model = state.offline_chat_processor_config.loaded_model
chat_response = converse_offline( chat_response = converse_offline(
@ -759,6 +812,7 @@ def generate_chat_response(
chat_response = converse( chat_response = converse(
compiled_references, compiled_references,
q, q,
image_url=uploaded_image_url,
online_results=online_results, online_results=online_results,
conversation_log=meta_log, conversation_log=meta_log,
model=chat_model, model=chat_model,
@ -771,6 +825,7 @@ def generate_chat_response(
location_data=location_data, location_data=location_data,
user_name=user_name, user_name=user_name,
agent=agent, agent=agent,
vision_available=vision_available,
) )
elif conversation_config.model_type == "anthropic": elif conversation_config.model_type == "anthropic":
@ -809,6 +864,7 @@ async def text_to_image(
online_results: Dict[str, Any], online_results: Dict[str, Any],
subscribed: bool = False, subscribed: bool = False,
send_status_func: Optional[Callable] = None, send_status_func: Optional[Callable] = None,
uploaded_image_url: Optional[str] = None,
): ):
status_code = 200 status_code = 200
image = None image = None
@ -845,6 +901,7 @@ async def text_to_image(
online_results=online_results, online_results=online_results,
model_type=text_to_image_config.model_type, model_type=text_to_image_config.model_type,
subscribed=subscribed, subscribed=subscribed,
uploaded_image_url=uploaded_image_url,
) )
if send_status_func: if send_status_func:
@ -908,13 +965,7 @@ async def text_to_image(
with timer("Convert image to webp", logger): with timer("Convert image to webp", logger):
# Convert png to webp for faster loading # Convert png to webp for faster loading
image_io = io.BytesIO(decoded_image) webp_image_bytes = convert_image_to_webp(decoded_image)
png_image = Image.open(image_io)
webp_image_io = io.BytesIO()
png_image.save(webp_image_io, "WEBP")
webp_image_bytes = webp_image_io.getvalue()
webp_image_io.close()
image_io.close()
with timer("Upload image to S3", logger): with timer("Upload image to S3", logger):
image_url = upload_image(webp_image_bytes, user.uuid) image_url = upload_image(webp_image_bytes, user.uuid)
@ -1095,6 +1146,7 @@ def should_notify(original_query: str, executed_query: str, ai_response: str) ->
with timer("Chat actor: Decide to notify user of automation response", logger): with timer("Chat actor: Decide to notify user of automation response", logger):
try: try:
# TODO Replace with async call so we don't have to maintain a sync version
response = send_message_to_model_wrapper_sync(to_notify_or_not) response = send_message_to_model_wrapper_sync(to_notify_or_not)
should_notify_result = "no" not in response.lower() should_notify_result = "no" not in response.lower()
logger.info(f'Decided to {"not " if not should_notify_result else ""}notify user of automation response.') logger.info(f'Decided to {"not " if not should_notify_result else ""}notify user of automation response.')

View file

@ -33,3 +33,31 @@ def upload_image(image: bytes, user_id: uuid.UUID):
except Exception as e: except Exception as e:
logger.error(f"Failed to upload image to S3: {e}") logger.error(f"Failed to upload image to S3: {e}")
return None return None
AWS_USER_UPLOADED_IMAGES_BUCKET_NAME = os.getenv("AWS_USER_UPLOADED_IMAGES_BUCKET_NAME")
def upload_image_to_bucket(image: bytes, user_id: uuid.UUID):
"""Upload the image to the S3 bucket"""
if not aws_enabled:
logger.info("AWS is not enabled. Skipping image upload")
return None
image_key = f"{user_id}/{uuid.uuid4()}.webp"
if not AWS_USER_UPLOADED_IMAGES_BUCKET_NAME:
logger.error("AWS_USER_UPLOADED_IMAGES_BUCKET_NAME is not set")
return None
try:
s3_client.put_object(
Bucket=AWS_USER_UPLOADED_IMAGES_BUCKET_NAME,
Key=image_key,
Body=image,
ACL="public-read",
ContentType="image/webp",
)
return f"https://{AWS_USER_UPLOADED_IMAGES_BUCKET_NAME}/{image_key}"
except Exception as e:
logger.error(f"Failed to upload image to S3: {e}")
return None

View file

@ -1,6 +1,7 @@
from __future__ import annotations # to avoid quoting type hints from __future__ import annotations # to avoid quoting type hints
import datetime import datetime
import io
import logging import logging
import os import os
import platform import platform
@ -22,6 +23,7 @@ import requests
import torch import torch
from asgiref.sync import sync_to_async from asgiref.sync import sync_to_async
from magika import Magika from magika import Magika
from PIL import Image
from khoj.utils import constants from khoj.utils import constants
@ -416,3 +418,16 @@ def is_internet_connected():
return response.status_code == 200 return response.status_code == 200
except: except:
return False return False
def convert_image_to_webp(image_bytes):
"""Convert image bytes to webp format for faster loading"""
image_io = io.BytesIO(image_bytes)
with Image.open(image_io) as original_image:
webp_image_io = io.BytesIO()
original_image.save(webp_image_io, "WEBP")
# Encode the WebP image back to base64
webp_image_bytes = webp_image_io.getvalue()
webp_image_io.close()
return webp_image_bytes

View file

@ -462,8 +462,8 @@ async def test_chat_with_unauthenticated_user(chat_client_with_auth, api_user2:
headers = {"Authorization": f"Bearer {api_user2.token}"} headers = {"Authorization": f"Bearer {api_user2.token}"}
# Act # Act
auth_response = chat_client_with_auth.get(f'/api/chat?q="Hello!"', headers=headers) auth_response = chat_client_with_auth.post(f'/api/chat?q="Hello!"', headers=headers)
no_auth_response = chat_client_with_auth.get(f'/api/chat?q="Hello!"') no_auth_response = chat_client_with_auth.post(f'/api/chat?q="Hello!"')
# Assert # Assert
assert auth_response.status_code == 200 assert auth_response.status_code == 200

View file

@ -49,7 +49,7 @@ def create_conversation(message_list, user, agent=None):
@pytest.mark.django_db(transaction=True) @pytest.mark.django_db(transaction=True)
def test_offline_chat_with_no_chat_history_or_retrieved_content(client_offline_chat): def test_offline_chat_with_no_chat_history_or_retrieved_content(client_offline_chat):
# Act # Act
response = client_offline_chat.get(f'/api/chat?q="Hello, my name is Testatron. Who are you?"&stream=true') response = client_offline_chat.post(f'/api/chat?q="Hello, my name is Testatron. Who are you?"&stream=true')
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -67,7 +67,7 @@ def test_chat_with_online_content(client_offline_chat):
# Act # Act
q = "/online give me the link to paul graham's essay how to do great work" q = "/online give me the link to paul graham's essay how to do great work"
encoded_q = quote(q, safe="") encoded_q = quote(q, safe="")
response = client_offline_chat.get(f"/api/chat?q={encoded_q}") response = client_offline_chat.post(f"/api/chat?q={encoded_q}")
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
@ -89,7 +89,7 @@ def test_chat_with_online_webpage_content(client_offline_chat):
# Act # Act
q = "/online how many firefighters were involved in the great chicago fire and which year did it take place?" q = "/online how many firefighters were involved in the great chicago fire and which year did it take place?"
encoded_q = quote(q, safe="") encoded_q = quote(q, safe="")
response = client_offline_chat.get(f"/api/chat?q={encoded_q}") response = client_offline_chat.post(f"/api/chat?q={encoded_q}")
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
@ -112,7 +112,7 @@ def test_answer_from_chat_history(client_offline_chat, default_user2):
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = client_offline_chat.get(f'/api/chat?q="What is my name?"&stream=true') response = client_offline_chat.post(f'/api/chat?q="What is my name?"&stream=true')
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -139,7 +139,7 @@ def test_answer_from_currently_retrieved_content(client_offline_chat, default_us
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = client_offline_chat.get(f'/api/chat?q="Where was Xi Li born?"') response = client_offline_chat.post(f'/api/chat?q="Where was Xi Li born?"')
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -163,7 +163,7 @@ def test_answer_from_chat_history_and_previously_retrieved_content(client_offlin
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = client_offline_chat.get(f'/api/chat?q="Where was I born?"') response = client_offline_chat.post(f'/api/chat?q="Where was I born?"')
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -185,7 +185,7 @@ def test_answer_from_chat_history_and_currently_retrieved_content(client_offline
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = client_offline_chat.get(f'/api/chat?q="Where was I born?"') response = client_offline_chat.post(f'/api/chat?q="Where was I born?"')
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -211,7 +211,7 @@ def test_no_answer_in_chat_history_or_retrieved_content(client_offline_chat, def
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = client_offline_chat.get(f'/api/chat?q="Where was I born?"&stream=true') response = client_offline_chat.post(f'/api/chat?q="Where was I born?"&stream=true')
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -232,7 +232,7 @@ def test_answer_using_general_command(client_offline_chat, default_user2):
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = client_offline_chat.get(f"/api/chat?q={query}&stream=true") response = client_offline_chat.post(f"/api/chat?q={query}&stream=true")
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -250,7 +250,7 @@ def test_answer_from_retrieved_content_using_notes_command(client_offline_chat,
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = client_offline_chat.get(f"/api/chat?q={query}&stream=true") response = client_offline_chat.post(f"/api/chat?q={query}&stream=true")
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -269,8 +269,8 @@ def test_answer_using_file_filter(client_offline_chat, default_user2):
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
no_answer_response = client_offline_chat.get(f"/api/chat?q={no_answer_query}&stream=true").content.decode("utf-8") no_answer_response = client_offline_chat.post(f"/api/chat?q={no_answer_query}&stream=true").content.decode("utf-8")
answer_response = client_offline_chat.get(f"/api/chat?q={answer_query}&stream=true").content.decode("utf-8") answer_response = client_offline_chat.post(f"/api/chat?q={answer_query}&stream=true").content.decode("utf-8")
# Assert # Assert
assert "Fujiang" not in no_answer_response assert "Fujiang" not in no_answer_response
@ -287,7 +287,7 @@ def test_answer_not_known_using_notes_command(client_offline_chat, default_user2
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = client_offline_chat.get(f"/api/chat?q={query}&stream=true") response = client_offline_chat.post(f"/api/chat?q={query}&stream=true")
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -320,7 +320,7 @@ def test_summarize_one_file(client_offline_chat, default_user2: KhojUser):
json={"filename": summarization_file, "conversation_id": str(conversation.id)}, json={"filename": summarization_file, "conversation_id": str(conversation.id)},
) )
query = urllib.parse.quote("/summarize") query = urllib.parse.quote("/summarize")
response = client_offline_chat.get(f"/api/chat?q={query}&conversation_id={conversation.id}&stream=true") response = client_offline_chat.post(f"/api/chat?q={query}&conversation_id={conversation.id}&stream=true")
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
assert response_message != "" assert response_message != ""
@ -352,7 +352,7 @@ def test_summarize_extra_text(client_offline_chat, default_user2: KhojUser):
json={"filename": summarization_file, "conversation_id": str(conversation.id)}, json={"filename": summarization_file, "conversation_id": str(conversation.id)},
) )
query = urllib.parse.quote("/summarize tell me about Xiu") query = urllib.parse.quote("/summarize tell me about Xiu")
response = client_offline_chat.get(f"/api/chat?q={query}&conversation_id={conversation.id}&stream=true") response = client_offline_chat.post(f"/api/chat?q={query}&conversation_id={conversation.id}&stream=true")
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
assert response_message != "" assert response_message != ""
@ -380,7 +380,7 @@ def test_summarize_multiple_files(client_offline_chat, default_user2: KhojUser):
) )
query = urllib.parse.quote("/summarize") query = urllib.parse.quote("/summarize")
response = client_offline_chat.get(f"/api/chat?q={query}&conversation_id={conversation.id}&stream=true") response = client_offline_chat.post(f"/api/chat?q={query}&conversation_id={conversation.id}&stream=true")
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -393,7 +393,7 @@ def test_summarize_no_files(client_offline_chat, default_user2: KhojUser):
message_list = [] message_list = []
conversation = create_conversation(message_list, default_user2) conversation = create_conversation(message_list, default_user2)
query = urllib.parse.quote("/summarize") query = urllib.parse.quote("/summarize")
response = client_offline_chat.get(f"/api/chat?q={query}&conversation_id={conversation.id}&stream=true") response = client_offline_chat.post(f"/api/chat?q={query}&conversation_id={conversation.id}&stream=true")
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
assert response_message == "No files selected for summarization. Please add files using the section on the left." assert response_message == "No files selected for summarization. Please add files using the section on the left."
@ -425,14 +425,14 @@ def test_summarize_different_conversation(client_offline_chat, default_user2: Kh
) )
query = urllib.parse.quote("/summarize") query = urllib.parse.quote("/summarize")
response = client_offline_chat.get(f"/api/chat?q={query}&conversation_id={conversation2.id}&stream=true") response = client_offline_chat.post(f"/api/chat?q={query}&conversation_id={conversation2.id}&stream=true")
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
assert response_message == "No files selected for summarization. Please add files using the section on the left." assert response_message == "No files selected for summarization. Please add files using the section on the left."
# now make sure that the file filter is still in conversation 1 # now make sure that the file filter is still in conversation 1
response = client_offline_chat.get(f"/api/chat?q={query}&conversation_id={conversation1.id}&stream=true") response = client_offline_chat.post(f"/api/chat?q={query}&conversation_id={conversation1.id}&stream=true")
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -452,7 +452,7 @@ def test_summarize_nonexistant_file(client_offline_chat, default_user2: KhojUser
json={"filename": "imaginary.markdown", "conversation_id": str(conversation.id)}, json={"filename": "imaginary.markdown", "conversation_id": str(conversation.id)},
) )
query = urllib.parse.quote("/summarize") query = urllib.parse.quote("/summarize")
response = client_offline_chat.get(f"/api/chat?q={query}&conversation_id={conversation.id}&stream=true") response = client_offline_chat.post(f"/api/chat?q={query}&conversation_id={conversation.id}&stream=true")
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
assert response_message == "No files selected for summarization. Please add files using the section on the left." assert response_message == "No files selected for summarization. Please add files using the section on the left."
@ -483,7 +483,7 @@ def test_summarize_diff_user_file(
json={"filename": summarization_file, "conversation_id": str(conversation.id)}, json={"filename": summarization_file, "conversation_id": str(conversation.id)},
) )
query = urllib.parse.quote("/summarize") query = urllib.parse.quote("/summarize")
response = client_offline_chat.get(f"/api/chat?q={query}&conversation_id={conversation.id}&stream=true") response = client_offline_chat.post(f"/api/chat?q={query}&conversation_id={conversation.id}&stream=true")
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
assert response_message == "No files selected for summarization. Please add files using the section on the left." assert response_message == "No files selected for summarization. Please add files using the section on the left."
@ -499,7 +499,7 @@ def test_answer_requires_current_date_awareness(client_offline_chat):
query = urllib.parse.quote("Where did I have lunch today?") query = urllib.parse.quote("Where did I have lunch today?")
# Act # Act
response = client_offline_chat.get(f"/api/chat?q={query}&stream=true") response = client_offline_chat.post(f"/api/chat?q={query}&stream=true")
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -518,7 +518,7 @@ def test_answer_requires_current_date_awareness(client_offline_chat):
def test_answer_requires_date_aware_aggregation_across_provided_notes(client_offline_chat): def test_answer_requires_date_aware_aggregation_across_provided_notes(client_offline_chat):
"Chat director should be able to answer questions that require date aware aggregation across multiple notes" "Chat director should be able to answer questions that require date aware aggregation across multiple notes"
# Act # Act
response = client_offline_chat.get(f'/api/chat?q="How much did I spend on dining this year?"&stream=true') response = client_offline_chat.post(f'/api/chat?q="How much did I spend on dining this year?"&stream=true')
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -559,7 +559,7 @@ def test_answer_general_question_not_in_chat_history_or_retrieved_content(client
@pytest.mark.django_db(transaction=True) @pytest.mark.django_db(transaction=True)
def test_ask_for_clarification_if_not_enough_context_in_question(client_offline_chat, default_user2): def test_ask_for_clarification_if_not_enough_context_in_question(client_offline_chat, default_user2):
# Act # Act
response = client_offline_chat.get(f'/api/chat?q="What is the name of Namitas older son"&stream=true') response = client_offline_chat.post(f'/api/chat?q="What is the name of Namitas older son"&stream=true')
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -589,7 +589,7 @@ def test_answer_in_chat_history_beyond_lookback_window(client_offline_chat, defa
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = client_offline_chat.get(f'/api/chat?q="What is my name?"&stream=true') response = client_offline_chat.post(f'/api/chat?q="What is my name?"&stream=true')
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -653,7 +653,7 @@ def test_answer_in_chat_history_by_conversation_id_with_agent(
# Act # Act
query = urllib.parse.quote("/general What did I eat for breakfast?") query = urllib.parse.quote("/general What did I eat for breakfast?")
response = client_offline_chat.get(f"/api/chat?q={query}&conversation_id={conversation.id}&stream=true") response = client_offline_chat.post(f"/api/chat?q={query}&conversation_id={conversation.id}&stream=true")
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert that agent only responds with the summary of spending # Assert that agent only responds with the summary of spending
@ -673,7 +673,7 @@ def test_answer_chat_history_very_long(client_offline_chat, default_user2):
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = client_offline_chat.get(f'/api/chat?q="What is my name?"&stream=true') response = client_offline_chat.post(f'/api/chat?q="What is my name?"&stream=true')
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -687,7 +687,7 @@ def test_answer_chat_history_very_long(client_offline_chat, default_user2):
def test_answer_requires_multiple_independent_searches(client_offline_chat): def test_answer_requires_multiple_independent_searches(client_offline_chat):
"Chat director should be able to answer by doing multiple independent searches for required information" "Chat director should be able to answer by doing multiple independent searches for required information"
# Act # Act
response = client_offline_chat.get(f'/api/chat?q="Is Xi older than Namita?"&stream=true') response = client_offline_chat.post(f'/api/chat?q="Is Xi older than Namita?"&stream=true')
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert

View file

@ -49,7 +49,7 @@ def create_conversation(message_list, user, agent=None):
@pytest.mark.django_db(transaction=True) @pytest.mark.django_db(transaction=True)
def test_chat_with_no_chat_history_or_retrieved_content(chat_client): def test_chat_with_no_chat_history_or_retrieved_content(chat_client):
# Act # Act
response = chat_client.get(f'/api/chat?q="Hello, my name is Testatron. Who are you?"') response = chat_client.post(f'/api/chat?q="Hello, my name is Testatron. Who are you?"')
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
@ -67,7 +67,7 @@ def test_chat_with_online_content(chat_client):
# Act # Act
q = "/online give me the link to paul graham's essay how to do great work" q = "/online give me the link to paul graham's essay how to do great work"
encoded_q = quote(q, safe="") encoded_q = quote(q, safe="")
response = chat_client.get(f"/api/chat?q={encoded_q}") response = chat_client.post(f"/api/chat?q={encoded_q}")
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
@ -88,7 +88,7 @@ def test_chat_with_online_webpage_content(chat_client):
# Act # Act
q = "/online how many firefighters were involved in the great chicago fire and which year did it take place?" q = "/online how many firefighters were involved in the great chicago fire and which year did it take place?"
encoded_q = quote(q, safe="") encoded_q = quote(q, safe="")
response = chat_client.get(f"/api/chat?q={encoded_q}") response = chat_client.post(f"/api/chat?q={encoded_q}")
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
@ -111,7 +111,7 @@ def test_answer_from_chat_history(chat_client, default_user2: KhojUser):
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = chat_client.get(f'/api/chat?q="What is my name?"') response = chat_client.post(f'/api/chat?q="What is my name?"')
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -138,7 +138,7 @@ def test_answer_from_currently_retrieved_content(chat_client, default_user2: Kho
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = chat_client.get(f'/api/chat?q="Where was Xi Li born?"') response = chat_client.post(f'/api/chat?q="Where was Xi Li born?"')
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
@ -162,7 +162,7 @@ def test_answer_from_chat_history_and_previously_retrieved_content(chat_client_n
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = chat_client_no_background.get(f'/api/chat?q="Where was I born?"') response = chat_client_no_background.post(f'/api/chat?q="Where was I born?"')
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
@ -185,7 +185,7 @@ def test_answer_from_chat_history_and_currently_retrieved_content(chat_client, d
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = chat_client.get(f'/api/chat?q="Where was I born?"') response = chat_client.post(f'/api/chat?q="Where was I born?"')
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
@ -210,7 +210,7 @@ def test_no_answer_in_chat_history_or_retrieved_content(chat_client, default_use
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = chat_client.get(f'/api/chat?q="Where was I born?"') response = chat_client.post(f'/api/chat?q="Where was I born?"')
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
@ -240,7 +240,7 @@ def test_answer_using_general_command(chat_client, default_user2: KhojUser):
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = chat_client.get(f"/api/chat?q={query}&stream=true") response = chat_client.post(f"/api/chat?q={query}&stream=true")
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -258,7 +258,7 @@ def test_answer_from_retrieved_content_using_notes_command(chat_client, default_
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = chat_client.get(f"/api/chat?q={query}") response = chat_client.post(f"/api/chat?q={query}")
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
@ -276,7 +276,7 @@ def test_answer_not_known_using_notes_command(chat_client_no_background, default
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = chat_client_no_background.get(f"/api/chat?q={query}") response = chat_client_no_background.post(f"/api/chat?q={query}")
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
@ -309,7 +309,7 @@ def test_summarize_one_file(chat_client, default_user2: KhojUser):
json={"filename": summarization_file, "conversation_id": str(conversation.id)}, json={"filename": summarization_file, "conversation_id": str(conversation.id)},
) )
query = urllib.parse.quote("/summarize") query = urllib.parse.quote("/summarize")
response = chat_client.get(f"/api/chat?q={query}&conversation_id={conversation.id}") response = chat_client.post(f"/api/chat?q={query}&conversation_id={conversation.id}")
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
assert response_message != "" assert response_message != ""
@ -341,7 +341,7 @@ def test_summarize_extra_text(chat_client, default_user2: KhojUser):
json={"filename": summarization_file, "conversation_id": str(conversation.id)}, json={"filename": summarization_file, "conversation_id": str(conversation.id)},
) )
query = urllib.parse.quote("/summarize tell me about Xiu") query = urllib.parse.quote("/summarize tell me about Xiu")
response = chat_client.get(f"/api/chat?q={query}&conversation_id={conversation.id}") response = chat_client.post(f"/api/chat?q={query}&conversation_id={conversation.id}")
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
assert response_message != "" assert response_message != ""
@ -369,7 +369,7 @@ def test_summarize_multiple_files(chat_client, default_user2: KhojUser):
) )
query = urllib.parse.quote("/summarize") query = urllib.parse.quote("/summarize")
response = chat_client.get(f"/api/chat?q={query}&conversation_id={conversation.id}") response = chat_client.post(f"/api/chat?q={query}&conversation_id={conversation.id}")
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
@ -382,7 +382,7 @@ def test_summarize_no_files(chat_client, default_user2: KhojUser):
message_list = [] message_list = []
conversation = create_conversation(message_list, default_user2) conversation = create_conversation(message_list, default_user2)
query = urllib.parse.quote("/summarize") query = urllib.parse.quote("/summarize")
response = chat_client.get(f"/api/chat?q={query}&conversation_id={conversation.id}") response = chat_client.post(f"/api/chat?q={query}&conversation_id={conversation.id}")
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
assert response_message == "No files selected for summarization. Please add files using the section on the left." assert response_message == "No files selected for summarization. Please add files using the section on the left."
@ -414,14 +414,14 @@ def test_summarize_different_conversation(chat_client, default_user2: KhojUser):
) )
query = urllib.parse.quote("/summarize") query = urllib.parse.quote("/summarize")
response = chat_client.get(f"/api/chat?q={query}&conversation_id={conversation2.id}") response = chat_client.post(f"/api/chat?q={query}&conversation_id={conversation2.id}")
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
assert response_message == "No files selected for summarization. Please add files using the section on the left." assert response_message == "No files selected for summarization. Please add files using the section on the left."
# now make sure that the file filter is still in conversation 1 # now make sure that the file filter is still in conversation 1
response = chat_client.get(f"/api/chat?q={query}&conversation_id={conversation1.id}") response = chat_client.post(f"/api/chat?q={query}&conversation_id={conversation1.id}")
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
@ -441,7 +441,7 @@ def test_summarize_nonexistant_file(chat_client, default_user2: KhojUser):
json={"filename": "imaginary.markdown", "conversation_id": str(conversation.id)}, json={"filename": "imaginary.markdown", "conversation_id": str(conversation.id)},
) )
query = urllib.parse.quote("/summarize") query = urllib.parse.quote("/summarize")
response = chat_client.get(f"/api/chat?q={query}&conversation_id={conversation.id}") response = chat_client.post(f"/api/chat?q={query}&conversation_id={conversation.id}")
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
assert response_message == "No files selected for summarization. Please add files using the section on the left." assert response_message == "No files selected for summarization. Please add files using the section on the left."
@ -470,7 +470,7 @@ def test_summarize_diff_user_file(chat_client, default_user: KhojUser, pdf_confi
json={"filename": summarization_file, "conversation_id": str(conversation.id)}, json={"filename": summarization_file, "conversation_id": str(conversation.id)},
) )
query = urllib.parse.quote("/summarize") query = urllib.parse.quote("/summarize")
response = chat_client.get(f"/api/chat?q={query}&conversation_id={conversation.id}") response = chat_client.post(f"/api/chat?q={query}&conversation_id={conversation.id}")
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
assert response_message == "No files selected for summarization. Please add files using the section on the left." assert response_message == "No files selected for summarization. Please add files using the section on the left."
@ -484,7 +484,7 @@ def test_summarize_diff_user_file(chat_client, default_user: KhojUser, pdf_confi
def test_answer_requires_current_date_awareness(chat_client): def test_answer_requires_current_date_awareness(chat_client):
"Chat actor should be able to answer questions relative to current date using provided notes" "Chat actor should be able to answer questions relative to current date using provided notes"
# Act # Act
response = chat_client.get(f'/api/chat?q="Where did I have lunch today?"&stream=true') response = chat_client.post(f'/api/chat?q="Where did I have lunch today?"&stream=true')
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -502,7 +502,7 @@ def test_answer_requires_current_date_awareness(chat_client):
def test_answer_requires_date_aware_aggregation_across_provided_notes(chat_client): def test_answer_requires_date_aware_aggregation_across_provided_notes(chat_client):
"Chat director should be able to answer questions that require date aware aggregation across multiple notes" "Chat director should be able to answer questions that require date aware aggregation across multiple notes"
# Act # Act
response = chat_client.get(f'/api/chat?q="How much did I spend on dining this year?"') response = chat_client.post(f'/api/chat?q="How much did I spend on dining this year?"')
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
@ -523,7 +523,7 @@ def test_answer_general_question_not_in_chat_history_or_retrieved_content(chat_c
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = chat_client.get(f'/api/chat?q="Write a haiku about unit testing. Do not say anything else.') response = chat_client.post(f'/api/chat?q="Write a haiku about unit testing. Do not say anything else.')
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
@ -540,7 +540,7 @@ def test_answer_general_question_not_in_chat_history_or_retrieved_content(chat_c
@pytest.mark.chatquality @pytest.mark.chatquality
def test_ask_for_clarification_if_not_enough_context_in_question(chat_client_no_background): def test_ask_for_clarification_if_not_enough_context_in_question(chat_client_no_background):
# Act # Act
response = chat_client_no_background.get(f'/api/chat?q="What is the name of Namitas older son?"') response = chat_client_no_background.post(f'/api/chat?q="What is the name of Namitas older son?"')
response_message = response.json()["response"].lower() response_message = response.json()["response"].lower()
# Assert # Assert
@ -574,7 +574,7 @@ def test_answer_in_chat_history_beyond_lookback_window(chat_client, default_user
create_conversation(message_list, default_user2) create_conversation(message_list, default_user2)
# Act # Act
response = chat_client.get(f'/api/chat?q="What is my name?"') response = chat_client.post(f'/api/chat?q="What is my name?"')
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert # Assert
@ -607,7 +607,7 @@ def test_answer_in_chat_history_by_conversation_id(chat_client, default_user2: K
# Act # Act
query = urllib.parse.quote("/general What is my favorite color?") query = urllib.parse.quote("/general What is my favorite color?")
response = chat_client.get(f"/api/chat?q={query}&conversation_id={conversation.id}&stream=true") response = chat_client.post(f"/api/chat?q={query}&conversation_id={conversation.id}&stream=true")
response_message = response.content.decode("utf-8") response_message = response.content.decode("utf-8")
# Assert # Assert
@ -640,7 +640,7 @@ def test_answer_in_chat_history_by_conversation_id_with_agent(
# Act # Act
query = urllib.parse.quote("/general What did I buy for breakfast?") query = urllib.parse.quote("/general What did I buy for breakfast?")
response = chat_client.get(f"/api/chat?q={query}&conversation_id={conversation.id}") response = chat_client.post(f"/api/chat?q={query}&conversation_id={conversation.id}")
response_message = response.json()["response"] response_message = response.json()["response"]
# Assert that agent only responds with the summary of spending # Assert that agent only responds with the summary of spending
@ -657,7 +657,7 @@ def test_answer_in_chat_history_by_conversation_id_with_agent(
def test_answer_requires_multiple_independent_searches(chat_client): def test_answer_requires_multiple_independent_searches(chat_client):
"Chat director should be able to answer by doing multiple independent searches for required information" "Chat director should be able to answer by doing multiple independent searches for required information"
# Act # Act
response = chat_client.get(f'/api/chat?q="Is Xi Li older than Namita? Just say the older persons full name"') response = chat_client.post(f'/api/chat?q="Is Xi Li older than Namita? Just say the older persons full name"')
response_message = response.json()["response"].lower() response_message = response.json()["response"].lower()
# Assert # Assert
@ -682,7 +682,7 @@ def test_answer_using_file_filter(chat_client):
'Is Xi Li older than Namita? Just say the older persons full name. file:"Namita.markdown" file:"Xi Li.markdown"' 'Is Xi Li older than Namita? Just say the older persons full name. file:"Namita.markdown" file:"Xi Li.markdown"'
) )
response = chat_client.get(f"/api/chat?q={query}") response = chat_client.post(f"/api/chat?q={query}")
response_message = response.json()["response"].lower() response_message = response.json()["response"].lower()
# Assert # Assert