Add Vision Support (#889)

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

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

---------

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

View file

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

View file

@ -407,7 +407,7 @@
? `&region=${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);

View file

@ -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)))

View file

@ -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}`,
},
})

View file

@ -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';"

View file

@ -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<string | null>(null);
const [processingMessage, setProcessingMessage] = useState(false);
const [agentMetadata, setAgentMetadata] = useState<AgentData | null>(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<string>("");
const [processQuerySignal, setProcessQuerySignal] = useState(false);
const [uploadedFiles, setUploadedFiles] = useState<string[]>([]);
const [image64, setImage64] = useState<string>("");
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 += `&region=${locationData.region}&country=${locationData.country}&city=${locationData.city}&timezone=${locationData.timezone}`;
}
const response = await fetch(chatAPI);
const response = await fetch(chatAPI, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: image64 ? JSON.stringify({ image: image64 }) : undefined,
});
try {
await readChatStream(response);
} catch (err) {
@ -282,6 +308,7 @@ export default function Chat() {
setUploadedFiles={setUploadedFiles}
isMobileWidth={isMobileWidth}
onConversationIdChange={handleConversationIdChange}
setImage64={setImage64}
/>
</Suspense>
</div>

View file

@ -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`}

View file

@ -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<string | null>(null);
const [imageData, setImageData] = useState<string | null>(null);
const [mediaRecorder, setMediaRecorder] = useState<MediaRecorder | null>(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) {
</div>
)}
<div
className={`${styles.actualInputArea} items-center justify-between dark:bg-neutral-700`}
className={`${styles.actualInputArea} items-center justify-between dark:bg-neutral-700 relative`}
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDragAndDropFiles}
>
{imageUploaded && (
<div className="absolute bottom-[80px] left-0 right-0 dark:bg-neutral-700 bg-white pt-5 pb-5 w-full rounded-lg border dark:border-none grid grid-cols-2">
<div className="pl-4 pr-4">
<img src={imagePath || ""} alt="img" className="w-auto max-h-[100px]" />
</div>
<div className="pl-4 pr-4">
<X
className="w-6 h-6 float-right dark:hover:bg-[hsl(var(--background))] hover:bg-neutral-100 rounded-sm"
onClick={removeImageUpload}
/>
</div>
</div>
)}
<input
type="file"
multiple={true}
@ -427,6 +484,8 @@ export default function ChatInputArea(props: ChatInputProps) {
value={message}
onKeyDown={(e) => {
if (e.key === "Enter" && !e.shiftKey) {
setImageUploaded(false);
setImagePath(null);
e.preventDefault();
onSendMessage();
}

View file

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

View file

@ -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<boolean>(false);
const [isHovering, setIsHovering] = useState<boolean>(false);
const [textRendered, setTextRendered] = useState<string>("");
const [markdownRendered, setMarkdownRendered] = useState<string>("");
const [isPlaying, setIsPlaying] = useState<boolean>(false);
const [interrupted, setInterrupted] = useState<boolean>(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}
>
<div className={chatMessageWrapperClasses(props.chatMessage)}>
<div
@ -595,7 +605,7 @@ export default function ChatMessage(props: ChatMessageProps) {
title="Copy"
className={`${styles.copyButton}`}
onClick={() => {
navigator.clipboard.writeText(props.chatMessage.message);
navigator.clipboard.writeText(textRendered);
setCopySuccess(true);
}}
>

View file

@ -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();

View file

@ -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';"

View file

@ -43,6 +43,7 @@ function FisherYatesShuffle(array: any[]) {
function ChatBodyData(props: ChatBodyDataProps) {
const [message, setMessage] = useState("");
const [image, setImage] = useState<string | null>(null);
const [processingMessage, setProcessingMessage] = useState(false);
const [greeting, setGreeting] = useState("");
const [shuffledOptions, setShuffledOptions] = useState<Suggestion[]>([]);
@ -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) {
<ChatInputArea
isLoggedIn={props.isLoggedIn}
sendMessage={(message) => setMessage(message)}
sendImage={(image) => setImage(image)}
sendDisabled={processingMessage}
chatOptionsData={props.chatOptionsData}
conversationId={null}
@ -310,6 +315,7 @@ function ChatBodyData(props: ChatBodyDataProps) {
<ChatInputArea
isLoggedIn={props.isLoggedIn}
sendMessage={(message) => setMessage(message)}
sendImage={(image) => setImage(image)}
sendDisabled={processingMessage}
chatOptionsData={props.chatOptionsData}
conversationId={null}

View file

@ -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';"

View file

@ -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<string | null>(null);
const [processingMessage, setProcessingMessage] = useState(false);
const [agentMetadata, setAgentMetadata] = useState<AgentData | null>(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) {
/>
</div>
<div
className={`${styles.inputBox} shadow-md bg-background align-middle items-center justify-center px-3`}
className={`${styles.inputBox} p-1 md:px-2 shadow-md bg-background align-middle items-center justify-center dark:bg-neutral-700 dark:border-0 dark:shadow-sm rounded-t-2xl rounded-b-none md:rounded-xl`}
>
<ChatInputArea
isLoggedIn={props.isLoggedIn}
sendMessage={(message) => 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<string[]>([]);
const [paramSlug, setParamSlug] = useState<string | undefined>(undefined);
const [image64, setImage64] = useState<string>("");
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 += `&region=${locationData.region}&country=${locationData.country}&city=${locationData.city}&timezone=${locationData.timezone}`;
}
const response = await fetch(chatAPI);
const response = await fetch(chatAPI, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: image64 ? JSON.stringify({ image: image64 }) : undefined,
});
try {
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 <Loading />;
}
@ -275,6 +274,7 @@ export default function SharedChat() {
setTitle={setTitle}
setUploadedFiles={setUploadedFiles}
isMobileWidth={isMobileWidth}
setImage64={setImage64}
/>
</Suspense>
</div>

View file

@ -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;
}
}

View file

@ -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",

View file

@ -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"

View file

@ -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()

View file

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

View file

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

View file

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

View file

@ -93,6 +93,7 @@ class ChatModelOptions(BaseModel):
tokenizer = models.CharField(max_length=200, default=None, null=True, blank=True)
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
)

View file

@ -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}")

View file

@ -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"))

View file

@ -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:

View file

@ -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)

View file

@ -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.')

View file

@ -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

View file

@ -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

View file

@ -462,8 +462,8 @@ async def test_chat_with_unauthenticated_user(chat_client_with_auth, api_user2:
headers = {"Authorization": f"Bearer {api_user2.token}"}
# 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

View file

@ -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

View file

@ -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