From 549686a7a4188d5deb48f7fe34c45b66f4140e42 Mon Sep 17 00:00:00 2001 From: Raghav Tirumale <62105787+MythicalCow@users.noreply.github.com> Date: Mon, 9 Sep 2024 17:22:18 -0500 Subject: [PATCH] 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 Co-authored-by: Debanjum Singh Solanky --- src/interface/desktop/chat.html | 2 +- src/interface/desktop/shortcut.html | 2 +- src/interface/emacs/khoj.el | 2 +- src/interface/obsidian/src/chat_view.ts | 4 +- src/interface/web/app/chat/layout.tsx | 4 +- src/interface/web/app/chat/page.tsx | 29 +- .../components/chatHistory/chatHistory.tsx | 2 + .../chatInputArea/chatInputArea.tsx | 61 +- .../chatMessage/chatMessage.module.css | 5 + .../components/chatMessage/chatMessage.tsx | 14 +- src/interface/web/app/factchecker/page.tsx | 2 +- src/interface/web/app/layout.tsx | 4 +- src/interface/web/app/page.tsx | 6 + src/interface/web/app/share/chat/layout.tsx | 4 +- src/interface/web/app/share/chat/page.tsx | 48 +- .../web/app/share/chat/sharedChat.module.css | 8 +- src/interface/web/package.json | 2 +- src/interface/web/yarn.lock | 543 +++++++++--------- src/khoj/database/adapters/__init__.py | 12 + .../0056_chatmodeloptions_vision_enabled.py | 17 + .../migrations/0057_merge_20240816_1409.py | 13 + .../migrations/0060_merge_20240905_1828.py | 14 + src/khoj/database/models/__init__.py | 1 + src/khoj/processor/conversation/openai/gpt.py | 4 + src/khoj/processor/conversation/utils.py | 44 +- src/khoj/processor/tools/online_search.py | 8 +- src/khoj/routers/api_chat.py | 49 +- src/khoj/routers/helpers.py | 94 ++- src/khoj/routers/storage.py | 28 + src/khoj/utils/helpers.py | 15 + tests/test_client.py | 4 +- tests/test_offline_chat_director.py | 56 +- tests/test_openai_chat_director.py | 56 +- 33 files changed, 740 insertions(+), 417 deletions(-) create mode 100644 src/khoj/database/migrations/0056_chatmodeloptions_vision_enabled.py create mode 100644 src/khoj/database/migrations/0057_merge_20240816_1409.py create mode 100644 src/khoj/database/migrations/0060_merge_20240905_1828.py diff --git a/src/interface/desktop/chat.html b/src/interface/desktop/chat.html index 16119bcf..7f0fc8a4 100644 --- a/src/interface/desktop/chat.html +++ b/src/interface/desktop/chat.html @@ -154,7 +154,7 @@ ? `®ion=${region}&city=${city}&country=${countryName}&timezone=${timezone}` : ''; - const response = await fetch(chatApi, { headers }); + const response = await fetch(chatApi, { method: 'POST', headers }); try { if (!response.ok) throw new Error(response.statusText); diff --git a/src/interface/desktop/shortcut.html b/src/interface/desktop/shortcut.html index 52207f20..4ae7e2dc 100644 --- a/src/interface/desktop/shortcut.html +++ b/src/interface/desktop/shortcut.html @@ -407,7 +407,7 @@ ? `®ion=${region}&city=${city}&country=${countryName}&timezone=${timezone}` : ''; - const response = await fetch(chatApi, { headers }); + const response = await fetch(chatApi, { method: 'POST', headers }); try { if (!response.ok) throw new Error(response.statusText); diff --git a/src/interface/emacs/khoj.el b/src/interface/emacs/khoj.el index e84db5d4..b984519a 100644 --- a/src/interface/emacs/khoj.el +++ b/src/interface/emacs/khoj.el @@ -878,7 +878,7 @@ Call CALLBACK func with response and CBARGS." (let ((params `(("q" ,query) ("n" ,khoj-results-count)))) (when session-id (push `("conversation_id" ,session-id) params)) (khoj--call-api-async "/api/chat" - "GET" + "POST" params callback cbargs))) diff --git a/src/interface/obsidian/src/chat_view.ts b/src/interface/obsidian/src/chat_view.ts index 06642bd2..f55ad46d 100644 --- a/src/interface/obsidian/src/chat_view.ts +++ b/src/interface/obsidian/src/chat_view.ts @@ -1074,9 +1074,9 @@ export class KhojChatView extends KhojPaneView { }; let response = await fetch(chatUrl, { - method: "GET", + method: "POST", headers: { - "Content-Type": "text/plain", + "Content-Type": "application/json", "Authorization": `Bearer ${this.setting.khojApiKey}`, }, }) diff --git a/src/interface/web/app/chat/layout.tsx b/src/interface/web/app/chat/layout.tsx index 788a2cdc..6688cfc8 100644 --- a/src/interface/web/app/chat/layout.tsx +++ b/src/interface/web/app/chat/layout.tsx @@ -40,9 +40,9 @@ export default function RootLayout({ content="default-src 'self' https://assets.khoj.dev; media-src * blob:; 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; - 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; child-src 'none'; object-src 'none';" diff --git a/src/interface/web/app/chat/page.tsx b/src/interface/web/app/chat/page.tsx index d54376f0..2b9155d2 100644 --- a/src/interface/web/app/chat/page.tsx +++ b/src/interface/web/app/chat/page.tsx @@ -27,12 +27,14 @@ interface ChatBodyDataProps { setUploadedFiles: (files: string[]) => void; isMobileWidth?: boolean; isLoggedIn: boolean; + setImage64: (image64: string) => void; } function ChatBodyData(props: ChatBodyDataProps) { const searchParams = useSearchParams(); const conversationId = searchParams.get("conversationId"); const [message, setMessage] = useState(""); + const [image, setImage] = useState(null); const [processingMessage, setProcessingMessage] = useState(false); const [agentMetadata, setAgentMetadata] = useState(null); @@ -40,6 +42,19 @@ function ChatBodyData(props: ChatBodyDataProps) { const onConversationIdChange = props.onConversationIdChange; 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"); if (storedMessage) { setProcessingMessage(true); @@ -95,6 +110,7 @@ function ChatBodyData(props: ChatBodyDataProps) { agentColor={agentMetadata?.color} isLoggedIn={props.isLoggedIn} sendMessage={(message) => setMessage(message)} + sendImage={(image) => setImage(image)} sendDisabled={processingMessage} chatOptionsData={props.chatOptionsData} conversationId={conversationId} @@ -116,6 +132,7 @@ export default function Chat() { const [queryToProcess, setQueryToProcess] = useState(""); const [processQuerySignal, setProcessQuerySignal] = useState(false); const [uploadedFiles, setUploadedFiles] = useState([]); + const [image64, setImage64] = useState(""); const locationData = useIPLocationData(); const authenticatedData = useAuthenticatedData(); const isMobileWidth = useIsMobileWidth(); @@ -148,6 +165,7 @@ export default function Chat() { completed: false, timestamp: new Date().toISOString(), rawQuery: queryToProcess || "", + uploadedImageData: decodeURIComponent(image64), }; setMessages((prevMessages) => [...prevMessages, newStreamMessage]); setProcessQuerySignal(true); @@ -178,6 +196,7 @@ export default function Chat() { if (done) { setQueryToProcess(""); setProcessQuerySignal(false); + setImage64(""); break; } @@ -218,7 +237,14 @@ export default function Chat() { chatAPI += `®ion=${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 { await readChatStream(response); } catch (err) { @@ -282,6 +308,7 @@ export default function Chat() { setUploadedFiles={setUploadedFiles} isMobileWidth={isMobileWidth} onConversationIdChange={handleConversationIdChange} + setImage64={setImage64} /> diff --git a/src/interface/web/app/components/chatHistory/chatHistory.tsx b/src/interface/web/app/components/chatHistory/chatHistory.tsx index 453ab900..85d31f4a 100644 --- a/src/interface/web/app/components/chatHistory/chatHistory.tsx +++ b/src/interface/web/app/components/chatHistory/chatHistory.tsx @@ -267,6 +267,7 @@ export default function ChatHistory(props: ChatHistoryProps) { created: message.timestamp, by: "you", automationId: "", + uploadedImageData: message.uploadedImageData, }} customClassName="fullHistory" borderLeftColor={`${data?.agent.color}-500`} @@ -309,6 +310,7 @@ export default function ChatHistory(props: ChatHistoryProps) { created: new Date().getTime().toString(), by: "you", automationId: "", + uploadedImageData: props.pendingMessage, }} customClassName="fullHistory" borderLeftColor={`${data?.agent.color}-500`} diff --git a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx index b6d2a9f9..9a763d15 100644 --- a/src/interface/web/app/components/chatInputArea/chatInputArea.tsx +++ b/src/interface/web/app/components/chatInputArea/chatInputArea.tsx @@ -16,6 +16,7 @@ import { Microphone, Notebook, Paperclip, + X, Question, Robot, Shapes, @@ -55,6 +56,7 @@ export interface ChatOptions { interface ChatInputProps { sendMessage: (message: string) => void; + sendImage: (image: string) => void; sendDisabled: boolean; setUploadedFiles?: (files: string[]) => void; conversationId?: string | null; @@ -75,6 +77,9 @@ export default function ChatInputArea(props: ChatInputProps) { const [showLoginPrompt, setShowLoginPrompt] = useState(false); const [recording, setRecording] = useState(false); + const [imageUploaded, setImageUploaded] = useState(false); + const [imagePath, setImagePath] = useState(null); + const [imageData, setImageData] = useState(null); const [mediaRecorder, setMediaRecorder] = useState(null); const [progressValue, setProgressValue] = useState(0); @@ -97,7 +102,30 @@ export default function ChatInputArea(props: ChatInputProps) { } }, [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() { + if (imageUploaded) { + setImageUploaded(false); + setImagePath(null); + props.sendImage(imageData || ""); + } if (!message.trim()) return; if (!props.isLoggedIn) { @@ -142,6 +170,17 @@ export default function ChatInputArea(props: ChatInputProps) { setShowLoginPrompt(true); 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( files, @@ -287,6 +326,11 @@ export default function ChatInputArea(props: ChatInputProps) { setIsDragAndDropping(false); } + function removeImageUpload() { + setImageUploaded(false); + setImagePath(null); + } + return ( <> {showLoginPrompt && loginRedirectMessage && ( @@ -397,11 +441,24 @@ export default function ChatInputArea(props: ChatInputProps) { )}
+ {imageUploaded && ( +
+
+ img +
+
+ +
+
+ )} { if (e.key === "Enter" && !e.shiftKey) { + setImageUploaded(false); + setImagePath(null); e.preventDefault(); onSendMessage(); } diff --git a/src/interface/web/app/components/chatMessage/chatMessage.module.css b/src/interface/web/app/components/chatMessage/chatMessage.module.css index 809acc2e..17654d5c 100644 --- a/src/interface/web/app/components/chatMessage/chatMessage.module.css +++ b/src/interface/web/app/components/chatMessage/chatMessage.module.css @@ -53,6 +53,11 @@ div.chatMessageContainer h3 img { width: 24px; } +div.you img { + height: 16rem; + width: auto; +} + div.you { color: hsla(var(--secondary-foreground)); } diff --git a/src/interface/web/app/components/chatMessage/chatMessage.tsx b/src/interface/web/app/components/chatMessage/chatMessage.tsx index 98f74d5a..5fa20c2b 100644 --- a/src/interface/web/app/components/chatMessage/chatMessage.tsx +++ b/src/interface/web/app/components/chatMessage/chatMessage.tsx @@ -111,6 +111,7 @@ export interface SingleChatMessage { rawQuery?: string; intent?: Intent; agent?: AgentData; + uploadedImageData?: string; } export interface StreamMessage { @@ -122,6 +123,7 @@ export interface StreamMessage { rawQuery: string; timestamp: string; agent?: AgentData; + uploadedImageData?: string; } export interface ChatHistoryData { @@ -203,6 +205,7 @@ interface ChatMessageProps { borderLeftColor?: string; isLastMessage?: boolean; agent?: AgentData; + uploadedImageData?: string; } interface TrainOfThoughtProps { @@ -273,6 +276,7 @@ export function TrainOfThought(props: TrainOfThoughtProps) { export default function ChatMessage(props: ChatMessageProps) { const [copySuccess, setCopySuccess] = useState(false); const [isHovering, setIsHovering] = useState(false); + const [textRendered, setTextRendered] = useState(""); const [markdownRendered, setMarkdownRendered] = useState(""); const [isPlaying, setIsPlaying] = useState(false); const [interrupted, setInterrupted] = useState(false); @@ -322,6 +326,10 @@ export default function ChatMessage(props: ChatMessageProps) { .replace(/\\\[/g, "LEFTBRACKET") .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") { message = `![generated image](data:image/png;base64,${message})`; } 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]}`; } + setTextRendered(message); + + // Render the markdown let markdownRendered = md.render(message); // Replace placeholders with LaTeX delimiters @@ -542,7 +553,6 @@ export default function ChatMessage(props: ChatMessageProps) { className={constructClasses(props.chatMessage)} onMouseLeave={(event) => setIsHovering(false)} onMouseEnter={(event) => setIsHovering(true)} - onClick={props.chatMessage.by === "khoj" ? (event) => undefined : undefined} >
{ - navigator.clipboard.writeText(props.chatMessage.message); + navigator.clipboard.writeText(textRendered); setCopySuccess(true); }} > diff --git a/src/interface/web/app/factchecker/page.tsx b/src/interface/web/app/factchecker/page.tsx index 1bc7a54f..d44f3259 100644 --- a/src/interface/web/app/factchecker/page.tsx +++ b/src/interface/web/app/factchecker/page.tsx @@ -79,7 +79,7 @@ async function verifyStatement( let verificationMessage = `${verificationPrecursor} ${message}`; const apiURL = `${chatURL}?q=${encodeURIComponent(verificationMessage)}&client=web&stream=true&conversation_id=${conversationId}`; try { - const response = await fetch(apiURL); + const response = await fetch(apiURL, { method: "POST" }); if (!response.body) throw new Error("No response body found"); const reader = response.body?.getReader(); diff --git a/src/interface/web/app/layout.tsx b/src/interface/web/app/layout.tsx index 007298c3..b7e6c0be 100644 --- a/src/interface/web/app/layout.tsx +++ b/src/interface/web/app/layout.tsx @@ -45,9 +45,9 @@ export default function RootLayout({ content="default-src 'self' https://assets.khoj.dev; media-src * blob:; 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; - 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; child-src 'none'; object-src 'none';" diff --git a/src/interface/web/app/page.tsx b/src/interface/web/app/page.tsx index 05c3dd40..c5cf7012 100644 --- a/src/interface/web/app/page.tsx +++ b/src/interface/web/app/page.tsx @@ -43,6 +43,7 @@ function FisherYatesShuffle(array: any[]) { function ChatBodyData(props: ChatBodyDataProps) { const [message, setMessage] = useState(""); + const [image, setImage] = useState(null); const [processingMessage, setProcessingMessage] = useState(false); const [greeting, setGreeting] = useState(""); const [shuffledOptions, setShuffledOptions] = useState([]); @@ -141,6 +142,9 @@ function ChatBodyData(props: ChatBodyDataProps) { onConversationIdChange?.(newConversationId); window.location.href = `/chat?conversationId=${newConversationId}`; localStorage.setItem("message", message); + if (image) { + localStorage.setItem("image", image); + } } catch (error) { console.error("Error creating new conversation:", error); setProcessingMessage(false); @@ -230,6 +234,7 @@ function ChatBodyData(props: ChatBodyDataProps) { setMessage(message)} + sendImage={(image) => setImage(image)} sendDisabled={processingMessage} chatOptionsData={props.chatOptionsData} conversationId={null} @@ -310,6 +315,7 @@ function ChatBodyData(props: ChatBodyDataProps) { setMessage(message)} + sendImage={(image) => setImage(image)} sendDisabled={processingMessage} chatOptionsData={props.chatOptionsData} conversationId={null} diff --git a/src/interface/web/app/share/chat/layout.tsx b/src/interface/web/app/share/chat/layout.tsx index 8c243190..095a12a2 100644 --- a/src/interface/web/app/share/chat/layout.tsx +++ b/src/interface/web/app/share/chat/layout.tsx @@ -20,9 +20,9 @@ export default function RootLayout({ httpEquiv="Content-Security-Policy" content="default-src 'self' https://assets.khoj.dev; 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; - 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; child-src 'none'; object-src 'none';" diff --git a/src/interface/web/app/share/chat/page.tsx b/src/interface/web/app/share/chat/page.tsx index b07a6df2..eefc159c 100644 --- a/src/interface/web/app/share/chat/page.tsx +++ b/src/interface/web/app/share/chat/page.tsx @@ -28,16 +28,23 @@ interface ChatBodyDataProps { isLoggedIn: boolean; conversationId?: string; setQueryToProcess: (query: string) => void; + setImage64: (image64: string) => void; } function ChatBodyData(props: ChatBodyDataProps) { const [message, setMessage] = useState(""); + const [image, setImage] = useState(null); const [processingMessage, setProcessingMessage] = useState(false); const [agentMetadata, setAgentMetadata] = useState(null); - const setQueryToProcess = props.setQueryToProcess; const streamedMessages = props.streamedMessages; + useEffect(() => { + if (image) { + props.setImage64(encodeURIComponent(image)); + } + }, [image, props.setImage64]); + useEffect(() => { if (message) { setProcessingMessage(true); @@ -74,11 +81,12 @@ function ChatBodyData(props: ChatBodyDataProps) { />
setMessage(message)} + sendImage={(image) => setImage(image)} sendDisabled={processingMessage} chatOptionsData={props.chatOptionsData} conversationId={props.conversationId} @@ -101,6 +109,7 @@ export default function SharedChat() { const [processQuerySignal, setProcessQuerySignal] = useState(false); const [uploadedFiles, setUploadedFiles] = useState([]); const [paramSlug, setParamSlug] = useState(undefined); + const [image64, setImage64] = useState(""); const locationData = useIPLocationData(); const authenticatedData = useAuthenticatedData(); @@ -156,6 +165,7 @@ export default function SharedChat() { completed: false, timestamp: new Date().toISOString(), rawQuery: queryToProcess || "", + uploadedImageData: decodeURIComponent(image64), }; setMessages((prevMessages) => [...prevMessages, newStreamMessage]); setProcessQuerySignal(true); @@ -182,6 +192,7 @@ export default function SharedChat() { if (done) { setQueryToProcess(""); setProcessQuerySignal(false); + setImage64(""); break; } @@ -216,33 +227,21 @@ export default function SharedChat() { chatAPI += `®ion=${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 { await readChatStream(response); - } catch (err) { - console.log(err); + } catch (error) { + 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) { return ; } @@ -275,6 +274,7 @@ export default function SharedChat() { setTitle={setTitle} setUploadedFiles={setUploadedFiles} isMobileWidth={isMobileWidth} + setImage64={setImage64} />
diff --git a/src/interface/web/app/share/chat/sharedChat.module.css b/src/interface/web/app/share/chat/sharedChat.module.css index 6adf821c..703b0fb1 100644 --- a/src/interface/web/app/share/chat/sharedChat.module.css +++ b/src/interface/web/app/share/chat/sharedChat.module.css @@ -12,15 +12,11 @@ div.main { div.inputBox { border: 1px solid var(--border-color); - border-radius: 16px; margin-bottom: 20px; gap: 12px; - padding-left: 20px; - padding-right: 20px; align-content: center; } - input.inputBox { border: none; } @@ -31,7 +27,7 @@ input.inputBox:focus { } div.inputBox:focus { - box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.2); + box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2); } div.chatBodyFull { @@ -94,7 +90,6 @@ div.agentIndicator { padding: 10px; } - @media (max-width: 768px) { div.chatBody { grid-template-columns: 0fr 1fr; @@ -124,5 +119,4 @@ div.agentIndicator { gap: 0; grid-template-columns: 1fr; } - } diff --git a/src/interface/web/package.json b/src/interface/web/package.json index 4d79e700..e942aebd 100644 --- a/src/interface/web/package.json +++ b/src/interface/web/package.json @@ -47,7 +47,7 @@ "eslint": "^8", "eslint-config-next": "14.2.3", "input-otp": "^1.2.4", - "intl-tel-input": "^23.8.0", + "intl-tel-input": "^23.8.1", "katex": "^0.16.10", "libphonenumber-js": "^1.11.4", "lucide-react": "^0.397.0", diff --git a/src/interface/web/yarn.lock b/src/interface/web/yarn.lock index e697f948..e6fc7f36 100644 --- a/src/interface/web/yarn.lock +++ b/src/interface/web/yarn.lock @@ -28,38 +28,38 @@ "@babel/highlight" "^7.24.7" picocolors "^1.0.0" -"@babel/compat-data@^7.24.8": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.0.tgz#6b226a5da3a686db3c30519750e071dce292ad95" - integrity sha512-P4fwKI2mjEb3ZU5cnMJzvRsRKGBUcs8jvxIoRmr6ufAY9Xk2Bz7JubRTTivkw55c7WQJfTECeqYVa+HZ0FzREg== +"@babel/compat-data@^7.25.2": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb" + integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ== "@babel/core@^7.22.1": - version "7.24.9" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.9.tgz#dc07c9d307162c97fa9484ea997ade65841c7c82" - integrity sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg== + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77" + integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA== dependencies: "@ampproject/remapping" "^2.2.0" "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.24.9" - "@babel/helper-compilation-targets" "^7.24.8" - "@babel/helper-module-transforms" "^7.24.9" - "@babel/helpers" "^7.24.8" - "@babel/parser" "^7.24.8" - "@babel/template" "^7.24.7" - "@babel/traverse" "^7.24.8" - "@babel/types" "^7.24.9" + "@babel/generator" "^7.25.0" + "@babel/helper-compilation-targets" "^7.25.2" + "@babel/helper-module-transforms" "^7.25.2" + "@babel/helpers" "^7.25.0" + "@babel/parser" "^7.25.0" + "@babel/template" "^7.25.0" + "@babel/traverse" "^7.25.2" + "@babel/types" "^7.25.2" convert-source-map "^2.0.0" debug "^4.1.0" gensync "^1.0.0-beta.2" json5 "^2.2.3" semver "^6.3.1" -"@babel/generator@^7.24.9", "@babel/generator@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.0.tgz#f858ddfa984350bc3d3b7f125073c9af6988f18e" - integrity sha512-3LEEcj3PVW8pW2R1SR1M89g/qrYk/m/mB/tLqn7dn4sbBUQyTqnlod+II2U4dqiGtUmkcnAmkMDralTFZttRiw== +"@babel/generator@^7.25.0", "@babel/generator@^7.25.6": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.6.tgz#0df1ad8cb32fe4d2b01d8bf437f153d19342a87c" + integrity sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw== dependencies: - "@babel/types" "^7.25.0" + "@babel/types" "^7.25.6" "@jridgewell/gen-mapping" "^0.3.5" "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" @@ -71,28 +71,28 @@ dependencies: "@babel/types" "^7.24.7" -"@babel/helper-compilation-targets@^7.24.8": - version "7.24.8" - resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz#b607c3161cd9d1744977d4f97139572fe778c271" - integrity sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw== +"@babel/helper-compilation-targets@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c" + integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw== dependencies: - "@babel/compat-data" "^7.24.8" + "@babel/compat-data" "^7.25.2" "@babel/helper-validator-option" "^7.24.8" browserslist "^4.23.1" lru-cache "^5.1.1" semver "^6.3.1" "@babel/helper-create-class-features-plugin@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.0.tgz#a109bf9c3d58dfed83aaf42e85633c89f43a6253" - integrity sha512-GYM6BxeQsETc9mnct+nIIpf63SAyzvyYN7UB/IlTyd+MBg06afFGp0mIeUqGyWgS2mxad6vqbMrHVlaL3m70sQ== + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.4.tgz#57eaf1af38be4224a9d9dd01ddde05b741f50e14" + integrity sha512-ro/bFs3/84MDgDmMwbcHgDa8/E6J3QKNTk4xJJnVeFtGE+tL0K26E3pNxhYz2b67fJpt7Aphw5XcploKXuCvCQ== dependencies: "@babel/helper-annotate-as-pure" "^7.24.7" "@babel/helper-member-expression-to-functions" "^7.24.8" "@babel/helper-optimise-call-expression" "^7.24.7" "@babel/helper-replace-supers" "^7.25.0" "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" - "@babel/traverse" "^7.25.0" + "@babel/traverse" "^7.25.4" semver "^6.3.1" "@babel/helper-member-expression-to-functions@^7.24.8": @@ -111,15 +111,15 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" -"@babel/helper-module-transforms@^7.24.9": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.0.tgz#3ffc23c473a2769a7e40d3274495bd559fdd2ecc" - integrity sha512-bIkOa2ZJYn7FHnepzr5iX9Kmz8FjIz4UKzJ9zhX3dnYuVW0xul9RuR3skBfoLu+FPTQw90EHW9rJsSZhyLQ3fQ== +"@babel/helper-module-transforms@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6" + integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ== dependencies: "@babel/helper-module-imports" "^7.24.7" "@babel/helper-simple-access" "^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": version "7.24.7" @@ -128,7 +128,7 @@ dependencies: "@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" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" 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" integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== -"@babel/helpers@^7.24.8": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.0.tgz#e69beb7841cb93a6505531ede34f34e6a073650a" - integrity sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw== +"@babel/helpers@^7.25.0": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.6.tgz#57ee60141829ba2e102f30711ffe3afab357cc60" + integrity sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q== dependencies: "@babel/template" "^7.25.0" - "@babel/types" "^7.25.0" + "@babel/types" "^7.25.6" "@babel/highlight@^7.24.7": version "7.24.7" @@ -191,22 +191,24 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.22.6", "@babel/parser@^7.24.8", "@babel/parser@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.0.tgz#9fdc9237504d797b6e7b8f66e78ea7f570d256ad" - integrity sha512-CzdIU9jdP0dg7HdyB+bHvDJGagUv+qtzZt5rYCWwW6tITNqV9odjp6Qu41gkG0ca5UfdDUWrKkiAnHHdGRnOrA== +"@babel/parser@^7.22.6", "@babel/parser@^7.25.0", "@babel/parser@^7.25.6": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.6.tgz#85660c5ef388cbbf6e3d2a694ee97a38f18afe2f" + integrity sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q== + dependencies: + "@babel/types" "^7.25.6" "@babel/plugin-syntax-typescript@^7.24.7": - version "7.24.7" - resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.24.7.tgz#58d458271b4d3b6bb27ee6ac9525acbb259bad1c" - integrity sha512-c/+fVeJBB0FeKsFvwytYiUD+LBvhHjGSI0g446PRGdSVGZLRNArBUno2PETbAly3tpiNAQR5XaZ+JslxkotsbA== + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.25.4.tgz#04db9ce5a9043d9c635e75ae7969a2cd50ca97ff" + integrity sha512-uMOCoHVU52BsSWxPOMVv5qKRdeSlPuImUCB2dlPuBSU+W2/ROE7/Zg8F2Kepbk+8yBa68LlRKxO+xgEVWorsDg== dependencies: - "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" "@babel/plugin-transform-typescript@^7.22.5": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.0.tgz#56f47fb87b86a97caa9c7770920a1967d40ac86e" - integrity sha512-LZicxFzHIw+Sa3pzgMgSz6gdpsdkfiMObHUzhSIrwKF0+/rP/nuR49u79pSS+zIFJ1FeGeqQD2Dq4QGFbOVvSw== + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.2.tgz#237c5d10de6d493be31637c6b9fa30b6c5461add" + integrity sha512-lBwRvjSmqiMYe/pS0+1gggjJleUJi7NzjvQ1Fkqtt69hBa/0t1YuW/MLQMAPixfwaQOHUXsd6jeU3Z+vdGv3+A== dependencies: "@babel/helper-annotate-as-pure" "^7.24.7" "@babel/helper-create-class-features-plugin" "^7.25.0" @@ -215,13 +217,13 @@ "@babel/plugin-syntax-typescript" "^7.24.7" "@babel/runtime@^7.13.10": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.0.tgz#3af9a91c1b739c569d5d80cc917280919c544ecb" - integrity sha512-7dRy4DwXwtzBrPbZflqxnvfxLF8kdZXPkhymtDeFoFqE6ldzjQFgYTtYIFARcLEYDrqfBfYcZt1WqFxRoyC9Rw== + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2" + integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ== dependencies: regenerator-runtime "^0.14.0" -"@babel/template@^7.24.7", "@babel/template@^7.25.0": +"@babel/template@^7.25.0": version "7.25.0" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== @@ -230,23 +232,23 @@ "@babel/parser" "^7.25.0" "@babel/types" "^7.25.0" -"@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8", "@babel/traverse@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.0.tgz#e8533c0a57ed97921d1e5b20dd3db63d3efaebf5" - integrity sha512-ubALThHQy4GCf6mbb+5ZRNmLLCI7bJ3f8Q6LHBSRlSKSWj5a7dSUzJBLv3VuIhFrFPgjF4IzPF567YG/HSCdZA== +"@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.6" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.6.tgz#04fad980e444f182ecf1520504941940a90fea41" + integrity sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ== dependencies: "@babel/code-frame" "^7.24.7" - "@babel/generator" "^7.25.0" - "@babel/parser" "^7.25.0" + "@babel/generator" "^7.25.6" + "@babel/parser" "^7.25.6" "@babel/template" "^7.25.0" - "@babel/types" "^7.25.0" + "@babel/types" "^7.25.6" debug "^4.3.1" globals "^11.1.0" -"@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.24.9", "@babel/types@^7.25.0": - version "7.25.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.0.tgz#e6e3656c581f28da8452ed4f69e38008ec0ba41b" - integrity sha512-LcnxQSsd9aXOIgmmSpvZ/1yo46ra2ESYyqLcryaBZOghxy5qqOBjvCWP5JfkI8yl9rlxRgdLTTMCQQRcN2hdCg== +"@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.6" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.6.tgz#893942ddb858f32ae7a004ec9d3a76b3463ef8e6" + integrity sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw== dependencies: "@babel/helper-string-parser" "^7.24.8" "@babel/helper-validator-identifier" "^7.24.7" @@ -285,19 +287,19 @@ integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== "@floating-ui/core@^1.6.0": - version "1.6.5" - resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.5.tgz#102335cac0d22035b04d70ca5ff092d2d1a26f2b" - integrity sha512-8GrTWmoFhm5BsMZOTHeGD2/0FLKLQQHvO/ZmQga4tKempYRLz8aqJGqXVuQgisnMObq2YZ2SgkwctN1LOOxcqA== + version "1.6.7" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.7.tgz#7602367795a390ff0662efd1c7ae8ca74e75fb12" + integrity sha512-yDzVT/Lm101nQ5TCVeK65LtdN7Tj4Qpr9RTXJ2vPFLqtLxwOrpoxAHAJI8J3yYWUc40J0BDBheaitK5SJmno2g== dependencies: - "@floating-ui/utils" "^0.2.5" + "@floating-ui/utils" "^0.2.7" "@floating-ui/dom@^1.0.0": - version "1.6.8" - resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.8.tgz#45e20532b6d8a061b356a4fb336022cf2609754d" - integrity sha512-kx62rP19VZ767Q653wsP1XZCGIirkE09E0QUGNYTM/ttbbQHqcGPdSfWFxUyyNLc/W6aoJRBajOSXhP6GXjC0Q== + version "1.6.10" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.10.tgz#b74c32f34a50336c86dcf1f1c845cf3a39e26d6f" + integrity sha512-fskgCFv8J8OamCmyun8MfjB1Olfn+uZKjOKZ0vhYF3gRmEUXcGOjxWL8bBr7i4kIuPZ2KD2S3EUIOxnjC8kl2A== dependencies: "@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": version "2.1.1" @@ -306,10 +308,10 @@ dependencies: "@floating-ui/dom" "^1.0.0" -"@floating-ui/utils@^0.2.5": - version "0.2.5" - resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.5.tgz#105c37d9d9620ce69b7f692a20c821bf1ad2cbf9" - integrity sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ== +"@floating-ui/utils@^0.2.7": + version "0.2.7" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.7.tgz#d0ece53ce99ab5a8e37ebdfe5e32452a2bfc073e" + integrity sha512-X8R8Oj771YRl/w+c1HqAC1szL8zWQRwFvgDwT129k9ACdBoud/+/rX9V0qiMl6LWUdP9voC2nDVZYPMQQsb6eA== "@hookform/resolvers@^3.9.0": version "3.9.0" @@ -457,6 +459,11 @@ "@nodelib/fs.scandir" "2.1.5" 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": version "2.1.7" resolved "https://registry.yarnpkg.com/@phosphor-icons/react/-/react-2.1.7.tgz#b11a4b25849b7e3849970b688d9fe91e5d4fd8d7" @@ -1220,9 +1227,9 @@ integrity sha512-A9+lCBZoaMJlVKcRBz2YByCG+Cp2t6nAnMnNba+XiWxnj6r4JUFqfsgwocMBZU9LPtdxC6wB56ySYpc7LQIoJg== "@radix-ui/themes@^3.1.1": - version "3.1.1" - resolved "https://registry.yarnpkg.com/@radix-ui/themes/-/themes-3.1.1.tgz#a444f181975b8550f548c989ef3a432908c51075" - integrity sha512-G+j+x+7kyqQXnn+ftlNPgk1DdZ8h/vVZnLsG4hZB0Mxw4fdKCh1tThQuXDSBNWhFt/vTG79BMzRMiflovENrmA== + version "3.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/themes/-/themes-3.1.3.tgz#9b4287d4e60c9a175e0e453ba620d693fadf6f7e" + integrity sha512-GJt4r7Vh0w4yiGuqOKLvrZXLEAxUWfEUUtdj17rCfi+P/zpKUc7TsXro8GftA2Y1tm78Cx9x1HKt23dHc/WqIA== dependencies: "@radix-ui/colors" "3.0.0" "@radix-ui/primitive" "1.1.0" @@ -1260,6 +1267,11 @@ classnames "2.3.2" 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": version "1.10.4" resolved "https://registry.yarnpkg.com/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz#427d5549943a9c6fce808e39ea64dbe60d4047f1" @@ -1338,11 +1350,11 @@ integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg== "@types/node@^20": - version "20.14.12" - resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.12.tgz#129d7c3a822cb49fc7ff661235f19cfefd422b49" - integrity sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ== + version "20.16.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.16.5.tgz#d43c7f973b32ffdf9aa7bd4f80e1072310fd7a53" + integrity sha512-VwYCweNo3ERajwy0IUlqqcyZ8/A7Zwa9ZP3MnENWcB11AejO+tLy3pu850goUW2FC/IJMdZUfKpX/yxL1gymCA== dependencies: - undici-types "~5.26.4" + undici-types "~6.19.2" "@types/prop-types@*": version "15.7.12" @@ -1357,9 +1369,9 @@ "@types/react" "*" "@types/react@*", "@types/react@^18": - version "18.3.3" - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.3.tgz#9679020895318b0915d7a3ab004d92d33375c45f" - integrity sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw== + version "18.3.5" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.3.5.tgz#5f524c2ad2089c0ff372bbdabc77ca2c4dbadf8f" + integrity sha512-WeqMfGJLGuLCqHGYRGHxnKrXcTitc6L/nBUWfWPcTarG3t9PsquqUMuVeXZeca+mglY4Vo5GZjCi0A3Or2lnxA== dependencies: "@types/prop-types" "*" 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" 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" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== @@ -1562,7 +1574,7 @@ array.prototype.findlast@^1.2.5: es-object-atoms "^1.0.0" es-shim-unscopables "^1.0.2" -array.prototype.findlastindex@^1.2.3: +array.prototype.findlastindex@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d" integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ== @@ -1632,15 +1644,15 @@ ast-types@^0.16.1: tslib "^2.0.1" autoprefixer@^10.4.19: - version "10.4.19" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.19.tgz#ad25a856e82ee9d7898c59583c1afeb3fa65f89f" - integrity sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew== + version "10.4.20" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.20.tgz#5caec14d43976ef42e32dcb4bd62878e96be5b3b" + integrity sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g== dependencies: - browserslist "^4.23.0" - caniuse-lite "^1.0.30001599" + browserslist "^4.23.3" + caniuse-lite "^1.0.30001646" fraction.js "^4.3.7" normalize-range "^0.1.2" - picocolors "^1.0.0" + picocolors "^1.0.1" postcss-value-parser "^4.2.0" available-typed-arrays@^1.0.7: @@ -1650,17 +1662,15 @@ available-typed-arrays@^1.0.7: dependencies: possible-typed-array-names "^1.0.0" -axe-core@^4.9.1: - version "4.9.1" - resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.9.1.tgz#fcd0f4496dad09e0c899b44f6c4bb7848da912ae" - integrity sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw== +axe-core@^4.10.0: + version "4.10.0" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.10.0.tgz#d9e56ab0147278272739a000880196cdfe113b59" + integrity sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g== -axobject-query@~3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-3.1.1.tgz#3b6e5c6d4e43ca7ba51c5babf99d22a9c68485e1" - integrity sha512-goKlv8DZrK9hUh975fnHzhNIO4jUnFCfv/dszV5VwUGDFjI6vQ2VwoyjYjYNEbBE8AH87TduWP5uyDR1D+Iteg== - dependencies: - deep-equal "^2.0.5" +axobject-query@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-4.1.0.tgz#28768c76d0e3cff21bc62a9e2d0b6ac30042a1ee" + integrity sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ== balanced-match@^1.0.0: version "1.0.2" @@ -1708,14 +1718,14 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.23.0, browserslist@^4.23.1: - version "4.23.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.2.tgz#244fe803641f1c19c28c48c4b6ec9736eb3d32ed" - integrity sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA== +browserslist@^4.23.1, browserslist@^4.23.3: + version "4.23.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800" + integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA== dependencies: - caniuse-lite "^1.0.30001640" - electron-to-chromium "^1.4.820" - node-releases "^2.0.14" + caniuse-lite "^1.0.30001646" + electron-to-chromium "^1.5.4" + node-releases "^2.0.18" update-browserslist-db "^1.1.0" 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" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== -caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001599, caniuse-lite@^1.0.30001640: - version "1.0.30001643" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz#9c004caef315de9452ab970c3da71085f8241dbd" - integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg== +caniuse-lite@^1.0.30001579, caniuse-lite@^1.0.30001646: + version "1.0.30001658" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001658.tgz#b5f7be8ac748a049ab06aa1cf7a1408d83f074ec" + integrity sha512-N2YVqWbJELVdrnsW5p+apoQyYt51aBMSsBZki1XZEfeBCexcM/sf4xiAHcXQBkuOwJBXtWF7aW1sYX6tKebPHw== chalk@5.2.0: version "5.2.0" @@ -2003,12 +2013,12 @@ data-view-byte-offset@^1.0.0: es-errors "^1.3.0" 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: - version "4.3.5" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" - integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== +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.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" + integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== dependencies: - ms "2.1.2" + ms "^2.1.3" debug@^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" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== -electron-to-chromium@^1.4.820: - version "1.5.2" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.2.tgz#6126ad229ce45e781ec54ca40db0504787f23d19" - integrity sha512-kc4r3U3V3WLaaZqThjYz/Y6z8tJe+7K0bbjUVo3i+LWIypVdMx5nXCkwRe6SWbY6ILqLdc1rKcKmr3HoH7wjSQ== +electron-to-chromium@^1.5.4: + version "1.5.17" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.17.tgz#292da718d3d96961d022e49bb843e0c4ea10be70" + integrity sha512-Q6Q+04tjC2KJ8qsSOSgovvhWcv5t+SmpH6/YfAWmhpE5/r+zw6KQy1/yWVFFNyEBvy68twTTXr2d5eLfCq7QIw== emoji-regex@^10.3.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.3.0.tgz#76998b9268409eb3dae3de989254d456e70cfe23" - integrity sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw== + version "10.4.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.4.0.tgz#03553afea80b3975749cfcb36f776ca268e413d4" + integrity sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw== emoji-regex@^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" integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== -enhanced-resolve@^5.12.0: +enhanced-resolve@^5.15.0: version "5.17.1" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== @@ -2299,9 +2309,9 @@ es-to-primitive@^1.2.1: is-symbol "^1.0.2" escalade@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" - integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + version "3.2.0" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.2.0.tgz#011a3f69856ba189dffa7dc8fcce99d2a87903e5" + integrity sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA== escape-string-regexp@^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" eslint-import-resolver-typescript@^3.5.2: - version "3.6.1" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.1.tgz#7b983680edd3f1c5bce1a5829ae0bc2d57fe9efa" - integrity sha512-xgdptdoi5W3niYeuQxKmzVDTATvLYqhpwmykwsh7f6HIOStGWEIL9iqZgQDF9u9OEzrRwR8no5q2VT+bjAujTg== + version "3.6.3" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.6.3.tgz#bb8e388f6afc0f940ce5d2c5fd4a3d147f038d9e" + integrity sha512-ud9aw4szY9cCT1EWWdGv1L1XR6hh2PaRWif0j2QjQ0pgTY/69iw+W0Z4qZv5wHahOl8isEr+k/JnyAqNQkLkIA== dependencies: - debug "^4.3.4" - enhanced-resolve "^5.12.0" - eslint-module-utils "^2.7.4" - fast-glob "^3.3.1" - get-tsconfig "^4.5.0" - is-core-module "^2.11.0" + "@nolyfill/is-core-module" "1.0.39" + debug "^4.3.5" + enhanced-resolve "^5.15.0" + eslint-module-utils "^2.8.1" + fast-glob "^3.3.2" + get-tsconfig "^4.7.5" + is-bun-module "^1.0.2" is-glob "^4.0.3" -eslint-module-utils@^2.7.4, eslint-module-utils@^2.8.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz#52f2404300c3bd33deece9d7372fb337cc1d7c34" - integrity sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q== +eslint-module-utils@^2.8.1, eslint-module-utils@^2.9.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.11.0.tgz#b99b211ca4318243f09661fae088f373ad5243c4" + integrity sha512-gbBE5Hitek/oG6MUVj6sFuzEjA/ClzNflVrLovHi/JgLdC7fiN5gLAY1WIPW1a0V5I999MnsrvVrCOGmmVqDBQ== dependencies: debug "^3.2.7" eslint-plugin-import@^2.28.1: - version "2.29.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz#d45b37b5ef5901d639c15270d74d46d161150643" - integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw== + version "2.30.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz#21ceea0fc462657195989dd780e50c92fe95f449" + integrity sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw== dependencies: - array-includes "^3.1.7" - array.prototype.findlastindex "^1.2.3" + "@rtsao/scc" "^1.1.0" + array-includes "^3.1.8" + array.prototype.findlastindex "^1.2.5" array.prototype.flat "^1.3.2" array.prototype.flatmap "^1.3.2" debug "^3.2.7" doctrine "^2.1.0" eslint-import-resolver-node "^0.3.9" - eslint-module-utils "^2.8.0" - hasown "^2.0.0" - is-core-module "^2.13.1" + eslint-module-utils "^2.9.0" + hasown "^2.0.2" + is-core-module "^2.15.1" is-glob "^4.0.3" minimatch "^3.1.2" - object.fromentries "^2.0.7" - object.groupby "^1.0.1" - object.values "^1.1.7" + object.fromentries "^2.0.8" + object.groupby "^1.0.3" + object.values "^1.2.0" semver "^6.3.1" tsconfig-paths "^3.15.0" eslint-plugin-jsx-a11y@^6.7.1: - version "6.9.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.9.0.tgz#67ab8ff460d4d3d6a0b4a570e9c1670a0a8245c8" - integrity sha512-nOFOCaJG2pYqORjK19lqPqxMO/JpvdCZdPtNdxY3kvom3jTvkAbOvQvD8wuD0G8BYR0IGAGYDlzqWJOh/ybn2g== + version "6.10.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.0.tgz#36fb9dead91cafd085ddbe3829602fb10ef28339" + integrity sha512-ySOHvXX8eSN6zz8Bywacm7CvGNhUtdjvqfQDVe6020TUK34Cywkw7m0KsCCk1Qtm9G1FayfTN1/7mMYnYO2Bhg== dependencies: aria-query "~5.1.3" array-includes "^3.1.8" array.prototype.flatmap "^1.3.2" ast-types-flow "^0.0.8" - axe-core "^4.9.1" - axobject-query "~3.1.1" + axe-core "^4.10.0" + axobject-query "^4.1.0" damerau-levenshtein "^1.0.8" emoji-regex "^9.2.2" es-iterator-helpers "^1.0.19" @@ -2421,9 +2433,9 @@ eslint-plugin-prettier@^5.1.3: integrity sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ== eslint-plugin-react@^7.33.2: - version "7.35.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz#00b1e4559896710e58af6358898f2ff917ea4c41" - integrity sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA== + version "7.35.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.35.2.tgz#d32500d3ec268656d5071918bfec78cfd8b070ed" + integrity sha512-Rbj2R9zwP2GYNcIak4xoAMV57hrBh3hTaR0k7hVjwCQgryE/pw5px4b13EYjduOI0hfXyZhwBxaGpOTbWSGzKQ== dependencies: array-includes "^3.1.8" 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" 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" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -2664,9 +2676,9 @@ for-each@^0.3.3: is-callable "^1.1.3" foreground-child@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.2.1.tgz#767004ccf3a5b30df39bed90718bab43fe0a59f7" - integrity sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA== + version "3.3.0" + resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.3.0.tgz#0ac8644c06e431439f8561db8ecf29a7b5519c77" + integrity sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg== dependencies: cross-spawn "^7.0.0" 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" 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" resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.6.tgz#cdf315b7d90ee77a4c6ee216c3c3362da07533fd" integrity sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg== @@ -2767,10 +2779,10 @@ get-symbol-description@^1.0.2: es-errors "^1.3.0" get-intrinsic "^1.2.4" -get-tsconfig@^4.5.0: - version "4.7.6" - resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.7.6.tgz#118fd5b7b9bae234cc7705a00cd771d7eb65d62a" - integrity sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA== +get-tsconfig@^4.7.5: + version "4.8.0" + resolved "https://registry.yarnpkg.com/get-tsconfig/-/get-tsconfig-4.8.0.tgz#125dc13a316f61650a12b20c97c11b8fd996fedd" + integrity sha512-Pgba6TExTZ0FJAn1qkJAjIeKoDJ3CsI2ChuLohJnZl/tTU8MVrq3b+2t5UOPfRa4RMsorClBjJALkJUMjG1PAw== dependencies: resolve-pkg-maps "^1.0.0" @@ -2942,9 +2954,9 @@ human-signals@^5.0.0: integrity sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ== husky@^9.0.11: - version "9.1.3" - resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.3.tgz#46cddff01f9a551f87b39accc67860bce5d00680" - integrity sha512-ET3TQmQgdIu0pt+jKkpo5oGyg/4MQZpG6xcam5J5JyNJV+CBT23OBpCF15bKHKycRyMH9k6ONy8g2HdGIsSkMQ== + version "9.1.5" + resolved "https://registry.yarnpkg.com/husky/-/husky-9.1.5.tgz#2b6edede53ee1adbbd3a3da490628a23f5243b83" + integrity sha512-rowAVRUBfI0b4+niA4SJMhfQwc107VLkBUgEYYAOQAbqDCnra1nYh83hF/MDmhYs9t9n1E3DuKOrs2LYNC+0Ag== ieee754@^1.2.1: version "1.2.1" @@ -2957,9 +2969,9 @@ ignore-by-default@^1.0.1: integrity sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA== ignore@^5.2.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" - integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== import-fresh@^3.2.1, import-fresh@^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" side-channel "^1.0.4" -intl-tel-input@^23.8.0: - version "23.8.0" - resolved "https://registry.yarnpkg.com/intl-tel-input/-/intl-tel-input-23.8.0.tgz#37bec095605516aa72529b3da11335b253b65b2f" - integrity sha512-lx8Sz5LfVyIXyjWbfjno89o4qHfIuWulctGaWbP2RKKnHvagdt9gdibCsv9uEH7izb/yjB6Nst0sRo988/lhpw== +intl-tel-input@^23.8.1: + version "23.9.3" + resolved "https://registry.yarnpkg.com/intl-tel-input/-/intl-tel-input-23.9.3.tgz#3870c78c16655bdc13e18cae557efcaa43dce719" + integrity sha512-vu/Hm825wPlkg2cOtWWgG5fRw2+M7G0sUhQKdZgP4UMjd+UKmCNBcr9gdz6OnXh9kmW5GDMCy7ArVdYY0yjSxQ== invariant@^2.2.4: version "2.2.4" @@ -3063,15 +3075,22 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" 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: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.11.0, is-core-module@^2.13.0, is-core-module@^2.13.1: - version "2.15.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" - integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== +is-core-module@^2.13.0, is-core-module@^2.15.1: + version "2.15.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.1.tgz#a7363a25bee942fefab0de13bf6aa372c82dcc37" + integrity sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ== dependencies: hasown "^2.0.2" @@ -3387,16 +3406,16 @@ levn@^0.4.1: type-check "~0.4.0" libphonenumber-js@^1.11.4: - version "1.11.4" - resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.4.tgz#e63fe553f45661b30bb10bb8c82c9cf2b22ec32a" - integrity sha512-F/R50HQuWWYcmU/esP5jrH5LiWYaN7DpN0a/99U8+mnGGtnx8kmRE+649dQh3v+CowXXZc8vpkf5AmYkO0AQ7Q== + version "1.11.7" + resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.11.7.tgz#efe4fcf816e1982925e9c800d0013b0ee99b8283" + integrity sha512-x2xON4/Qg2bRIS11KIN9yCNYUjhtiEjNyptjX0mX+pyKHecxuJVLIpfX1lq9ZD6CrC/rB+y4GBi18c6CEcUR+A== lilconfig@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.1.0.tgz#78e23ac89ebb7e1bfbf25b18043de756548e7f52" 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" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-3.1.2.tgz#e4a7c3cb549e3a606c8dcc32e5ae1005e62c05cb" integrity sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow== @@ -3414,30 +3433,30 @@ linkify-it@^5.0.0: uc.micro "^2.0.0" lint-staged@^15.2.7: - version "15.2.7" - resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.7.tgz#97867e29ed632820c0fb90be06cd9ed384025649" - integrity sha512-+FdVbbCZ+yoh7E/RosSdqKJyUM2OEjTciH0TFNkawKgvFp1zbGlEC39RADg+xKBG1R4mhoH2j85myBQZ5wR+lw== + version "15.2.10" + resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-15.2.10.tgz#92ac222f802ba911897dcf23671da5bb80643cd2" + integrity sha512-5dY5t743e1byO19P9I4b3x8HJwalIznL5E1FWYnU6OWw33KxNBSLAc6Cy7F2PsFEO8FKnLwjwm5hx7aMF0jzZg== dependencies: chalk "~5.3.0" commander "~12.1.0" - debug "~4.3.4" + debug "~4.3.6" execa "~8.0.1" - lilconfig "~3.1.1" - listr2 "~8.2.1" - micromatch "~4.0.7" + lilconfig "~3.1.2" + listr2 "~8.2.4" + micromatch "~4.0.8" pidtree "~0.6.0" string-argv "~0.3.2" - yaml "~2.4.2" + yaml "~2.5.0" -listr2@~8.2.1: - version "8.2.3" - resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.2.3.tgz#c494bb89b34329cf900e4e0ae8aeef9081d7d7a5" - integrity sha512-Lllokma2mtoniUOS94CcOErHWAug5iu7HOmDrvWgpw8jyQH2fomgB+7lZS4HWZxytUuQwkGOwe49FvwVaA85Xw== +listr2@~8.2.4: + version "8.2.4" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-8.2.4.tgz#486b51cbdb41889108cb7e2c90eeb44519f5a77f" + integrity sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g== dependencies: cli-truncate "^4.0.0" colorette "^2.0.20" eventemitter3 "^5.0.1" - log-update "^6.0.0" + log-update "^6.1.0" rfdc "^1.4.1" wrap-ansi "^9.0.0" @@ -3481,7 +3500,7 @@ log-symbols@^5.1.0: chalk "^5.0.0" is-unicode-supported "^1.1.0" -log-update@^6.0.0: +log-update@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.1.0.tgz#1a04ff38166f94647ae1af562f4bd6a15b1b7cd4" 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" integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== -micromatch@^4.0.4, micromatch@^4.0.5, micromatch@~4.0.7: - version "4.0.7" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.7.tgz#33e8190d9fe474a9895525f5618eee136d46c2e5" - integrity sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q== +micromatch@^4.0.4, micromatch@^4.0.5, micromatch@~4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: braces "^3.0.3" picomatch "^2.3.1" @@ -3616,12 +3635,7 @@ mkdirp@^2.1.6: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-2.1.6.tgz#964fbcb12b2d8c5d6fbc62a963ac95a273e2cc19" integrity sha512-+hEnITedc8LAtIP9u3HJDFIdcLV2vXP33sqLLIzkv1Db1zO/1OxbvYf0Y1OC/S/Qo5dxHXepofhmxL02PsKe+A== -ms@2.1.2: - 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: +ms@^2.1.1, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -3682,7 +3696,7 @@ node-fetch@^3.3.0: fetch-blob "^3.1.4" formdata-polyfill "^4.0.10" -node-releases@^2.0.14: +node-releases@^2.0.18: version "2.0.18" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== @@ -3767,7 +3781,7 @@ object.entries@^1.1.8: define-properties "^1.2.1" 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" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== @@ -3777,7 +3791,7 @@ object.fromentries@^2.0.7, object.fromentries@^2.0.8: es-abstract "^1.23.2" es-object-atoms "^1.0.0" -object.groupby@^1.0.1: +object.groupby@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== @@ -3786,7 +3800,7 @@ object.groupby@^1.0.1: define-properties "^1.2.1" 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" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== @@ -3930,9 +3944,9 @@ path-type@^4.0.0: integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== picocolors@^1.0.0, picocolors@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" - integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + version "1.1.0" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.1.0.tgz#5358b76a78cde483ba5cef6a9dc9671440b27d59" + integrity sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw== picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^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.0.11, postcss-selector-parser@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.1.tgz#5be94b277b8955904476a2400260002ce6c56e38" - integrity sha512-b4dlw/9V8A71rLIDsSwVmak9z2DuBUB7CA1/wSdelNEzqsjoSPeADTWNO09lpH49Diy3/JIZ2bSPB1dI3LJCHg== + version "6.1.2" + resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz#27ecb41fb0e3b6ba7a1ec84fff347f734c7929de" + integrity sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg== dependencies: cssesc "^3.0.0" util-deprecate "^1.0.2" @@ -4013,9 +4027,9 @@ postcss@8.4.31: source-map-js "^1.0.2" postcss@^8.4.23, postcss@^8.4.38: - version "8.4.40" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.40.tgz#eb81f2a4dd7668ed869a6db25999e02e9ad909d8" - integrity sha512-YF2kKIUzAofPMpfH6hOi2cGnv/HrUlfucspc7pDyvv7kGdqXrfj8SCl/t8owkEgKEuu8ZcRjSOxFxVLqwChZ2Q== + version "8.4.45" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.45.tgz#538d13d89a16ef71edbf75d895284ae06b79e603" + integrity sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q== dependencies: nanoid "^3.3.7" picocolors "^1.0.1" @@ -4084,9 +4098,9 @@ react-dom@^18: scheduler "^0.23.2" react-hook-form@^7.52.1: - version "7.52.1" - resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.52.1.tgz#ec2c96437b977f8b89ae2d541a70736c66284852" - integrity sha512-uNKIhaoICJ5KQALYZ4TOaOLElyM+xipord+Ha3crEFhTntdLvWZqVY49Wqd/0GiVCA/f9NjemLeiNPjG7Hpurg== + version "7.53.0" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.53.0.tgz#3cf70951bf41fa95207b34486203ebefbd3a05ab" + integrity sha512-M1n3HhqCww6S2hxLxciEXy2oISPnAzxY7gvwVPrtlczTM/1dDadXgUxDpHMrMTblDOcm/AXtXxHwZ3jpg1mqKQ== react-is@^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" 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" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== @@ -4647,9 +4661,9 @@ synckit@^0.9.1: tslib "^2.6.2" tailwind-merge@^2.3.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.4.0.tgz#1345209dc1f484f15159c9180610130587703042" - integrity sha512-49AwoOQNKdqKPd9CViyH5wJoSKsCDjUlzL8DxuGp3P1FsGY36NJDAa18jLZcaHAUUuTj+JB8IAo8zWgBNvBF7A== + version "2.5.2" + resolved "https://registry.yarnpkg.com/tailwind-merge/-/tailwind-merge-2.5.2.tgz#000f05a703058f9f9f3829c644235f81d4c08a1f" + integrity sha512-kjEBm+pvD+6eAwzJL2Bi+02/9LFLal1Gs61+QB7HvTfQQ0aXwC5LGT8PEt1gS0CWKktKe6ysPTAy3cBC5MeiIg== tailwindcss-animate@^1.0.7: version "1.0.7" @@ -4657,9 +4671,9 @@ tailwindcss-animate@^1.0.7: integrity sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA== tailwindcss@^3.4.6: - version "3.4.7" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.7.tgz#6092f18767f5933f59375b9afe558e592fc77201" - integrity sha512-rxWZbe87YJb4OcSopb7up2Ba4U82BoiSGUdoDr3Ydrg9ckxFS/YWsvhN323GMcddgU65QRy7JndC7ahhInhvlQ== + version "3.4.10" + resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.4.10.tgz#70442d9aeb78758d1f911af29af8255ecdb8ffef" + integrity sha512-KWZkVPm7yJRhdu4SRSl9d4AK2wM3a50UsvgHZO7xY77NQr2V+fIrEuoDGQcbvswWvFGbS2f6e+jC/6WJm1Dl0w== dependencies: "@alloc/quick-lru" "^5.2.0" arg "^5.0.2" @@ -4768,9 +4782,9 @@ tsconfig-paths@^4.2.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: - version "2.6.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" - integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== + version "2.7.0" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.7.0.tgz#d9b40c5c40ab59e8738f297df3087bf1a2690c01" + integrity sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA== type-check@^0.4.0, type-check@~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" integrity sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA== -undici-types@~5.26.4: - version "5.26.5" - resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" - integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== universalify@^2.0.0: version "2.0.1" @@ -4904,9 +4918,9 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2: integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== vaul@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/vaul/-/vaul-0.9.1.tgz#3640198e04636b209b1f907fcf3079bec6ecc66b" - integrity sha512-fAhd7i4RNMinx+WEm6pF3nOl78DFkAazcN04ElLPFF9BMCNGbY/kou8UMhIcicm0rJCNePJP0Yyza60gGOD0Jw== + version "0.9.2" + resolved "https://registry.yarnpkg.com/vaul/-/vaul-0.9.2.tgz#fe7ad8a0acf0863b9bc7e956da27a8ce6169ab7c" + integrity sha512-m2A7UgAU/JMWiwUhmARK8LMvEfXiudA4trJxfZF5AtH2uBTgN855msZ2yjPnUDfa7i5glocMYLSfML8wriBtBA== dependencies: "@radix-ui/react-dialog" "^1.0.4" @@ -4934,12 +4948,12 @@ which-boxed-primitive@^1.0.2: is-symbol "^1.0.3" which-builtin-type@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.3.tgz#b1b8443707cc58b6e9bf98d32110ff0c2cbd029b" - integrity sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw== + version "1.1.4" + resolved "https://registry.yarnpkg.com/which-builtin-type/-/which-builtin-type-1.1.4.tgz#592796260602fc3514a1b5ee7fa29319b72380c3" + integrity sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w== dependencies: - function.prototype.name "^1.1.5" - has-tostringtag "^1.0.0" + function.prototype.name "^1.1.6" + has-tostringtag "^1.0.2" is-async-function "^2.0.0" is-date-object "^1.0.5" is-finalizationregistry "^1.0.2" @@ -4948,10 +4962,10 @@ which-builtin-type@^1.1.3: is-weakref "^1.0.2" isarray "^2.0.5" which-boxed-primitive "^1.0.2" - which-collection "^1.0.1" - which-typed-array "^1.1.9" + which-collection "^1.0.2" + 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" resolved "https://registry.yarnpkg.com/which-collection/-/which-collection-1.0.2.tgz#627ef76243920a107e7ce8e96191debe4b16c2a0" integrity sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw== @@ -4961,7 +4975,7 @@ which-collection@^1.0.1: is-weakmap "^2.0.2" 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" resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.15.tgz#264859e9b11a649b388bfaaf4f767df1f779b38d" 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" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== -yaml@^2.3.4: - version "2.5.0" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.0.tgz#c6165a721cf8000e91c36490a41d7be25176cf5d" - integrity sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw== - -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== +yaml@^2.3.4, yaml@~2.5.0: + version "2.5.1" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.5.1.tgz#c9772aacf62cb7494a95b0c4f1fb065b563db130" + integrity sha512-bLQOjaX/ADgQ20isPJRvF0iRUHIxVhYvr53Of7wGcWlO2jvtUlH5m87DsmulFVxRpNLOnI4tB6p/oh8D7kpn9Q== yocto-queue@^0.1.0: version "0.1.0" diff --git a/src/khoj/database/adapters/__init__.py b/src/khoj/database/adapters/__init__.py index f21f8aef..df693a70 100644 --- a/src/khoj/database/adapters/__init__.py +++ b/src/khoj/database/adapters/__init__.py @@ -757,6 +757,18 @@ class ConversationAdapters: def has_any_conversation_config(user: KhojUser): 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 def get_openai_conversation_config(): return OpenAIProcessorConversationConfig.objects.filter().first() diff --git a/src/khoj/database/migrations/0056_chatmodeloptions_vision_enabled.py b/src/khoj/database/migrations/0056_chatmodeloptions_vision_enabled.py new file mode 100644 index 00000000..dc914169 --- /dev/null +++ b/src/khoj/database/migrations/0056_chatmodeloptions_vision_enabled.py @@ -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), + ), + ] diff --git a/src/khoj/database/migrations/0057_merge_20240816_1409.py b/src/khoj/database/migrations/0057_merge_20240816_1409.py new file mode 100644 index 00000000..91c981da --- /dev/null +++ b/src/khoj/database/migrations/0057_merge_20240816_1409.py @@ -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] = [] diff --git a/src/khoj/database/migrations/0060_merge_20240905_1828.py b/src/khoj/database/migrations/0060_merge_20240905_1828.py new file mode 100644 index 00000000..00ce0dd7 --- /dev/null +++ b/src/khoj/database/migrations/0060_merge_20240905_1828.py @@ -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] = [] diff --git a/src/khoj/database/models/__init__.py b/src/khoj/database/models/__init__.py index 99e5191a..5b995194 100644 --- a/src/khoj/database/models/__init__.py +++ b/src/khoj/database/models/__init__.py @@ -93,6 +93,7 @@ class ChatModelOptions(BaseModel): 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") model_type = models.CharField(max_length=200, choices=ModelType.choices, default=ModelType.OFFLINE) + vision_enabled = models.BooleanField(default=False) openai_config = models.ForeignKey( OpenAIProcessorConversationConfig, on_delete=models.CASCADE, default=None, null=True, blank=True ) diff --git a/src/khoj/processor/conversation/openai/gpt.py b/src/khoj/processor/conversation/openai/gpt.py index d6e2a281..f2e87884 100644 --- a/src/khoj/processor/conversation/openai/gpt.py +++ b/src/khoj/processor/conversation/openai/gpt.py @@ -123,6 +123,8 @@ def converse( location_data: LocationData = None, user_name: str = None, agent: Agent = None, + image_url: Optional[str] = None, + vision_available: bool = False, ): """ Converse with user using OpenAI's ChatGPT @@ -178,6 +180,8 @@ def converse( model_name=model, max_prompt_size=max_prompt_size, 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}) logger.debug(f"Conversation Context for GPT: {truncated_messages}") diff --git a/src/khoj/processor/conversation/utils.py b/src/khoj/processor/conversation/utils.py index 976e0d0e..8b4e34e6 100644 --- a/src/khoj/processor/conversation/utils.py +++ b/src/khoj/processor/conversation/utils.py @@ -101,12 +101,16 @@ def save_to_conversation_log( client_application: ClientApplication = None, conversation_id: int = 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") updated_conversation = message_to_log( user_message=q, 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={ "context": compiled_references, "intent": {"inferred-queries": inferred_queries, "type": intent_type}, @@ -141,6 +145,8 @@ def generate_chatml_messages_with_context( loaded_model: Optional[Llama] = None, max_prompt_size=None, tokenizer_name=None, + uploaded_image_url=None, + vision_enabled=False, ): """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 @@ -150,28 +156,40 @@ def generate_chatml_messages_with_context( else: 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 lookback_turns = max_prompt_size // 750 # Extract Chat History for Context - chat_logs = [] + chatml_messages: List[ChatMessage] = [] for chat in conversation_log.get("chat", []): - chat_notes = f'\n\n Notes:\n{chat.get("context")}' if chat.get("context") else "\n" - chat_logs += [chat["message"] + chat_notes] + message_notes = f'\n\n Notes:\n{chat.get("context")}' if chat.get("context") else "\n" + role = "user" if chat["by"] == "you" else "assistant" - rest_backnforths: List[ChatMessage] = [] - # Extract in reverse chronological order - for user_msg, assistant_msg in zip(chat_logs[-2::-2], chat_logs[::-2]): - if len(rest_backnforths) >= 2 * lookback_turns: + message_content = chat["message"] + message_notes + + if chat.get("uploadedImageData") and vision_enabled: + 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 - rest_backnforths += reciprocal_conversation_to_chatml([user_msg, assistant_msg])[::-1] - # Format user and system messages to chatml format messages = [] if not is_none_or_empty(user_message): - messages.append(ChatMessage(content=user_message, role="user")) - if len(rest_backnforths) > 0: - messages += rest_backnforths + messages.append( + ChatMessage(content=construct_structured_message(user_message, uploaded_image_url), role="user") + ) + if len(chatml_messages) > 0: + messages += chatml_messages if not is_none_or_empty(system_message): messages.append(ChatMessage(content=system_message, role="system")) diff --git a/src/khoj/processor/tools/online_search.py b/src/khoj/processor/tools/online_search.py index 8678e2bb..e0f97cd0 100644 --- a/src/khoj/processor/tools/online_search.py +++ b/src/khoj/processor/tools/online_search.py @@ -56,6 +56,7 @@ async def search_online( subscribed: bool = False, send_status_func: Optional[Callable] = None, custom_filters: List[str] = [], + uploaded_image_url: str = None, ): query += " ".join(custom_filters) if not is_internet_connected(): @@ -64,7 +65,9 @@ async def search_online( return # 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 = {} if subqueries: @@ -138,13 +141,14 @@ async def read_webpages( user: KhojUser, subscribed: bool = False, 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" logger.info(f"Inferring web pages to read") if send_status_func: async for event in send_status_func(f"**Inferring web pages to read**"): 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}") if send_status_func: diff --git a/src/khoj/routers/api_chat.py b/src/khoj/routers/api_chat.py index 7fb48347..38cf5d3d 100644 --- a/src/khoj/routers/api_chat.py +++ b/src/khoj/routers/api_chat.py @@ -1,4 +1,5 @@ import asyncio +import base64 import json import logging import time @@ -46,11 +47,13 @@ from khoj.routers.helpers import ( update_telemetry_state, validate_conversation_config, ) +from khoj.routers.storage import upload_image_to_bucket from khoj.utils import state from khoj.utils.helpers import ( AsyncIteratorWrapper, ConversationCommand, command_descriptions, + convert_image_to_webp, get_device, 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"]) async def chat( request: Request, @@ -532,6 +539,7 @@ async def chat( region: Optional[str] = None, country: Optional[str] = None, timezone: Optional[str] = None, + image: Optional[ImageUploadObject] = None, rate_limiter_per_minute=Depends( 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") ), ): - async def event_generator(q: str): + async def event_generator(q: str, image: ImageUploadObject): start_time = time.perf_counter() ttft = None chat_metadata: dict = {} @@ -550,6 +558,17 @@ async def chat( q = unquote(q) 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): nonlocal connection_alive, ttft 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: 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]) async for result in send_event( @@ -645,7 +664,7 @@ async def chat( ): 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}"): yield result if mode not in conversation_commands: @@ -693,7 +712,9 @@ async def chat( ): 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) async for result in send_llm_response(response_log): yield result @@ -711,6 +732,7 @@ async def chat( intent_type="summarize", client_application=request.user.client_app, conversation_id=conversation_id, + uploaded_image_url=uploaded_image_url, ) return @@ -753,6 +775,7 @@ async def chat( conversation_id=conversation_id, inferred_queries=[query_to_run], automation_id=automation.id, + uploaded_image_url=uploaded_image_url, ) async for result in send_llm_response(llm_response): yield result @@ -807,6 +830,7 @@ async def chat( subscribed, partial(send_event, ChatEvent.STATUS), custom_filters, + uploaded_image_url=uploaded_image_url, ): if isinstance(result, dict) and ChatEvent.STATUS in result: yield result[ChatEvent.STATUS] @@ -823,7 +847,13 @@ async def chat( if ConversationCommand.Webpage in conversation_commands: try: 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: yield result[ChatEvent.STATUS] @@ -869,6 +899,7 @@ async def chat( online_results=online_results, subscribed=subscribed, send_status_func=partial(send_event, ChatEvent.STATUS), + uploaded_image_url=uploaded_image_url, ): if isinstance(result, dict) and ChatEvent.STATUS in result: yield result[ChatEvent.STATUS] @@ -898,6 +929,7 @@ async def chat( conversation_id=conversation_id, compiled_references=compiled_references, online_results=online_results, + uploaded_image_url=uploaded_image_url, ) content_obj = { "intentType": intent_type, @@ -924,6 +956,7 @@ async def chat( conversation_id, location, user_name, + uploaded_image_url, ) # Send Response @@ -949,9 +982,9 @@ async def chat( ## Stream Text Response 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 else: - response_iterator = event_generator(q) + response_iterator = event_generator(q, image=image) response_data = await read_chat_stream(response_iterator) return Response(content=json.dumps(response_data), media_type="application/json", status_code=200) diff --git a/src/khoj/routers/helpers.py b/src/khoj/routers/helpers.py index 638dbb0e..da688ddb 100644 --- a/src/khoj/routers/helpers.py +++ b/src/khoj/routers/helpers.py @@ -97,6 +97,7 @@ from khoj.utils.helpers import ( LRU, ConversationCommand, ImageIntentType, + convert_image_to_webp, is_none_or_empty, is_valid_url, log_telemetry, @@ -252,7 +253,9 @@ async def acreate_title_from_query(query: str) -> str: 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. """ @@ -266,6 +269,9 @@ async def aget_relevant_information_sources(query: str, conversation_history: di 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( query=query, 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): 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: @@ -302,7 +310,9 @@ async def aget_relevant_information_sources(query: str, conversation_history: di 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. """ @@ -319,6 +329,9 @@ async def aget_relevant_output_modes(query: str, conversation_history: dict, is_ 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( query=query, 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( - 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]: """ 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): - 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 try: @@ -381,7 +396,7 @@ async def infer_webpage_urls( 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]: """ Generate subqueries from the given query @@ -400,7 +415,9 @@ async def generate_online_subqueries( ) 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 try: @@ -419,7 +436,7 @@ async def generate_online_subqueries( 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. """ @@ -430,7 +447,9 @@ async def schedule_query(q: str, conversation_history: dict) -> Tuple[str, ...]: 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 try: @@ -468,7 +487,9 @@ async def extract_relevant_info(q: str, corpus: str, subscribed: bool) -> Union[ 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 """ @@ -489,6 +510,7 @@ async def extract_relevant_summary(q: str, corpus: str, subscribed: bool = False prompts.system_prompt_extract_relevant_summary, chat_model_option=chat_model, subscribed=subscribed, + uploaded_image_url=uploaded_image_url, ) return response.strip() @@ -501,6 +523,7 @@ async def generate_better_image_prompt( online_results: Optional[dict] = None, model_type: Optional[str] = None, subscribed: bool = False, + uploaded_image_url: Optional[str] = None, ) -> str: """ 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): 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() if response.startswith(('"', "'")) and response.endswith(('"', "'")): @@ -564,11 +587,19 @@ async def send_message_to_model_wrapper( response_type: str = "text", chat_model_option: ChatModelOptions = None, subscribed: bool = False, + uploaded_image_url: str = None, ): conversation_config: ChatModelOptions = ( 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 max_tokens = ( conversation_config.subscribed_max_prompt_size @@ -576,6 +607,7 @@ async def send_message_to_model_wrapper( else conversation_config.max_prompt_size ) tokenizer = conversation_config.tokenizer + vision_available = conversation_config.vision_enabled if conversation_config.model_type == "offline": 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, tokenizer_name=tokenizer, max_prompt_size=max_tokens, + vision_enabled=vision_available, ) return send_message_to_model_offline( @@ -609,6 +642,8 @@ async def send_message_to_model_wrapper( model_name=chat_model, max_prompt_size=max_tokens, tokenizer_name=tokenizer, + vision_enabled=vision_available, + uploaded_image_url=uploaded_image_url, ) openai_response = send_message_to_model( @@ -628,6 +663,7 @@ async def send_message_to_model_wrapper( model_name=chat_model, max_prompt_size=max_tokens, tokenizer_name=tokenizer, + vision_enabled=vision_available, ) return anthropic_send_message_to_model( @@ -651,6 +687,7 @@ def send_message_to_model_wrapper_sync( chat_model = conversation_config.chat_model max_tokens = conversation_config.max_prompt_size + vision_available = conversation_config.vision_enabled if conversation_config.model_type == "offline": 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 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( @@ -672,7 +713,10 @@ def send_message_to_model_wrapper_sync( elif conversation_config.model_type == "openai": api_key = conversation_config.openai_config.api_key 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( @@ -688,6 +732,7 @@ def send_message_to_model_wrapper_sync( system_message=system_message, model_name=chat_model, max_prompt_size=max_tokens, + vision_enabled=vision_available, ) return anthropic_send_message_to_model( @@ -712,6 +757,7 @@ def generate_chat_response( conversation_id: int = None, location_data: LocationData = None, user_name: Optional[str] = None, + uploaded_image_url: Optional[str] = None, ) -> Tuple[Union[ThreadedGenerator, Iterator[str]], Dict[str, str]]: # Initialize Variables chat_response = None @@ -719,7 +765,6 @@ def generate_chat_response( metadata = {} agent = AgentAdapters.get_conversation_agent_by_id(conversation.agent.id) if conversation.agent else None - try: partial_completion = partial( save_to_conversation_log, @@ -731,9 +776,17 @@ def generate_chat_response( inferred_queries=inferred_queries, client_application=client_application, conversation_id=conversation_id, + uploaded_image_url=uploaded_image_url, ) 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": loaded_model = state.offline_chat_processor_config.loaded_model chat_response = converse_offline( @@ -759,6 +812,7 @@ def generate_chat_response( chat_response = converse( compiled_references, q, + image_url=uploaded_image_url, online_results=online_results, conversation_log=meta_log, model=chat_model, @@ -771,6 +825,7 @@ def generate_chat_response( location_data=location_data, user_name=user_name, agent=agent, + vision_available=vision_available, ) elif conversation_config.model_type == "anthropic": @@ -809,6 +864,7 @@ async def text_to_image( online_results: Dict[str, Any], subscribed: bool = False, send_status_func: Optional[Callable] = None, + uploaded_image_url: Optional[str] = None, ): status_code = 200 image = None @@ -845,6 +901,7 @@ async def text_to_image( online_results=online_results, model_type=text_to_image_config.model_type, subscribed=subscribed, + uploaded_image_url=uploaded_image_url, ) if send_status_func: @@ -908,13 +965,7 @@ async def text_to_image( with timer("Convert image to webp", logger): # Convert png to webp for faster loading - image_io = io.BytesIO(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() + webp_image_bytes = convert_image_to_webp(decoded_image) with timer("Upload image to S3", logger): 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): 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) 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.') diff --git a/src/khoj/routers/storage.py b/src/khoj/routers/storage.py index 8d7b08e5..e0b9b91a 100644 --- a/src/khoj/routers/storage.py +++ b/src/khoj/routers/storage.py @@ -33,3 +33,31 @@ def upload_image(image: bytes, user_id: uuid.UUID): except Exception as e: logger.error(f"Failed to upload image to S3: {e}") 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 diff --git a/src/khoj/utils/helpers.py b/src/khoj/utils/helpers.py index 9775e7ce..1ebb1fdd 100644 --- a/src/khoj/utils/helpers.py +++ b/src/khoj/utils/helpers.py @@ -1,6 +1,7 @@ from __future__ import annotations # to avoid quoting type hints import datetime +import io import logging import os import platform @@ -22,6 +23,7 @@ import requests import torch from asgiref.sync import sync_to_async from magika import Magika +from PIL import Image from khoj.utils import constants @@ -416,3 +418,16 @@ def is_internet_connected(): return response.status_code == 200 except: 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 diff --git a/tests/test_client.py b/tests/test_client.py index 0f09a0c5..d5bc2ba1 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -462,8 +462,8 @@ async def test_chat_with_unauthenticated_user(chat_client_with_auth, api_user2: headers = {"Authorization": f"Bearer {api_user2.token}"} # Act - auth_response = chat_client_with_auth.get(f'/api/chat?q="Hello!"', headers=headers) - no_auth_response = chat_client_with_auth.get(f'/api/chat?q="Hello!"') + auth_response = chat_client_with_auth.post(f'/api/chat?q="Hello!"', headers=headers) + no_auth_response = chat_client_with_auth.post(f'/api/chat?q="Hello!"') # Assert assert auth_response.status_code == 200 diff --git a/tests/test_offline_chat_director.py b/tests/test_offline_chat_director.py index c0532c4b..82f53b69 100644 --- a/tests/test_offline_chat_director.py +++ b/tests/test_offline_chat_director.py @@ -49,7 +49,7 @@ def create_conversation(message_list, user, agent=None): @pytest.mark.django_db(transaction=True) def test_offline_chat_with_no_chat_history_or_retrieved_content(client_offline_chat): # 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") # Assert @@ -67,7 +67,7 @@ def test_chat_with_online_content(client_offline_chat): # Act q = "/online give me the link to paul graham's essay how to do great work" 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"] # Assert @@ -89,7 +89,7 @@ def test_chat_with_online_webpage_content(client_offline_chat): # Act q = "/online how many firefighters were involved in the great chicago fire and which year did it take place?" 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"] # Assert @@ -112,7 +112,7 @@ def test_answer_from_chat_history(client_offline_chat, default_user2): create_conversation(message_list, default_user2) # 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") # Assert @@ -139,7 +139,7 @@ def test_answer_from_currently_retrieved_content(client_offline_chat, default_us create_conversation(message_list, default_user2) # 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") # Assert @@ -163,7 +163,7 @@ def test_answer_from_chat_history_and_previously_retrieved_content(client_offlin create_conversation(message_list, default_user2) # 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") # Assert @@ -185,7 +185,7 @@ def test_answer_from_chat_history_and_currently_retrieved_content(client_offline create_conversation(message_list, default_user2) # 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") # 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) # 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") # Assert @@ -232,7 +232,7 @@ def test_answer_using_general_command(client_offline_chat, default_user2): create_conversation(message_list, default_user2) # 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") # Assert @@ -250,7 +250,7 @@ def test_answer_from_retrieved_content_using_notes_command(client_offline_chat, create_conversation(message_list, default_user2) # 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") # Assert @@ -269,8 +269,8 @@ def test_answer_using_file_filter(client_offline_chat, default_user2): create_conversation(message_list, default_user2) # Act - no_answer_response = client_offline_chat.get(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") + 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.post(f"/api/chat?q={answer_query}&stream=true").content.decode("utf-8") # Assert 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) # 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") # 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)}, ) 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") # Assert 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)}, ) 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") # Assert assert response_message != "" @@ -380,7 +380,7 @@ def test_summarize_multiple_files(client_offline_chat, default_user2: KhojUser): ) 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") # Assert @@ -393,7 +393,7 @@ def test_summarize_no_files(client_offline_chat, default_user2: KhojUser): message_list = [] conversation = create_conversation(message_list, default_user2) 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") # Assert 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") - 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") # Assert 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 - 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") # 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)}, ) 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") # Assert 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)}, ) 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") # Assert 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?") # 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") # 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): "Chat director should be able to answer questions that require date aware aggregation across multiple notes" # 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") # Assert @@ -559,7 +559,7 @@ def test_answer_general_question_not_in_chat_history_or_retrieved_content(client @pytest.mark.django_db(transaction=True) def test_ask_for_clarification_if_not_enough_context_in_question(client_offline_chat, default_user2): # 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") # Assert @@ -589,7 +589,7 @@ def test_answer_in_chat_history_beyond_lookback_window(client_offline_chat, defa create_conversation(message_list, default_user2) # 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") # Assert @@ -653,7 +653,7 @@ def test_answer_in_chat_history_by_conversation_id_with_agent( # Act 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") # 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) # 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") # 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): "Chat director should be able to answer by doing multiple independent searches for required information" # 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") # Assert diff --git a/tests/test_openai_chat_director.py b/tests/test_openai_chat_director.py index f0998f5d..371ff5a4 100644 --- a/tests/test_openai_chat_director.py +++ b/tests/test_openai_chat_director.py @@ -49,7 +49,7 @@ def create_conversation(message_list, user, agent=None): @pytest.mark.django_db(transaction=True) def test_chat_with_no_chat_history_or_retrieved_content(chat_client): # 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"] # Assert @@ -67,7 +67,7 @@ def test_chat_with_online_content(chat_client): # Act q = "/online give me the link to paul graham's essay how to do great work" 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"] # Assert @@ -88,7 +88,7 @@ def test_chat_with_online_webpage_content(chat_client): # Act q = "/online how many firefighters were involved in the great chicago fire and which year did it take place?" 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"] # Assert @@ -111,7 +111,7 @@ def test_answer_from_chat_history(chat_client, default_user2: KhojUser): create_conversation(message_list, default_user2) # 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") # Assert @@ -138,7 +138,7 @@ def test_answer_from_currently_retrieved_content(chat_client, default_user2: Kho create_conversation(message_list, default_user2) # 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"] # Assert @@ -162,7 +162,7 @@ def test_answer_from_chat_history_and_previously_retrieved_content(chat_client_n create_conversation(message_list, default_user2) # 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"] # Assert @@ -185,7 +185,7 @@ def test_answer_from_chat_history_and_currently_retrieved_content(chat_client, d create_conversation(message_list, default_user2) # 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"] # 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) # 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"] # Assert @@ -240,7 +240,7 @@ def test_answer_using_general_command(chat_client, default_user2: KhojUser): create_conversation(message_list, default_user2) # 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") # Assert @@ -258,7 +258,7 @@ def test_answer_from_retrieved_content_using_notes_command(chat_client, default_ create_conversation(message_list, default_user2) # Act - response = chat_client.get(f"/api/chat?q={query}") + response = chat_client.post(f"/api/chat?q={query}") response_message = response.json()["response"] # Assert @@ -276,7 +276,7 @@ def test_answer_not_known_using_notes_command(chat_client_no_background, default create_conversation(message_list, default_user2) # 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"] # Assert @@ -309,7 +309,7 @@ def test_summarize_one_file(chat_client, default_user2: KhojUser): json={"filename": summarization_file, "conversation_id": str(conversation.id)}, ) 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"] # Assert 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)}, ) 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"] # Assert assert response_message != "" @@ -369,7 +369,7 @@ def test_summarize_multiple_files(chat_client, default_user2: KhojUser): ) 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"] # Assert @@ -382,7 +382,7 @@ def test_summarize_no_files(chat_client, default_user2: KhojUser): message_list = [] conversation = create_conversation(message_list, default_user2) 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"] # Assert 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") - 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"] # Assert 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 - 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"] # Assert @@ -441,7 +441,7 @@ def test_summarize_nonexistant_file(chat_client, default_user2: KhojUser): json={"filename": "imaginary.markdown", "conversation_id": str(conversation.id)}, ) 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"] # Assert 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)}, ) 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"] # Assert 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): "Chat actor should be able to answer questions relative to current date using provided notes" # 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") # 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): "Chat director should be able to answer questions that require date aware aggregation across multiple notes" # 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"] # 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) # 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"] # Assert @@ -540,7 +540,7 @@ def test_answer_general_question_not_in_chat_history_or_retrieved_content(chat_c @pytest.mark.chatquality def test_ask_for_clarification_if_not_enough_context_in_question(chat_client_no_background): # 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() # Assert @@ -574,7 +574,7 @@ def test_answer_in_chat_history_beyond_lookback_window(chat_client, default_user create_conversation(message_list, default_user2) # 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"] # Assert @@ -607,7 +607,7 @@ def test_answer_in_chat_history_by_conversation_id(chat_client, default_user2: K # Act 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") # Assert @@ -640,7 +640,7 @@ def test_answer_in_chat_history_by_conversation_id_with_agent( # Act 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"] # 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): "Chat director should be able to answer by doing multiple independent searches for required information" # 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() # 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"' ) - response = chat_client.get(f"/api/chat?q={query}") + response = chat_client.post(f"/api/chat?q={query}") response_message = response.json()["response"].lower() # Assert