"\n[{\"role\":\"user\",\"content\":[\"Release notes for this?\\n\\ndiff --git a/.github/workflows/labeeb-workers-main-AutoDeployTrigger-80f2a7e2-7b85-4d05-974c-f2699ea43214.yml b/.github/workflows/labeeb-workers-main-AutoDeployTrigger-80f2a7e2-7b85-4d05-974c-f2699ea43214.yml\\ndeleted file mode 100644\\nindex ed46bb3..0000000\\n--- a/.github/workflows/labeeb-workers-main-AutoDeployTrigger-80f2a7e2-7b85-4d05-974c-f2699ea43214.yml\\n+++ /dev/null\\n@@ -1,48 +0,0 @@\\n-name: Trigger auto deployment for labeeb-workers-main\\n-\\n-# When this action will be executed\\n-on:\\n- # Automatically trigger it when detected changes in repo\\n- push:\\n- branches: \\n- [ main ]\\n- paths:\\n- - '**'\\n- - '.github/workflows/labeeb-workers-main-AutoDeployTrigger-80f2a7e2-7b85-4d05-974c-f2699ea43214.yml'\\n-\\n- # Allow manual trigger \\n- workflow_dispatch: \\n-\\n-jobs:\\n- build-and-deploy-workers:\\n- runs-on: ubuntu-latest\\n- permissions: \\n- id-token: write #This is required for requesting the OIDC JWT Token\\n- contents: read #Required when GH token is used to authenticate with private repo\\n-\\n- steps:\\n- - name: Checkout to the branch\\n- uses: actions/checkout@v2\\n-\\n- - name: Azure Login\\n- uses: azure/login@v1\\n- with:\\n- client-id: ${{ secrets.LABEEBWORKERSMAIN_AZURE_CLIENT_ID }}\\n- tenant-id: ${{ secrets.LABEEBWORKERSMAIN_AZURE_TENANT_ID }}\\n- subscription-id: ${{ secrets.LABEEBWORKERSMAIN_AZURE_SUBSCRIPTION_ID }}\\n-\\n- - name: Build and push container image to registry\\n- uses: azure/container-apps-deploy-action@v2\\n- with:\\n- appSourcePath: ${{ github.workspace }}\\n- dockerFilePath: Dockerfile.worker\\n- registryUrl: archipelagoairegistry.azurecr.io\\n- registryUsername: ${{ secrets.LABEEBWORKERSMAIN_REGISTRY_USERNAME }}\\n- registryPassword: ${{ secrets.LABEEBWORKERSMAIN_REGISTRY_PASSWORD }}\\n- containerAppName: labeeb-workers-main\\n- resourceGroup: Archipelago-ML-Experimentation\\n- imageToBuild: archipelagoairegistry.azurecr.io/labeeb-workers-main:${{ github.sha }}\\n- _buildArgumentsKey_: |\\n- _buildArgumentsValues_\\n-\\n-\\ndiff --git a/.gitignore b/.gitignore\\nindex a71a12f..3138f16 100644\\n--- a/.gitignore\\n+++ b/.gitignore\\n@@ -30,3 +30,4 @@ public/app/\\n src/locales/\\n dump.rdb\\n *.code-workspace\\n+.cursorrules\\n\\\\ No newline at end of file\\ndiff --git a/@/components/ui/alert-dialog.jsx b/@/components/ui/alert-dialog.jsx\\nnew file mode 100644\\nindex 0000000..1a0edf7\\n--- /dev/null\\n+++ b/@/components/ui/alert-dialog.jsx\\n@@ -0,0 +1,122 @@\\n+\\\"use client\\\";\\n+\\n+import * as React from \\\"react\\\";\\n+import * as AlertDialogPrimitive from \\\"@radix-ui/react-alert-dialog\\\";\\n+\\n+import { cn } from \\\"@/lib/utils\\\";\\n+import { buttonVariants } from \\\"@/components/ui/button\\\";\\n+\\n+const AlertDialog = AlertDialogPrimitive.Root;\\n+\\n+const AlertDialogTrigger = AlertDialogPrimitive.Trigger;\\n+\\n+const AlertDialogPortal = AlertDialogPrimitive.Portal;\\n+\\n+const AlertDialogOverlay = React.forwardRef(({ className, ...props }, ref) => (\\n+ \\n+));\\n+AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName;\\n+\\n+const AlertDialogContent = React.forwardRef(({ className, ...props }, ref) => (\\n+ \\n+ \\n+ \\n+ \\n+));\\n+AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName;\\n+\\n+const AlertDialogHeader = ({ className, ...props }) => (\\n+ \\n+);\\n+AlertDialogHeader.displayName = \\\"AlertDialogHeader\\\";\\n+\\n+const AlertDialogFooter = ({ className, ...props }) => (\\n+ \\n+);\\n+AlertDialogFooter.displayName = \\\"AlertDialogFooter\\\";\\n+\\n+const AlertDialogTitle = React.forwardRef(({ className, ...props }, ref) => (\\n+ \\n+));\\n+AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName;\\n+\\n+const AlertDialogDescription = React.forwardRef(\\n+ ({ className, ...props }, ref) => (\\n+ \\n+ ),\\n+);\\n+AlertDialogDescription.displayName =\\n+ AlertDialogPrimitive.Description.displayName;\\n+\\n+const AlertDialogAction = React.forwardRef(({ className, ...props }, ref) => (\\n+ \\n+));\\n+AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName;\\n+\\n+const AlertDialogCancel = React.forwardRef(({ className, ...props }, ref) => (\\n+ \\n+));\\n+AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName;\\n+\\n+export {\\n+ AlertDialog,\\n+ AlertDialogPortal,\\n+ AlertDialogOverlay,\\n+ AlertDialogTrigger,\\n+ AlertDialogContent,\\n+ AlertDialogHeader,\\n+ AlertDialogFooter,\\n+ AlertDialogTitle,\\n+ AlertDialogDescription,\\n+ AlertDialogAction,\\n+ AlertDialogCancel,\\n+};\\ndiff --git a/__mocks__/styleMock.js b/__mocks__/styleMock.js\\nnew file mode 100644\\nindex 0000000..f053ebf\\n--- /dev/null\\n+++ b/__mocks__/styleMock.js\\n@@ -0,0 +1 @@\\n+module.exports = {};\\ndiff --git a/app.config/config/data/taxonomySets.js b/app.config/config/data/taxonomySets.js\\nindex 82bafa9..1ef9412 100644\\n--- a/app.config/config/data/taxonomySets.js\\n+++ b/app.config/config/data/taxonomySets.js\\n@@ -62,7 +62,7 @@ export async function initializeTaxonomies() {\\n // will include the same file with two paths:\\n // ./filename.json and /filename.json\\n if (dedupedFileNames.includes(filenameOnly)) {\\n- return;\\n+ return null;\\n }\\n \\n const setName = filename.slice(2, -5); // Remove './' and '.json' from the file name\\ndiff --git a/app.config/config/index.js b/app.config/config/index.js\\nindex eb7facb..30c326d 100644\\n--- a/app.config/config/index.js\\n+++ b/app.config/config/index.js\\n@@ -14,7 +14,7 @@ const cortexURLs = {\\n // The entire Labeeb application can be configured here\\n // Note that all assets and locales are copied to the public/app and src/locales directories respectively\\n // by the prebuild.js script\\n-export default {\\n+const config = {\\n global: {\\n siteTitle: \\\"Labeeb\\\",\\n getLogo: (language) =>\\n@@ -77,3 +77,5 @@ export default {\\n provider: \\\"entra\\\",\\n },\\n };\\n+\\n+export default config;\\ndiff --git a/app.config/config/transcribe/TranscribeUrlConstants.js b/app.config/config/transcribe/TranscribeUrlConstants.js\\nindex db70436..e5e824c 100644\\n--- a/app.config/config/transcribe/TranscribeUrlConstants.js\\n+++ b/app.config/config/transcribe/TranscribeUrlConstants.js\\n@@ -1,3 +1,5 @@\\n+import { isYoutubeUrl } from \\\"../../../src/utils/urlUtils\\\";\\n+\\n export const AJE = \\\"665003303001\\\";\\n export const AJA = \\\"665001584001\\\";\\n export const getAxisUrl = (accountId, searchQuery) =>\\n@@ -9,6 +11,40 @@ export const fetchUrlSource = async (url) => {\\n );\\n if (!response.ok) {\\n const data = await response.json();\\n+ if (data.error === \\\"Unsupported YouTube channel\\\" && isYoutubeUrl(url)) {\\n+ // Convert YouTube URL to embed URL\\n+ const videoId = url.match(/(?:v=|\\\\/)([\\\\w-]{11})(?:\\\\?|$|&)/)?.[1];\\n+ const embedUrl = videoId\\n+ ? `https://www.youtube.com/embed/${videoId}`\\n+ : url;\\n+\\n+ // Fetch video title using oEmbed\\n+ let videoTitle = \\\"YouTube Video (External)\\\";\\n+ try {\\n+ const oembedResponse = await fetch(\\n+ `https://www.youtube.com/oembed?url=${encodeURIComponent(url)}&format=json`,\\n+ );\\n+ if (oembedResponse.ok) {\\n+ const oembedData = await oembedResponse.json();\\n+ videoTitle = oembedData.title;\\n+ }\\n+ } catch (e) {\\n+ console.warn(\\\"Failed to fetch YouTube video title:\\\", e);\\n+ }\\n+\\n+ return {\\n+ results: [\\n+ {\\n+ name: videoTitle,\\n+ similarity: 1,\\n+ videoUrl: embedUrl,\\n+ url: url,\\n+ isYouTube: true,\\n+ fromExternalChannel: true,\\n+ },\\n+ ],\\n+ };\\n+ }\\n throw new Error(\\n formatErrorMessage(data.error) || \\\"Network response was not ok\\\",\\n );\\ndiff --git a/app.config/locales/ar.json b/app.config/locales/ar.json\\nindex 8550558..3966c3f 100644\\n--- a/app.config/locales/ar.json\\n+++ b/app.config/locales/ar.json\\n@@ -497,5 +497,31 @@\\n \\\"Download .txt\\\": \\\"تنزيل بتنسيق TXT\\\",\\n \\\"Taxonomy\\\": \\\"التصنيف\\\",\\n \\\"Transcript\\\": \\\"النص المنسوخ\\\",\\n- \\\"{{name}}: {{language}} Translation\\\": \\\"{{name}}: ترجمة {{language}}\\\"\\n+ \\\"{{name}}: {{language}} Translation\\\": \\\"{{name}}: ترجمة {{language}}\\\",\\n+ \\\"Processing media...\\\": \\\"جاري معالجة الوسائط...\\\",\\n+ \\\"Transcription type\\\": \\\"نوع التنسيق\\\",\\n+ \\\"Memory backup\\\": \\\"نسخة احتياطية للذاكرة\\\",\\n+ \\\"Download memory backup\\\": \\\"تنزيل نسخة احتياطية للذاكرة\\\",\\n+ \\\"Upload memory from backup\\\": \\\"تحميل الذاكرة من النسخة الاحتياطية\\\",\\n+ \\\"Failed to read the file. Please try again.\\\": \\\"فشل في قراءة الملف. يرجى المحاولة مرة أخرى.\\\",\\n+ \\\"Failed to parse memory file. Please ensure it is a valid JSON file with the correct memory structure.\\\": \\\"فشل في تحليل ملف الذاكرة. يرجى التأكد من أنه ملف JSON صالح بهيكل الذاكرة الصحيح.\\\",\\n+ \\\"Invalid memory file format\\\": \\\"تنسيق ملف الذاكرة غير صالح\\\",\\n+ \\\"Enable streaming responses\\\": \\\"تفعيل الاستجابات المنسية\\\",\\n+ \\\"{{from}} to {{to}}\\\": \\\"{{from}} إلى {{to}}\\\",\\n+ \\\"Video translation\\\": \\\"ترجمة الفيديو\\\",\\n+ \\\"In progress\\\": \\\"قيد التنفيذ\\\",\\n+ \\\"Completed\\\": \\\"منجز\\\",\\n+ \\\"Failed\\\": \\\"فشل\\\",\\n+ \\\"View all\\\": \\\"عرض الكل\\\",\\n+ \\\"No recent or active notifications\\\": \\\"لا يوجد إشعارات مفعلة\\\",\\n+ \\\"View history\\\": \\\"عرض التاريخ\\\",\\n+ \\\"All notifications\\\": \\\"جميع الإشعارات\\\",\\n+ \\\"Transcript not looking right?\\\": \\\"النص المنسوخ لا يبدو صحيحًا؟\\\",\\n+ \\\"Transcribe again using an alternate model\\\": \\\"تنسيق مرة أخرى باستخدام نموذج مختلف\\\",\\n+ \\\"Re-transcribing\\\": \\\"إعادة التنسيق\\\",\\n+ \\\"Add audio track\\\": \\\"إضافة صوت\\\",\\n+ \\\"Transcribing... This may take a few minutes.\\\": \\\"جاري التنسيق... قد يستغرق هذا بضع دقائق.\\\",\\n+ \\\"Auto-transcribing\\\": \\\"تنسيق تلقائي\\\",\\n+ \\\"Edit title\\\": \\\"تعديل العنوان\\\",\\n+ \\\"Delete chat\\\": \\\"حذف الدردشة\\\"\\n }\\ndiff --git a/app/api/azure-video-translate/route.js b/app/api/azure-video-translate/route.js\\nnew file mode 100644\\nindex 0000000..1bb3a97\\n--- /dev/null\\n+++ b/app/api/azure-video-translate/route.js\\n@@ -0,0 +1,107 @@\\n+import { NextResponse } from \\\"next/server\\\";\\n+import { Queue } from \\\"bullmq\\\";\\n+import Redis from \\\"ioredis\\\";\\n+import { AZURE_VIDEO_TRANSLATE } from \\\"../../../src/graphql\\\";\\n+import { getClient } from \\\"../../../src/graphql\\\";\\n+import RequestProgress from \\\"../models/request-progress.mjs\\\";\\n+import { getCurrentUser } from \\\"../utils/auth\\\";\\n+\\n+const connection = new Redis(\\n+ process.env.REDIS_CONNECTION_STRING || \\\"redis://localhost:6379\\\",\\n+ {\\n+ maxRetriesPerRequest: null,\\n+ },\\n+);\\n+\\n+const requestProgressQueue = new Queue(\\\"request-progress\\\", {\\n+ connection,\\n+});\\n+\\n+export async function POST(req) {\\n+ try {\\n+ const body = await req.json();\\n+ const { sourceLocale, targetLocale, targetLocaleLabel, url } = body;\\n+\\n+ console.log(\\\"Starting video translation request:\\\", {\\n+ sourceLocale,\\n+ targetLocale,\\n+ targetLocaleLabel,\\n+ url,\\n+ });\\n+\\n+ // Initial GraphQL query to start the translation\\n+ const { data } = await getClient().query({\\n+ query: AZURE_VIDEO_TRANSLATE,\\n+ variables: {\\n+ mode: \\\"uploadvideooraudiofileandcreatetranslation\\\",\\n+ sourcelocale: sourceLocale,\\n+ targetlocale: targetLocale,\\n+ sourcevideooraudiofilepath: url,\\n+ stream: true,\\n+ },\\n+ fetchPolicy: \\\"no-cache\\\",\\n+ });\\n+\\n+ const requestId = data.azure_video_translate.result;\\n+\\n+ console.log(\\\"Got requestId from Azure:\\\", requestId);\\n+\\n+ // Get current user\\n+ const user = await getCurrentUser();\\n+\\n+ // Create initial progress record\\n+ await RequestProgress.findOneAndUpdate(\\n+ { requestId },\\n+ {\\n+ owner: user._id,\\n+ type: \\\"video-translate\\\",\\n+ status: \\\"in_progress\\\",\\n+ metadata: {\\n+ sourceLocale,\\n+ targetLocale,\\n+ url,\\n+ },\\n+ },\\n+ {\\n+ new: true,\\n+ upsert: true,\\n+ },\\n+ );\\n+\\n+ // Add job to queue\\n+ const job = await requestProgressQueue.add(\\n+ \\\"request-progress\\\",\\n+ {\\n+ requestId,\\n+ type: \\\"video-translate\\\",\\n+ userId: user._id,\\n+ metadata: {\\n+ sourceLocale,\\n+ targetLocale,\\n+ targetLocaleLabel,\\n+ url,\\n+ },\\n+ },\\n+ {\\n+ timeout: 5 * 60 * 1000,\\n+ removeOnComplete: {\\n+ age: 24 * 3600,\\n+ count: 1000,\\n+ },\\n+ removeOnFail: {\\n+ age: 24 * 3600,\\n+ },\\n+ },\\n+ );\\n+\\n+ console.log(\\\"Added job to queue:\\\", job.id);\\n+\\n+ return NextResponse.json({\\n+ requestId,\\n+ jobId: job.id,\\n+ });\\n+ } catch (error) {\\n+ console.error(\\\"Azure video translate error:\\\", error);\\n+ return NextResponse.json({ error: error.message }, { status: 500 });\\n+ }\\n+}\\ndiff --git a/app/api/cancel-request/route.js b/app/api/cancel-request/route.js\\nnew file mode 100644\\nindex 0000000..ddcc3d5\\n--- /dev/null\\n+++ b/app/api/cancel-request/route.js\\n@@ -0,0 +1,53 @@\\n+import { NextResponse } from \\\"next/server\\\";\\n+import { Queue } from \\\"bullmq\\\";\\n+import Redis from \\\"ioredis\\\";\\n+import RequestProgress from \\\"../models/request-progress.mjs\\\";\\n+import { getCurrentUser } from \\\"../utils/auth\\\";\\n+\\n+const connection = new Redis(\\n+ process.env.REDIS_CONNECTION_STRING || \\\"redis://localhost:6379\\\",\\n+ {\\n+ maxRetriesPerRequest: null,\\n+ },\\n+);\\n+\\n+const requestProgressQueue = new Queue(\\\"request-progress\\\", { connection });\\n+\\n+export async function POST(req) {\\n+ try {\\n+ const { requestId } = await req.json();\\n+ const user = await getCurrentUser();\\n+\\n+ // Find the request and verify ownership\\n+ const request = await RequestProgress.findOne({\\n+ requestId,\\n+ owner: user._id,\\n+ });\\n+\\n+ if (!request) {\\n+ return NextResponse.json(\\n+ { error: \\\"Request not found\\\" },\\n+ { status: 404 },\\n+ );\\n+ }\\n+\\n+ // Get active jobs for this request\\n+ const jobs = await requestProgressQueue.getJobs([\\\"waiting\\\"]);\\n+ const job = jobs.find((job) => job.data.requestId === requestId);\\n+\\n+ if (job) {\\n+ await job.remove();\\n+ }\\n+\\n+ // Update request status\\n+ await RequestProgress.findOneAndUpdate(\\n+ { requestId },\\n+ { status: \\\"cancelled\\\" },\\n+ );\\n+\\n+ return NextResponse.json({ success: true });\\n+ } catch (error) {\\n+ console.error(\\\"Cancel request error:\\\", error);\\n+ return NextResponse.json({ error: error.message }, { status: 500 });\\n+ }\\n+}\\ndiff --git a/app/api/chats/_lib.js b/app/api/chats/_lib.js\\nindex 0a8e842..7e52d78 100644\\n--- a/app/api/chats/_lib.js\\n+++ b/app/api/chats/_lib.js\\n@@ -20,6 +20,24 @@ export async function getRecentChatsOfCurrentUser() {\\n { _id: 1, title: 1, titleSetByUser: 1 },\\n );\\n \\n+ // For chats without a custom title, fetch the first message separately\\n+ // This approach avoids truncating the messages array in the main cache\\n+ for (const chat of recentChatsUnordered) {\\n+ if (!chat.title || chat.title === \\\"New Chat\\\" || chat.title === \\\"\\\") {\\n+ const chatWithFirstMessage = await Chat.findOne(\\n+ { _id: chat._id },\\n+ { messages: { $slice: 1 } },\\n+ );\\n+ if (\\n+ chatWithFirstMessage &&\\n+ chatWithFirstMessage.messages &&\\n+ chatWithFirstMessage.messages.length > 0\\n+ ) {\\n+ chat._doc.firstMessage = chatWithFirstMessage.messages[0];\\n+ }\\n+ }\\n+ }\\n+\\n const recentChatsMap = recentChatsUnordered.reduce((acc, chat) => {\\n acc[chat._id] = chat;\\n return acc;\\ndiff --git a/app/api/models/request-progress.mjs b/app/api/models/request-progress.mjs\\nnew file mode 100644\\nindex 0000000..820b682\\n--- /dev/null\\n+++ b/app/api/models/request-progress.mjs\\n@@ -0,0 +1,60 @@\\n+import mongoose from \\\"mongoose\\\";\\n+\\n+const requestProgressSchema = new mongoose.Schema(\\n+ {\\n+ requestId: {\\n+ type: String,\\n+ required: true,\\n+ unique: true,\\n+ },\\n+ owner: {\\n+ type: mongoose.Schema.Types.ObjectId,\\n+ ref: \\\"User\\\",\\n+ required: true,\\n+ },\\n+ progress: {\\n+ type: Number,\\n+ required: true,\\n+ default: 0,\\n+ },\\n+ data: mongoose.Schema.Types.Mixed,\\n+ statusText: String,\\n+ status: {\\n+ type: String,\\n+ enum: [\\n+ \\\"pending\\\",\\n+ \\\"in_progress\\\",\\n+ \\\"completed\\\",\\n+ \\\"failed\\\",\\n+ \\\"cancelled\\\",\\n+ ],\\n+ default: \\\"pending\\\",\\n+ },\\n+ error: String,\\n+ type: {\\n+ type: String,\\n+ required: true,\\n+ },\\n+ metadata: {\\n+ type: mongoose.Schema.Types.Mixed,\\n+ default: null,\\n+ },\\n+ dismissed: {\\n+ type: Boolean,\\n+ default: false,\\n+ },\\n+ },\\n+ {\\n+ timestamps: true,\\n+ },\\n+);\\n+\\n+requestProgressSchema.index({ requestId: 1 });\\n+requestProgressSchema.index({ createdAt: -1 });\\n+requestProgressSchema.index({ owner: 1 });\\n+\\n+const RequestProgress =\\n+ mongoose.models.RequestProgress ||\\n+ mongoose.model(\\\"RequestProgress\\\", requestProgressSchema);\\n+\\n+export default RequestProgress;\\ndiff --git a/app/api/models/user-state.js b/app/api/models/user-state.mjs\\nsimilarity index 100%\\nrename from app/api/models/user-state.js\\nrename to app/api/models/user-state.mjs\\ndiff --git a/app/api/models/user.mjs b/app/api/models/user.mjs\\nindex 7bea9fc..a72968f 100644\\n--- a/app/api/models/user.mjs\\n+++ b/app/api/models/user.mjs\\n@@ -42,6 +42,11 @@ const userSchema = new mongoose.Schema(\\n required: true,\\n default: \\\"OpenAI\\\",\\n },\\n+ streamingEnabled: {\\n+ type: Boolean,\\n+ required: true,\\n+ default: false,\\n+ },\\n uploadedDocs: {\\n type: [uploadedDocsSchema],\\n required: false,\\ndiff --git a/app/api/options/route.js b/app/api/options/route.js\\nindex 1bda936..84173af 100644\\n--- a/app/api/options/route.js\\n+++ b/app/api/options/route.js\\n@@ -5,7 +5,14 @@ export async function POST(req) {\\n try {\\n const body = await req.json();\\n \\n- const { userId, contextId, aiMemorySelfModify, aiName, aiStyle } = body;\\n+ const {\\n+ userId,\\n+ contextId,\\n+ aiMemorySelfModify,\\n+ aiName,\\n+ aiStyle,\\n+ streamingEnabled,\\n+ } = body;\\n \\n if (!mongoose.connection.readyState) {\\n throw new Error(\\\"Database is not connected\\\");\\n@@ -26,6 +33,9 @@ export async function POST(req) {\\n if (aiStyle !== undefined) {\\n user.aiStyle = aiStyle;\\n }\\n+ if (streamingEnabled !== undefined) {\\n+ user.streamingEnabled = streamingEnabled;\\n+ }\\n await user.save();\\n return Response.json({ status: \\\"success\\\" });\\n } else {\\ndiff --git a/app/api/request-progress/route.js b/app/api/request-progress/route.js\\nnew file mode 100644\\nindex 0000000..506f11d\\n--- /dev/null\\n+++ b/app/api/request-progress/route.js\\n@@ -0,0 +1,64 @@\\n+import RequestProgress from \\\"../models/request-progress\\\";\\n+import { NextResponse } from \\\"next/server\\\";\\n+import { getCurrentUser } from \\\"../utils/auth\\\";\\n+\\n+export async function GET(request) {\\n+ try {\\n+ const user = await getCurrentUser();\\n+ const { searchParams } = new URL(request.url);\\n+ const showDismissed = searchParams.get(\\\"showDismissed\\\") === \\\"true\\\";\\n+ const page = parseInt(searchParams.get(\\\"page\\\")) || 1;\\n+ const limit = parseInt(searchParams.get(\\\"limit\\\")) || 10;\\n+\\n+ const query = {\\n+ owner: user._id,\\n+ };\\n+\\n+ if (!showDismissed) {\\n+ query.dismissed = { $ne: true };\\n+ const fortyEightHoursAgo = new Date(\\n+ Date.now() - 48 * 60 * 60 * 1000,\\n+ );\\n+ query.createdAt = { $gte: fortyEightHoursAgo };\\n+ }\\n+\\n+ const requests = await RequestProgress.find(query)\\n+ .sort({ createdAt: -1 })\\n+ .skip((page - 1) * limit)\\n+ .limit(limit);\\n+\\n+ const total = await RequestProgress.countDocuments(query);\\n+\\n+ return NextResponse.json({\\n+ requests,\\n+ hasMore: total > page * limit,\\n+ });\\n+ } catch (error) {\\n+ return NextResponse.json({ error: error.message }, { status: 500 });\\n+ }\\n+}\\n+\\n+export async function PATCH(request) {\\n+ try {\\n+ const user = await getCurrentUser();\\n+ const { requestId } = await request.json();\\n+ await RequestProgress.findOneAndUpdate(\\n+ { requestId, owner: user._id },\\n+ { dismissed: true },\\n+ );\\n+ return NextResponse.json({ success: true });\\n+ } catch (error) {\\n+ return NextResponse.json({ error: error.message }, { status: 500 });\\n+ }\\n+}\\n+\\n+export async function DELETE(request) {\\n+ try {\\n+ const user = await getCurrentUser();\\n+ const { requestId } = await request.json();\\n+ await RequestProgress.findOneAndDelete({ requestId, owner: user._id });\\n+ return NextResponse.json({ success: true });\\n+ } catch (error) {\\n+ return NextResponse.json({ error: error.message }, { status: 500 });\\n+ }\\n+}\\ndiff --git a/app/api/users/me/state/route.js b/app/api/users/me/state/route.js\\nindex aa35018..7310e59 100644\\n--- a/app/api/users/me/state/route.js\\n+++ b/app/api/users/me/state/route.js\\n@@ -1,4 +1,4 @@\\n-import UserState from \\\"../../../models/user-state\\\";\\n+import UserState from \\\"../../../models/user-state.mjs\\\";\\n import { getCurrentUser } from \\\"../../../utils/auth\\\";\\n \\n function transformUserState(userState) {\\ndiff --git a/app/notifications/NotificationsPage.js b/app/notifications/NotificationsPage.js\\nnew file mode 100644\\nindex 0000000..ce16b64\\n--- /dev/null\\n+++ b/app/notifications/NotificationsPage.js\\n@@ -0,0 +1,220 @@\\n+\\\"use client\\\";\\n+import { TrashIcon, XIcon } from \\\"lucide-react\\\";\\n+import { useEffect, useState, useCallback } from \\\"react\\\";\\n+import { useTranslation } from \\\"react-i18next\\\";\\n+import { useInView } from \\\"react-intersection-observer\\\";\\n+import TimeAgo from \\\"react-time-ago\\\";\\n+import stringcase from \\\"stringcase\\\";\\n+import {\\n+ useDeleteNotification,\\n+ useInfiniteNotifications,\\n+ useCancelRequest,\\n+} from \\\"../../app/queries/notifications\\\";\\n+import {\\n+ NotificationDisplayType,\\n+ StatusIndicator,\\n+ getStatusColorClass,\\n+} from \\\"../../src/components/notifications/NotificationButton\\\";\\n+import {\\n+ AlertDialog,\\n+ AlertDialogAction,\\n+ AlertDialogCancel,\\n+ AlertDialogContent,\\n+ AlertDialogDescription,\\n+ AlertDialogFooter,\\n+ AlertDialogHeader,\\n+ AlertDialogTitle,\\n+} from \\\"@/components/ui/alert-dialog\\\";\\n+\\n+export default function NotificationsPage() {\\n+ const { t } = useTranslation();\\n+ const { ref, inView } = useInView();\\n+\\n+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage, status } =\\n+ useInfiniteNotifications();\\n+\\n+ const deleteNotification = useDeleteNotification();\\n+ const [cancelRequestId, setCancelRequestId] = useState(null);\\n+ const cancelRequest = useCancelRequest();\\n+\\n+ useEffect(() => {\\n+ if (inView && hasNextPage) {\\n+ fetchNextPage();\\n+ }\\n+ }, [inView, hasNextPage, fetchNextPage]);\\n+\\n+ const handleDelete = (requestId) => {\\n+ if (\\n+ window.confirm(\\n+ t(\\\"Are you sure you want to delete this notification?\\\"),\\n+ )\\n+ ) {\\n+ deleteNotification.mutate(requestId);\\n+ }\\n+ };\\n+\\n+ const handleCancelRequest = (requestId) => {\\n+ setCancelRequestId(requestId);\\n+ };\\n+\\n+ const confirmCancel = useCallback(async () => {\\n+ if (cancelRequestId) {\\n+ await cancelRequest.mutate(cancelRequestId);\\n+ setCancelRequestId(null);\\n+ }\\n+ }, [cancelRequestId, cancelRequest]);\\n+\\n+ const notifications = data?.pages.flatMap((page) => page.requests) ?? [];\\n+\\n+ return (\\n+
\\n+

\\n+ {t(\\\"All notifications\\\")}\\n+

\\n+
\\n+ {status === \\\"pending\\\" ? (\\n+
\\n+
\\n+
\\n+ ) : notifications.length === 0 ? (\\n+

\\n+ {t(\\\"No notifications\\\")}\\n+

\\n+ ) : (\\n+ <>\\n+ {notifications.map((notification) => (\\n+ \\n+
\\n+
\\n+ \\n+
\\n+
\\n+
\\n+ \\n+ {t(\\n+ NotificationDisplayType[\\n+ notification.type\\n+ ],\\n+ )}\\n+ \\n+
\\n+ {notification.status ===\\n+ \\\"in_progress\\\" && (\\n+ \\n+ handleCancelRequest(\\n+ notification.requestId,\\n+ )\\n+ }\\n+ className=\\\"p-1 rounded flex items-center gap-1 text-sm text-gray-500 hover:text-red-500\\\"\\n+ title={t(\\\"Cancel\\\")}\\n+ >\\n+ \\n+ \\n+ )}\\n+ {(notification.status ===\\n+ \\\"completed\\\" ||\\n+ notification.status ===\\n+ \\\"failed\\\" ||\\n+ notification.status ===\\n+ \\\"cancelled\\\") && (\\n+ \\n+ handleDelete(\\n+ notification.requestId,\\n+ )\\n+ }\\n+ className=\\\"p-1 rounded flex items-center gap-1 text-sm text-gray-500 hover:text-red-500\\\"\\n+ title={t(\\\"Delete\\\")}\\n+ >\\n+ \\n+ \\n+ )}\\n+
\\n+
\\n+ {notification.createdAt && (\\n+ \\n+ {t(\\\"Created \\\")}{\\\" \\\"}\\n+ \\n+ \\n+ )}\\n+ \\n+ {notification.statusText ||\\n+ (notification.status ===\\n+ \\\"failed\\\"\\n+ ? t(\\\"Request failed\\\")\\n+ : \\\"\\\")}\\n+ \\n+ \\n+ {t(\\n+ stringcase.sentencecase(\\n+ notification.status,\\n+ ),\\n+ )}\\n+ \\n+\\n+ {notification.status ===\\n+ \\\"in_progress\\\" && (\\n+
\\n+ \\n+
\\n+ )}\\n+
\\n+
\\n+
\\n+ ))}\\n+\\n+
\\n+ {isFetchingNextPage && (\\n+
\\n+
\\n+
\\n+ )}\\n+
\\n+ \\n+ )}\\n+
\\n+ setCancelRequestId(null)}\\n+ >\\n+ \\n+ \\n+ \\n+ {t(\\\"Confirm Cancellation\\\")}\\n+ \\n+ \\n+ {t(\\n+ \\\"Are you sure you want to cancel this request? This action cannot be undone.\\\",\\n+ )}\\n+ \\n+ \\n+ \\n+ {t(\\\"No\\\")}\\n+ \\n+ {t(\\\"Yes, Cancel Request\\\")}\\n+ \\n+ \\n+ \\n+ \\n+
\\n+ );\\n+}\\ndiff --git a/app/notifications/page.js b/app/notifications/page.js\\nnew file mode 100644\\nindex 0000000..10354aa\\n--- /dev/null\\n+++ b/app/notifications/page.js\\n@@ -0,0 +1,5 @@\\n+import NotificationsPage from \\\"./NotificationsPage\\\";\\n+\\n+export default function Page() {\\n+ return ;\\n+}\\ndiff --git a/app/providers.js b/app/providers.js\\nindex 86f9bc3..24a2a6b 100644\\n--- a/app/providers.js\\n+++ b/app/providers.js\\n@@ -1,6 +1,7 @@\\n // In Next.js, this file would be called: app/providers.jsx\\n \\\"use client\\\";\\n import { QueryClient, QueryClientProvider } from \\\"@tanstack/react-query\\\";\\n+import { NotificationProvider } from \\\"../src/contexts/NotificationContext\\\";\\n \\n function makeQueryClient() {\\n return new QueryClient({\\n@@ -39,7 +40,7 @@ export default function Providers({ children }) {\\n \\n return (\\n \\n- {children}\\n+ {children}\\n \\n );\\n }\\ndiff --git a/app/queries/chats.js b/app/queries/chats.js\\nindex 186fb53..718c26a 100644\\n--- a/app/queries/chats.js\\n+++ b/app/queries/chats.js\\n@@ -38,10 +38,21 @@ export function useGetActiveChats() {\\n activeChats.forEach((chat) => {\\n const existingChat =\\n queryClient.getQueryData([\\\"chat\\\", chat._id]) || {};\\n- queryClient.setQueryData([\\\"chat\\\", chat._id], {\\n- ...existingChat,\\n- ...chat,\\n- });\\n+\\n+ // If chat has a firstMessage property but the existing chat has a full messages array,\\n+ // keep the existing messages and don't overwrite with the truncated version\\n+ const updatedChat = { ...existingChat, ...chat };\\n+\\n+ // Only preserve existing messages if they exist and are not empty\\n+ if (\\n+ chat.firstMessage &&\\n+ existingChat.messages &&\\n+ existingChat.messages.length > 0\\n+ ) {\\n+ updatedChat.messages = existingChat.messages;\\n+ }\\n+\\n+ queryClient.setQueryData([\\\"chat\\\", chat._id], updatedChat);\\n });\\n return activeChats;\\n },\\n@@ -53,10 +64,12 @@ export function useGetActiveChats() {\\n }\\n \\n function temporaryNewChat({ messages, title }) {\\n+ const tempId = `temp_${Date.now()}_${crypto.randomUUID()}`;\\n return {\\n- _id: null,\\n+ _id: tempId,\\n messages: messages || [],\\n title: title || \\\"\\\",\\n+ isTemporary: true,\\n };\\n }\\n \\n@@ -71,52 +84,170 @@ export function useAddChat() {\\n });\\n return response.data;\\n },\\n- onMutate: async ({ messages, title }) => {\\n+ // Using the standard Tanstack Query pattern for optimistic updates\\n+ onMutate: async (newChatData) => {\\n+ // Cancel related queries to prevent race conditions\\n+ await queryClient.cancelQueries({\\n+ queryKey: [\\\"activeChats\\\", \\\"userChatInfo\\\", \\\"chats\\\"],\\n+ });\\n+\\n+ // Snapshot the current state\\n const previousActiveChats =\\n queryClient.getQueryData([\\\"activeChats\\\"]) || [];\\n const previousUserChatInfo =\\n queryClient.getQueryData([\\\"userChatInfo\\\"]) || {};\\n- const newChat = temporaryNewChat({ messages, title });\\n \\n+ // Create an optimistic chat entry\\n+ const optimisticChat = temporaryNewChat(newChatData);\\n+\\n+ // Update all relevant query data optimistically\\n+ queryClient.setQueryData(\\n+ [\\\"chat\\\", optimisticChat._id],\\n+ optimisticChat,\\n+ );\\n queryClient.setQueryData(\\n [\\\"activeChats\\\"],\\n- [newChat, ...previousActiveChats],\\n+ [optimisticChat, ...previousActiveChats],\\n );\\n queryClient.setQueryData([\\\"userChatInfo\\\"], {\\n ...previousUserChatInfo,\\n- activeChatId: newChat._id,\\n+ activeChatId: optimisticChat._id,\\n+ recentChatIds: previousUserChatInfo.recentChatIds\\n+ ? [\\n+ optimisticChat._id,\\n+ ...previousUserChatInfo.recentChatIds\\n+ .filter((id) => id !== optimisticChat._id)\\n+ .slice(0, 2),\\n+ ]\\n+ : [optimisticChat._id],\\n });\\n \\n- return { previousActiveChats, previousUserChatInfo };\\n- },\\n- onSuccess: (newChat) => {\\n- queryClient.setQueryData([\\\"chat\\\", newChat._id], newChat);\\n- queryClient.setQueryData([\\\"activeChats\\\"], (oldChats = []) => [\\n- newChat,\\n- ...oldChats.filter(\\n- (chat) => chat._id !== null && chat._id !== newChat._id,\\n- ),\\n- ]);\\n- queryClient.setQueryData([\\\"userChatInfo\\\"], (oldInfo) => ({\\n- ...oldInfo,\\n- activeChatId: newChat._id,\\n- }));\\n- queryClient.invalidateQueries({ queryKey: [\\\"userChatInfo\\\"] });\\n- queryClient.invalidateQueries({ queryKey: [\\\"activeChats\\\"] });\\n- queryClient.invalidateQueries({ queryKey: [\\\"chats\\\"] });\\n+ // Return context for potential rollback\\n+ return {\\n+ previousActiveChats,\\n+ previousUserChatInfo,\\n+ optimisticChatId: optimisticChat._id,\\n+ };\\n },\\n- onError: (err, variables, context) => {\\n- if (context?.previousActiveChats) {\\n+ onError: (err, newChat, context) => {\\n+ // On error, roll back to the previous state\\n+ if (context) {\\n queryClient.setQueryData(\\n [\\\"activeChats\\\"],\\n context.previousActiveChats,\\n );\\n- }\\n- if (context?.previousUserChatInfo) {\\n queryClient.setQueryData(\\n [\\\"userChatInfo\\\"],\\n context.previousUserChatInfo,\\n );\\n+ queryClient.removeQueries({\\n+ queryKey: [\\\"chat\\\", context.optimisticChatId],\\n+ });\\n+ }\\n+ },\\n+ onSuccess: (serverChat, variables, context) => {\\n+ // Remove the optimistic entry\\n+ if (context?.optimisticChatId) {\\n+ queryClient.removeQueries({\\n+ queryKey: [\\\"chat\\\", context.optimisticChatId],\\n+ });\\n+ }\\n+\\n+ // Add the confirmed server data\\n+ queryClient.setQueryData([\\\"chat\\\", serverChat._id], serverChat);\\n+\\n+ // Update active chats by replacing the optimistic version\\n+ queryClient.setQueryData([\\\"activeChats\\\"], (oldData = []) => {\\n+ return [\\n+ serverChat,\\n+ ...oldData.filter(\\n+ (chat) =>\\n+ chat._id !== context?.optimisticChatId &&\\n+ chat._id !== serverChat._id,\\n+ ),\\n+ ];\\n+ });\\n+\\n+ // Update the userChatInfo with the actual chat ID\\n+ queryClient.setQueryData([\\\"userChatInfo\\\"], (oldData = {}) => {\\n+ return {\\n+ ...oldData,\\n+ activeChatId: serverChat._id,\\n+ recentChatIds: oldData.recentChatIds\\n+ ? [\\n+ serverChat._id,\\n+ ...oldData.recentChatIds.filter(\\n+ (id) =>\\n+ id !== context?.optimisticChatId &&\\n+ id !== serverChat._id,\\n+ ),\\n+ ]\\n+ : [serverChat._id],\\n+ };\\n+ });\\n+ },\\n+ onSettled: () => {\\n+ // Always refresh the data to ensure consistency\\n+ queryClient.invalidateQueries({ queryKey: [\\\"chats\\\"] });\\n+ queryClient.invalidateQueries({ queryKey: [\\\"activeChats\\\"] });\\n+ queryClient.invalidateQueries({ queryKey: [\\\"userChatInfo\\\"] });\\n+ },\\n+ });\\n+}\\n+\\n+// The useAddMessage function will now automatically leverage the optimistic behavior\\n+// of useAddChat if no chatId is provided\\n+export function useAddMessage() {\\n+ const queryClient = useQueryClient();\\n+ const addChatMutation = useAddChat();\\n+\\n+ return useMutation({\\n+ mutationFn: async ({ message, chatId }) => {\\n+ let chatData;\\n+ if (!chatId) {\\n+ // No changes needed here - the optimistic updates are handled in useAddChat\\n+ const newChat = await addChatMutation.mutateAsync({\\n+ messages: [message],\\n+ });\\n+ chatId = String(newChat?._id);\\n+ chatData = newChat;\\n+ } else {\\n+ const chatResponse = await axios.post(\\n+ `/api/chats/${String(chatId)}`,\\n+ { message },\\n+ );\\n+ chatData = chatResponse.data;\\n+ queryClient.setQueryData([\\\"chat\\\", String(chatId)], chatData);\\n+ }\\n+ return chatData;\\n+ },\\n+ onMutate: ({ message, chatId }) => {\\n+ if (!chatId || !message) return;\\n+ const existingChat = queryClient.getQueryData([\\n+ \\\"chat\\\",\\n+ String(chatId),\\n+ ]);\\n+ const expectedChatData = {\\n+ ...existingChat,\\n+ messages: [...(existingChat?.messages || []), message],\\n+ };\\n+ queryClient.setQueryData(\\n+ [\\\"chat\\\", String(chatId)],\\n+ expectedChatData,\\n+ );\\n+ },\\n+ onSuccess: (updatedChat) => {\\n+ queryClient.setQueryData(\\n+ [\\\"chat\\\", String(updatedChat?._id)],\\n+ updatedChat,\\n+ );\\n+ },\\n+ onError: (err, variables, context) => {\\n+ if (context?.previousChat) {\\n+ queryClient.setQueryData(\\n+ [\\\"chat\\\", String(context.previousChat._id)],\\n+ context.previousChat,\\n+ );\\n }\\n },\\n });\\n@@ -206,14 +337,39 @@ export function useGetActiveChatId() {\\n }\\n \\n export function useGetChatById(chatId) {\\n+ const queryClient = useQueryClient();\\n+\\n return useQuery({\\n queryKey: [\\\"chat\\\", chatId],\\n queryFn: async () => {\\n if (!chatId) throw new Error(\\\"chatId is required\\\");\\n+\\n+ // Track this query with a timestamp to identify outdated responses\\n+ const requestTimestamp = Date.now();\\n+ queryClient.setQueryData(\\n+ [\\\"chatRequestTimestamp\\\", chatId],\\n+ requestTimestamp,\\n+ );\\n+\\n const response = await axios.get(`/api/chats/${String(chatId)}`);\\n+\\n+ // Check if this response is still the most recent one\\n+ const currentTimestamp =\\n+ queryClient.getQueryData([\\\"chatRequestTimestamp\\\", chatId]) || 0;\\n+ if (requestTimestamp < currentTimestamp) {\\n+ // Return the current data instead of the outdated response\\n+ return (\\n+ queryClient.getQueryData([\\\"chat\\\", chatId]) || response.data\\n+ );\\n+ }\\n+\\n return response.data;\\n },\\n enabled: !!chatId,\\n+ // Reduce stale time to ensure more frequent refreshes\\n+ staleTime: 1000 * 60, // 1 minute\\n+ // Add refetchOnMount to ensure fresh data when switching chats\\n+ refetchOnMount: true,\\n });\\n }\\n \\n@@ -303,68 +459,9 @@ export function useSetActiveChatId() {\\n }\\n return previousData;\\n },\\n- });\\n-}\\n-\\n-export function useAddMessage() {\\n- const queryClient = useQueryClient();\\n- const addChatMutation = useAddChat();\\n-\\n- return useMutation({\\n- mutationFn: async ({ message, chatId }) => {\\n- let chatData;\\n- if (!chatId) {\\n- const newChat = await addChatMutation.mutateAsync({\\n- messages: [message],\\n- });\\n- chatId = String(newChat?._id);\\n- chatData = newChat;\\n- queryClient.setQueryData([\\\"chats\\\"], (old = []) => [\\n- newChat,\\n- ...old,\\n- ]);\\n- queryClient.setQueryData([\\\"activeChats\\\"], (old = []) => [\\n- newChat,\\n- ...old,\\n- ]);\\n- } else {\\n- const chatResponse = await axios.post(\\n- `/api/chats/${String(chatId)}`,\\n- { message },\\n- );\\n- chatData = chatResponse.data;\\n- queryClient.setQueryData([\\\"chat\\\", String(chatId)], chatData);\\n- }\\n- return chatData;\\n- },\\n- onMutate: ({ message, chatId }) => {\\n- if (!chatId || !message) return;\\n- const existingChat = queryClient.getQueryData([\\n- \\\"chat\\\",\\n- String(chatId),\\n- ]);\\n- const expectedChatData = {\\n- ...existingChat,\\n- messages: [...(existingChat?.messages || []), message],\\n- };\\n- queryClient.setQueryData(\\n- [\\\"chat\\\", String(chatId)],\\n- expectedChatData,\\n- );\\n- },\\n- onSuccess: (updatedChat) => {\\n- queryClient.setQueryData(\\n- [\\\"chat\\\", String(updatedChat?._id)],\\n- updatedChat,\\n- );\\n- },\\n- onError: (err, variables, context) => {\\n- if (context?.previousChat) {\\n- queryClient.setQueryData(\\n- [\\\"chat\\\", String(context.previousChat._id)],\\n- context.previousChat,\\n- );\\n- }\\n+ onSuccess: () => {\\n+ // Simply mark the queries as stale after setting the active chat ID\\n+ queryClient.invalidateQueries({ queryKey: [\\\"activeChats\\\"] });\\n },\\n });\\n }\\n@@ -377,17 +474,33 @@ export function useUpdateChat() {\\n if (!chatId) {\\n throw new Error(\\\"chatId is required\\\");\\n }\\n+\\n+ // Track this mutation with a timestamp\\n+ const requestTimestamp = Date.now();\\n+ queryClient.setQueryData(\\n+ [\\\"chatRequestTimestamp\\\", chatId],\\n+ requestTimestamp,\\n+ );\\n+\\n const response = await axios.put(\\n `/api/chats/${String(chatId)}`,\\n updateData,\\n );\\n- return response.data;\\n+\\n+ return { data: response.data, timestamp: requestTimestamp };\\n },\\n onMutate: async ({ chatId, ...updateData }) => {\\n await queryClient.cancelQueries({ queryKey: [\\\"chat\\\", chatId] });\\n await queryClient.cancelQueries({ queryKey: [\\\"chats\\\"] });\\n await queryClient.cancelQueries({ queryKey: [\\\"activeChats\\\"] });\\n \\n+ // Track this mutation with a timestamp\\n+ const requestTimestamp = Date.now();\\n+ queryClient.setQueryData(\\n+ [\\\"chatRequestTimestamp\\\", chatId],\\n+ requestTimestamp,\\n+ );\\n+\\n const previousChat = queryClient.getQueryData([\\\"chat\\\", chatId]);\\n const expectedChatData = { ...previousChat, ...updateData };\\n \\n@@ -413,7 +526,7 @@ export function useUpdateChat() {\\n ) || [],\\n );\\n \\n- return { previousChat };\\n+ return { previousChat, timestamp: requestTimestamp };\\n },\\n onError: (err, variables, context) => {\\n if (context?.previousChat) {\\n@@ -423,7 +536,20 @@ export function useUpdateChat() {\\n );\\n }\\n },\\n- onSuccess: (updatedChat, { chatId }) => {\\n+ onSuccess: (result, { chatId }) => {\\n+ const { data: updatedChat, timestamp } = result;\\n+\\n+ // Check if this response is still the most recent one\\n+ const currentTimestamp =\\n+ queryClient.getQueryData([\\\"chatRequestTimestamp\\\", chatId]) || 0;\\n+ if (timestamp < currentTimestamp) {\\n+ console.log(\\n+ \\\"[useUpdateChat:onSuccess] Ignoring outdated response for\\\",\\n+ chatId,\\n+ );\\n+ return;\\n+ }\\n+\\n queryClient.setQueryData([\\\"chat\\\", chatId], updatedChat);\\n queryClient.invalidateQueries({ queryKey: [\\\"chats\\\"] });\\n queryClient.invalidateQueries({ queryKey: [\\\"activeChats\\\"] });\\ndiff --git a/app/queries/notifications.js b/app/queries/notifications.js\\nnew file mode 100644\\nindex 0000000..4df777f\\n--- /dev/null\\n+++ b/app/queries/notifications.js\\n@@ -0,0 +1,129 @@\\n+import {\\n+ useQuery,\\n+ useMutation,\\n+ useQueryClient,\\n+ useInfiniteQuery,\\n+} from \\\"@tanstack/react-query\\\";\\n+import axios from \\\"../utils/axios-client\\\";\\n+import { useContext } from \\\"react\\\";\\n+import { AuthContext } from \\\"../../src/App\\\";\\n+\\n+export function useNotifications(showDismissed = false) {\\n+ const queryClient = useQueryClient();\\n+ const previousData = queryClient.getQueryData([\\n+ \\\"notifications\\\",\\n+ showDismissed,\\n+ ]);\\n+ const { refetchUserState } = useContext(AuthContext);\\n+\\n+ const invalidateNotifications = () => {\\n+ queryClient.invalidateQueries({ queryKey: [\\\"notifications\\\"] });\\n+ };\\n+\\n+ const query = useQuery({\\n+ queryKey: [\\\"notifications\\\", showDismissed],\\n+ queryFn: async () => {\\n+ const { data } = await axios.get(\\n+ `/api/request-progress?showDismissed=${showDismissed}`,\\n+ );\\n+\\n+ // Check if any notification has newly completed\\n+ if (previousData?.requests) {\\n+ const newlyCompleted = data.requests.some(\\n+ (notification) =>\\n+ notification.status === \\\"completed\\\" &&\\n+ previousData.requests.find(\\n+ (prev) =>\\n+ prev.requestId === notification.requestId &&\\n+ prev.status !== \\\"completed\\\",\\n+ ),\\n+ );\\n+\\n+ if (newlyCompleted) {\\n+ // Refetch user state when a notification completes\\n+ refetchUserState();\\n+ }\\n+ }\\n+\\n+ return data;\\n+ },\\n+ refetchInterval: (query) => {\\n+ const requests = query.state.data?.requests;\\n+ if (\\n+ requests?.some(\\n+ (notification) => notification.status === \\\"in_progress\\\",\\n+ )\\n+ ) {\\n+ return 5000;\\n+ } else {\\n+ return false;\\n+ }\\n+ },\\n+ refetchIntervalInBackground: true,\\n+ });\\n+\\n+ return { ...query, invalidateNotifications };\\n+}\\n+\\n+export function useDeleteNotification() {\\n+ const queryClient = useQueryClient();\\n+\\n+ return useMutation({\\n+ mutationFn: async (requestId) => {\\n+ const response = await axios.delete(\\\"/api/request-progress\\\", {\\n+ data: { requestId },\\n+ });\\n+ return response.data;\\n+ },\\n+ onSuccess: () => {\\n+ queryClient.invalidateQueries({ queryKey: [\\\"notifications\\\"] });\\n+ },\\n+ });\\n+}\\n+\\n+export function useDismissNotification() {\\n+ const queryClient = useQueryClient();\\n+\\n+ return useMutation({\\n+ mutationFn: async (requestId) => {\\n+ const response = await axios.patch(\\\"/api/request-progress\\\", {\\n+ requestId,\\n+ });\\n+ return response.data;\\n+ },\\n+ onSuccess: () => {\\n+ queryClient.invalidateQueries({ queryKey: [\\\"notifications\\\"] });\\n+ },\\n+ });\\n+}\\n+\\n+export function useCancelRequest() {\\n+ const queryClient = useQueryClient();\\n+\\n+ return useMutation({\\n+ mutationFn: async (requestId) => {\\n+ const response = await axios.post(\\\"/api/cancel-request\\\", {\\n+ requestId,\\n+ });\\n+ return response.data;\\n+ },\\n+ onSuccess: () => {\\n+ queryClient.invalidateQueries({ queryKey: [\\\"notifications\\\"] });\\n+ },\\n+ });\\n+}\\n+\\n+export function useInfiniteNotifications() {\\n+ return useInfiniteQuery({\\n+ queryKey: [\\\"notifications\\\", \\\"infinite\\\", true],\\n+ queryFn: async ({ pageParam = 1 }) => {\\n+ const response = await fetch(\\n+ `/api/request-progress?showDismissed=true&page=${pageParam}&limit=10`,\\n+ );\\n+ return response.json();\\n+ },\\n+ getNextPageParam: (lastPage, pages) => {\\n+ return lastPage.hasMore ? pages.length + 1 : undefined;\\n+ },\\n+ });\\n+}\\ndiff --git a/app/queries/options.js b/app/queries/options.js\\nindex e2813e3..d25ff34 100644\\n--- a/app/queries/options.js\\n+++ b/app/queries/options.js\\n@@ -1,13 +1,7 @@\\n import { useMutation, useQueryClient } from \\\"@tanstack/react-query\\\";\\n import axios from \\\"../utils/axios-client\\\";\\n \\n-export function useUpdateAiOptions(\\n- userId,\\n- contextId,\\n- aiMemorySelfModify,\\n- aiName,\\n- aiStyle,\\n-) {\\n+export function useUpdateAiOptions() {\\n const queryClient = useQueryClient();\\n \\n const mutation = useMutation({\\n@@ -17,6 +11,7 @@ export function useUpdateAiOptions(\\n aiMemorySelfModify,\\n aiName,\\n aiStyle,\\n+ streamingEnabled,\\n }) => {\\n // persist it to user options in the database\\n const response = await axios.post(`/api/options`, {\\n@@ -25,6 +20,7 @@ export function useUpdateAiOptions(\\n aiMemorySelfModify,\\n aiName,\\n aiStyle,\\n+ streamingEnabled,\\n });\\n return response.data;\\n },\\n@@ -34,6 +30,7 @@ export function useUpdateAiOptions(\\n aiMemorySelfModify,\\n aiName,\\n aiStyle,\\n+ streamingEnabled,\\n }) => {\\n await queryClient.cancelQueries({ queryKey: [\\\"currentUser\\\"] });\\n const previousUser = await queryClient.getQueryData([\\n@@ -47,6 +44,7 @@ export function useUpdateAiOptions(\\n aiMemorySelfModify,\\n aiName,\\n aiStyle,\\n+ streamingEnabled,\\n };\\n });\\n \\ndiff --git a/app/utils/video-state-handler.js b/app/utils/video-state-handler.js\\nnew file mode 100644\\nindex 0000000..5e7070b\\n--- /dev/null\\n+++ b/app/utils/video-state-handler.js\\n@@ -0,0 +1,131 @@\\n+async function fetchVttContent(url) {\\n+ try {\\n+ const response = await fetch(url);\\n+ if (!response.ok) {\\n+ throw new Error(\\n+ `Failed to fetch VTT content: ${response.statusText}`,\\n+ );\\n+ }\\n+ return await response.text();\\n+ } catch (error) {\\n+ console.error(`Error fetching VTT content from ${url}:`, error);\\n+ throw error;\\n+ }\\n+}\\n+\\n+async function handleVideoTranslationCompletion(\\n+ userId,\\n+ dataObject,\\n+ targetLocaleLabel,\\n+) {\\n+ if (!userId || !dataObject) {\\n+ console.log(\\\"Missing required data for video state update\\\");\\n+ return;\\n+ }\\n+\\n+ try {\\n+ const UserState = (await import(\\\"../api/models/user-state.mjs\\\"))\\n+ .default;\\n+ const userState = await UserState.findOne({ user: userId });\\n+ if (!userState) {\\n+ console.log(\\\"User state not found\\\");\\n+ return;\\n+ }\\n+\\n+ let state = {};\\n+ try {\\n+ state = userState.serializedState\\n+ ? JSON.parse(userState.serializedState)\\n+ : {};\\n+ } catch (e) {\\n+ console.error(\\\"Error parsing serializedState:\\\", e);\\n+ state = {};\\n+ }\\n+\\n+ // Get the target locale and URLs from the data\\n+ const targetLocale = Object.keys(dataObject.targetLocales)[0];\\n+ const targetVideoUrl =\\n+ dataObject.targetLocales[targetLocale].outputVideoFileUrl;\\n+ const originalVttUrl = dataObject.outputVideoSubtitleWebVttFileUrl;\\n+ const translatedVttUrl =\\n+ dataObject.targetLocales[targetLocale]\\n+ .outputVideoSubtitleWebVttFileUrl;\\n+\\n+ // Update the transcribe state\\n+ const transcribeState = state.transcribe || {};\\n+ const videoInformation = transcribeState.videoInformation || {};\\n+\\n+ // Update video languages with new format including label\\n+ const videoLanguages = videoInformation.videoLanguages || [];\\n+ videoLanguages.push({\\n+ code: targetLocale,\\n+ url: targetVideoUrl,\\n+ });\\n+\\n+ // Update transcripts\\n+ const transcripts = transcribeState.transcripts || [];\\n+\\n+ // Try to add original subtitles if they don't exist\\n+ const autoSubtitlesExist = transcripts.some(\\n+ (transcript) => transcript.name === \\\"Original Subtitles\\\",\\n+ );\\n+\\n+ if (!autoSubtitlesExist && originalVttUrl) {\\n+ try {\\n+ const vttContent = await fetchVttContent(originalVttUrl);\\n+ transcripts.push({\\n+ url: originalVttUrl,\\n+ text: vttContent,\\n+ format: \\\"vtt\\\",\\n+ name: \\\"Original Subtitles\\\",\\n+ timestamp: new Date().toISOString(),\\n+ });\\n+ } catch (error) {\\n+ console.error(\\\"Failed to fetch original VTT content:\\\", error);\\n+ // Continue with translation even if original subtitles fail\\n+ }\\n+ }\\n+\\n+ // Try to add translated subtitles\\n+ if (translatedVttUrl) {\\n+ try {\\n+ const vttContent = await fetchVttContent(translatedVttUrl);\\n+ transcripts.push({\\n+ url: translatedVttUrl,\\n+ text: vttContent,\\n+ format: \\\"vtt\\\",\\n+ name: `${targetLocaleLabel || targetLocale} Subtitles`, // Frontend will handle proper language display\\n+ timestamp: new Date().toISOString(),\\n+ });\\n+ } catch (error) {\\n+ console.error(\\\"Failed to fetch translated VTT content:\\\", error);\\n+ }\\n+ }\\n+\\n+ // Update the state\\n+ state.transcribe = {\\n+ ...transcribeState,\\n+ videoInformation: {\\n+ ...videoInformation,\\n+ videoLanguages,\\n+ },\\n+ transcripts,\\n+ };\\n+\\n+ // Save the updated state\\n+ await UserState.findOneAndUpdate(\\n+ { user: userId },\\n+ { serializedState: JSON.stringify(state) },\\n+ );\\n+ console.log(\\n+ \\\"User state updated successfully with new video languages and transcripts\\\",\\n+ );\\n+ } catch (error) {\\n+ console.error(\\\"Error updating user state:\\\", error);\\n+ throw error;\\n+ }\\n+}\\n+\\n+module.exports = {\\n+ handleVideoTranslationCompletion,\\n+};\\ndiff --git a/app/workspaces/components/WorkspaceOutputs.js b/app/workspaces/components/WorkspaceOutputs.js\\nindex 64c2492..d1b4759 100644\\n--- a/app/workspaces/components/WorkspaceOutputs.js\\n+++ b/app/workspaces/components/WorkspaceOutputs.js\\n@@ -2,10 +2,11 @@ import { useTranslation } from \\\"react-i18next\\\";\\n import ReactTimeAgo from \\\"react-time-ago\\\";\\n import CopyButton from \\\"../../../src/components/CopyButton\\\";\\n import { convertMessageToMarkdown } from \\\"../../../src/components/chat/ChatMessage\\\";\\n+import OutputSandbox from \\\"../../../src/components/sandbox/OutputSandbox\\\";\\n \\n export default function WorkspaceOutputs({ outputs = [], onDelete }) {\\n return (\\n-
\\n+
\\n {outputs.map((output) => (\\n \\n ))}\\n@@ -16,34 +17,54 @@ export default function WorkspaceOutputs({ outputs = [], onDelete }) {\\n function Output({ output, onDelete }) {\\n const { t } = useTranslation();\\n \\n+ // Check if the output is HTML content\\n+ const isHtmlContent =\\n+ output.output.trim().startsWith(\\\"\\\") ||\\n+ output.output.trim().startsWith(\\\"\\\") ||\\n+ (output.tool && JSON.parse(output.tool)?.isHtml);\\n+\\n return (\\n
\\n-
{output.title}
\\n-
\\n- \\n- {convertMessageToMarkdown({ payload: output.output })}\\n-
\\n-
\\n-
\\n- {t(\\\"Generated\\\")}{\\\" \\\"}\\n- \\n+
{output.title}
\\n+
\\n+
\\n+ \\n+
\\n+ {isHtmlContent ? (\\n+ \\n+ ) : (\\n+
\\n+ {convertMessageToMarkdown({\\n+ payload: output.output,\\n+ tool: output.tool,\\n+ })}\\n+
\\n+ )}\\n+
\\n+
\\n+ {t(\\\"Generated\\\")}{\\\" \\\"}\\n+ \\n+
\\n+ {\\n+ if (\\n+ window.confirm(\\n+ t(\\n+ \\\"Are you sure you want to delete this output?\\\",\\n+ ),\\n+ )\\n+ ) {\\n+ onDelete(output._id);\\n+ }\\n+ }}\\n+ className=\\\"text-gray-400 hover:text-gray-600\\\"\\n+ >\\n+ {t(\\\"Delete\\\")}\\n+ \\n
\\n- {\\n- if (\\n- window.confirm(\\n- t(\\n- \\\"Are you sure you want to delete this output?\\\",\\n- ),\\n- )\\n- ) {\\n- onDelete(output._id);\\n- }\\n- }}\\n- className=\\\"text-gray-300 hover:text-gray-500\\\"\\n- >\\n- {t(\\\"Delete\\\")}\\n- \\n
\\n
\\n );\\ndiff --git a/config/default/config/data/taxonomySets.js b/config/default/config/data/taxonomySets.js\\nindex 3ffb97c..ffaf82b 100644\\n--- a/config/default/config/data/taxonomySets.js\\n+++ b/config/default/config/data/taxonomySets.js\\n@@ -15,7 +15,7 @@ const taxonomySets = taxonomySetsContext\\n // will include the same file with two paths:\\n // ./filename.json and /filename.json\\n if (dedupedFileNames.includes(filenameOnly)) {\\n- return;\\n+ return null;\\n }\\n \\n const setName = filename.slice(2, -5); // Remove './' and '.json' from the file name\\ndiff --git a/config/default/config/index.js b/config/default/config/index.js\\nindex e4c0d5c..2f41b13 100644\\n--- a/config/default/config/index.js\\n+++ b/config/default/config/index.js\\n@@ -19,7 +19,7 @@ const LLM_IDENTIFIERS = {\\n claude35sonnet: \\\"claude35sonnet\\\",\\n claude3opus: \\\"claude3opus\\\",\\n o1: \\\"o1\\\",\\n- o1mini: \\\"o1mini\\\",\\n+ o3mini: \\\"o3mini\\\",\\n };\\n \\n // eslint-disable-next-line import/no-anonymous-default-export\\n@@ -93,10 +93,10 @@ export default {\\n cortexModelName: \\\"oai-o1\\\",\\n },\\n {\\n- identifier: LLM_IDENTIFIERS.o1mini,\\n- name: \\\"o1 Mini\\\",\\n- cortexPathwayName: \\\"run_o1_mini\\\",\\n- cortexModelName: \\\"oai-o1-mini\\\",\\n+ identifier: LLM_IDENTIFIERS.o3mini,\\n+ name: \\\"o3 Mini\\\",\\n+ cortexPathwayName: \\\"run_o3_mini\\\",\\n+ cortexModelName: \\\"oai-o3-mini\\\",\\n },\\n ],\\n },\\ndiff --git a/config/default/locales/ar.json b/config/default/locales/ar.json\\nindex 99aad35..e3a8cb0 100644\\n--- a/config/default/locales/ar.json\\n+++ b/config/default/locales/ar.json\\n@@ -463,5 +463,31 @@\\n \\\"Download .txt\\\": \\\"تنزيل بتنسيق TXT\\\",\\n \\\"Taxonomy\\\": \\\"التصنيف\\\",\\n \\\"Transcript\\\": \\\"النص المنسوخ\\\",\\n- \\\"{{name}}: {{language}} Translation\\\": \\\"{{name}}: ترجمة {{language}}\\\"\\n+ \\\"{{name}}: {{language}} Translation\\\": \\\"{{name}}: ترجمة {{language}}\\\",\\n+ \\\"Processing media...\\\": \\\"جاري معالجة الوسائط...\\\",\\n+ \\\"Transcription type\\\": \\\"نوع التنسيق\\\",\\n+ \\\"{{from}} to {{to}}\\\": \\\"{{from}} إلى {{to}}\\\",\\n+ \\\"Video translation\\\": \\\"ترجمة الفيديو\\\",\\n+ \\\"In progress\\\": \\\"قيد التنفيذ\\\",\\n+ \\\"Completed\\\": \\\"منجز\\\",\\n+ \\\"Failed\\\": \\\"فشل\\\",\\n+ \\\"View all\\\": \\\"عرض الكل\\\",\\n+ \\\"No recent or active notifications\\\": \\\"لا يوجد إشعارات مفعلة\\\",\\n+ \\\"View history\\\": \\\"عرض التاريخ\\\",\\n+ \\\"All notifications\\\": \\\"جميع الإشعارات\\\",\\n+ \\\"Enable streaming responses\\\": \\\"تفعيل الاستجابات المنسية\\\",\\n+ \\\"Memory backup\\\": \\\"نسخة احتياطية للذاكرة\\\",\\n+ \\\"Download memory backup\\\": \\\"تنزيل نسخة احتياطية للذاكرة\\\",\\n+ \\\"Upload memory from backup\\\": \\\"تحميل الذاكرة من النسخة الاحتياطية\\\",\\n+ \\\"Failed to read the file. Please try again.\\\": \\\"فشل في قراءة الملف. يرجى المحاولة مرة أخرى.\\\",\\n+ \\\"Failed to parse memory file. Please ensure it is a valid JSON file with the correct memory structure.\\\": \\\"فشل في تحليل ملف الذاكرة. يرجى التأكد من أنه ملف JSON صالح بهيكل الذاكرة الصحيح.\\\",\\n+ \\\"Invalid memory file format\\\": \\\"تنسيق ملف الذاكرة غير صالح\\\",\\n+ \\\"Add audio track\\\": \\\"إضافة صوت\\\",\\n+ \\\"Transcript not looking right?\\\": \\\"النص المنسوخ لا يبدو صحيحًا؟\\\",\\n+ \\\"Transcribe again using an alternate model\\\": \\\"تنسيق مرة أخرى باستخدام نموذج مختلف\\\",\\n+ \\\"Re-transcribing\\\": \\\"إعادة التنسيق\\\",\\n+ \\\"Transcribing... This may take a few minutes.\\\": \\\"جاري التنسيق... قد يستغرق هذا بضع دقائق.\\\",\\n+ \\\"Auto-transcribing\\\": \\\"تنسيق تلقائي\\\",\\n+ \\\"Edit title\\\": \\\"تعديل العنوان\\\",\\n+ \\\"Delete chat\\\": \\\"حذف الدردشة\\\"\\n }\\ndiff --git a/jobs/digest/digest.utils.js b/jobs/digest/digest.utils.js\\nindex 2f304af..d73592f 100644\\n--- a/jobs/digest/digest.utils.js\\n+++ b/jobs/digest/digest.utils.js\\n@@ -1,6 +1,5 @@\\n const APPROXIMATE_DURATION_SECONDS = 60;\\n const PROGRESS_UPDATE_INTERVAL = 3000;\\n-const { processImageUrls } = require(\\\"../../src/utils/imageUtils\\\");\\n \\n const generateDigestBlockContent = async (\\n block,\\n@@ -8,6 +7,9 @@ const generateDigestBlockContent = async (\\n logger,\\n onProgressUpdate,\\n ) => {\\n+ let imageUtils = await import(\\\"../../src/utils/imageUtils.mjs\\\");\\n+ const { processImageUrls } = imageUtils;\\n+\\n let graphql = await import(\\\"../graphql.mjs\\\");\\n const { QUERIES, getClient } = graphql;\\n const { prompt } = block;\\n@@ -36,13 +38,13 @@ const generateDigestBlockContent = async (\\n \\n try {\\n const result = await client.query({\\n- query: QUERIES.RAG_START,\\n+ query: QUERIES.SYS_ENTITY_START,\\n variables,\\n });\\n \\n- tool = result.data.rag_start.tool;\\n+ tool = result.data.sys_entity_start.tool;\\n if (tool) {\\n- const toolObj = JSON.parse(result.data.rag_start.tool);\\n+ const toolObj = JSON.parse(result.data.sys_entity_start.tool);\\n toolCallbackName = toolObj?.toolCallbackName;\\n }\\n \\n@@ -67,14 +69,14 @@ const generateDigestBlockContent = async (\\n try {\\n content = JSON.stringify({\\n payload: await processImageUrls(\\n- JSON.parse(result.data.rag_start.result).response,\\n+ JSON.parse(result.data.sys_entity_start.result),\\n process.env.SERVER_URL,\\n ),\\n tool,\\n });\\n } catch (e) {\\n logger.error(\\n- `Error while parsing rag_start result: ${e.message}`,\\n+ `Error while parsing sys_entity_start result: ${e.message}`,\\n user?._id,\\n block?._id,\\n );\\ndiff --git a/jobs/graphql.mjs b/jobs/graphql.mjs\\nindex b95daac..6ea5952 100644\\n--- a/jobs/graphql.mjs\\n+++ b/jobs/graphql.mjs\\n@@ -115,7 +115,7 @@ const SYS_SAVE_MEMORY = gql`\\n }\\n `;\\n \\n-const RAG_START = gql`\\n+const SYS_ENTITY_START = gql`\\n query RagStart(\\n $chatHistory: [MultiMessage]!\\n $dataSources: [String]\\n@@ -129,7 +129,7 @@ const RAG_START = gql`\\n $title: String\\n $aiStyle: String\\n ) {\\n- rag_start(\\n+ sys_entity_start(\\n chatHistory: $chatHistory\\n dataSources: $dataSources\\n contextId: $contextId\\n@@ -493,6 +493,8 @@ const REQUEST_PROGRESS = gql`\\n requestProgress(requestIds: $requestIds) {\\n data\\n progress\\n+ info\\n+ error\\n }\\n }\\n `;\\n@@ -631,7 +633,7 @@ const QUERIES = {\\n COGNITIVE_INSERT,\\n IMAGE,\\n SYS_SAVE_MEMORY,\\n- RAG_START,\\n+ SYS_ENTITY_START,\\n SYS_ENTITY_CONTINUE,\\n EXPAND_STORY,\\n FORMAT_PARAGRAPH_TURBO,\\n@@ -687,7 +689,7 @@ export {\\n COGNITIVE_DELETE,\\n EXPAND_STORY,\\n SYS_SAVE_MEMORY,\\n- RAG_START,\\n+ SYS_ENTITY_START,\\n SYS_ENTITY_CONTINUE,\\n SELECT_SERVICES,\\n SUMMARY,\\ndiff --git a/jobs/request-progress-worker.js b/jobs/request-progress-worker.js\\nnew file mode 100644\\nindex 0000000..095cd0c\\n--- /dev/null\\n+++ b/jobs/request-progress-worker.js\\n@@ -0,0 +1,588 @@\\n+const { Worker } = require(\\\"bullmq\\\");\\n+const Redis = require(\\\"ioredis\\\");\\n+const {\\n+ ApolloClient,\\n+ InMemoryCache,\\n+ split,\\n+ HttpLink,\\n+ gql,\\n+} = require(\\\"@apollo/client\\\");\\n+const { GraphQLWsLink } = require(\\\"@apollo/client/link/subscriptions\\\");\\n+const { getMainDefinition } = require(\\\"@apollo/client/utilities\\\");\\n+const { createClient } = require(\\\"graphql-ws\\\");\\n+const WebSocket = require(\\\"ws\\\");\\n+\\n+const REQUEST_PROGRESS_SUBSCRIPTION = gql`\\n+ subscription RequestProgress($requestIds: [String!]!) {\\n+ requestProgress(requestIds: $requestIds) {\\n+ progress\\n+ data\\n+ info\\n+ error\\n+ }\\n+ }\\n+`;\\n+\\n+const graphqlEndpoint =\\n+ process.env.CORTEX_GRAPHQL_API_URL || \\\"http://localhost:4000/graphql\\\";\\n+\\n+const connection = new Redis(\\n+ process.env.REDIS_CONNECTION_STRING || \\\"redis://localhost:6379\\\",\\n+ {\\n+ maxRetriesPerRequest: null,\\n+ },\\n+);\\n+\\n+const httpLink = new HttpLink({\\n+ uri: graphqlEndpoint,\\n+});\\n+\\n+const wsLink = new GraphQLWsLink(\\n+ createClient({\\n+ url: graphqlEndpoint.replace(\\\"http\\\", \\\"ws\\\"),\\n+ webSocketImpl: WebSocket,\\n+ }),\\n+);\\n+\\n+const splitLink = split(\\n+ ({ query }) => {\\n+ const definition = getMainDefinition(query);\\n+ return (\\n+ definition.kind === \\\"OperationDefinition\\\" &&\\n+ definition.operation === \\\"subscription\\\"\\n+ );\\n+ },\\n+ wsLink,\\n+ httpLink,\\n+);\\n+\\n+const client = new ApolloClient({\\n+ link: splitLink,\\n+ cache: new InMemoryCache(),\\n+});\\n+\\n+// Add a helper function for DB operations with retries\\n+async function retryDbOperation(operation, maxRetries = 3, retryDelay = 1000) {\\n+ let lastError;\\n+ for (let attempt = 1; attempt <= maxRetries; attempt++) {\\n+ try {\\n+ return await operation();\\n+ } catch (error) {\\n+ lastError = error;\\n+ console.warn(\\n+ `DB operation attempt ${attempt}/${maxRetries} failed: ${error.message}`,\\n+ );\\n+\\n+ // Check explicitly for MongoNotConnectedError and other connection issues\\n+ if (\\n+ error.name === \\\"MongoNotConnectedError\\\" ||\\n+ ((error.name === \\\"MongooseError\\\" ||\\n+ error.name === \\\"MongoError\\\") &&\\n+ error.message &&\\n+ (error.message.includes(\\\"buffering\\\") ||\\n+ error.message.includes(\\\"disconnected\\\") ||\\n+ error.message.includes(\\\"timeout\\\") ||\\n+ error.message.includes(\\\"not connected\\\") ||\\n+ error.message.includes(\\\"must be connected\\\")))\\n+ ) {\\n+ console.log(\\n+ \\\"Detected MongoDB connection issue, attempting to reconnect...\\\",\\n+ );\\n+ // Use the global mongoose instance to check connection state\\n+ const mongoose = (await import(\\\"mongoose\\\")).default;\\n+ if (mongoose.connection.readyState !== 1) {\\n+ try {\\n+ // First try to close any existing connection\\n+ if (mongoose.connection.readyState !== 0) {\\n+ await mongoose.connection\\n+ .close()\\n+ .catch((err) =>\\n+ console.warn(\\n+ \\\"Error closing existing connection:\\\",\\n+ err.message,\\n+ ),\\n+ );\\n+ }\\n+\\n+ // Get a fresh database connection\\n+ const { connectToDatabase } = await import(\\n+ \\\"../src/db.mjs\\\"\\n+ );\\n+ await connectToDatabase();\\n+ console.log(\\\"Successfully reconnected to MongoDB\\\");\\n+\\n+ // Reset the dbInitialized flag to ensure ensureDbConnection will work properly\\n+ dbInitialized = mongoose.connection.readyState === 1;\\n+ } catch (reconnectError) {\\n+ console.error(\\n+ \\\"Failed to reconnect to MongoDB:\\\",\\n+ reconnectError.message,\\n+ );\\n+ }\\n+ }\\n+ }\\n+\\n+ if (attempt < maxRetries) {\\n+ const waitTime = Math.min(retryDelay, 30000); // Cap at 30 seconds max\\n+ console.log(\\n+ `Waiting ${waitTime / 1000}s before retry ${attempt + 1}/${maxRetries}...`,\\n+ );\\n+ await new Promise((resolve) => setTimeout(resolve, waitTime));\\n+ // Increase delay for next retry (exponential backoff)\\n+ retryDelay *= 2;\\n+ }\\n+ }\\n+ }\\n+ throw lastError;\\n+}\\n+\\n+// Ensure the worker has a database connection before starting operations\\n+let dbInitialized = false;\\n+let connectionAttempts = 0;\\n+const MAX_CONNECTION_ATTEMPTS = 5;\\n+\\n+async function ensureDbConnection(forceReconnect = false) {\\n+ if (forceReconnect) {\\n+ dbInitialized = false;\\n+ }\\n+\\n+ if (!dbInitialized) {\\n+ try {\\n+ // Use the global mongoose instance directly\\n+ const mongoose = (await import(\\\"mongoose\\\")).default;\\n+\\n+ // Check if already connected\\n+ if (mongoose.connection && mongoose.connection.readyState === 1) {\\n+ console.log(\\\"Already connected to MongoDB\\\");\\n+ dbInitialized = true;\\n+ connectionAttempts = 0;\\n+ return;\\n+ }\\n+\\n+ // If previous connection exists but is disconnected, close it\\n+ if (mongoose.connection && mongoose.connection.readyState !== 0) {\\n+ console.log(\\n+ \\\"Closing existing MongoDB connection before reconnecting...\\\",\\n+ );\\n+ await mongoose.connection\\n+ .close()\\n+ .catch((err) =>\\n+ console.warn(\\\"Error closing connection:\\\", err.message),\\n+ );\\n+ }\\n+\\n+ connectionAttempts++;\\n+ console.log(\\n+ `Connecting to MongoDB (attempt ${connectionAttempts}/${MAX_CONNECTION_ATTEMPTS})...`,\\n+ );\\n+\\n+ const { connectToDatabase } = await import(\\\"../src/db.mjs\\\");\\n+ await connectToDatabase();\\n+\\n+ // Wait a moment to ensure the connection is established\\n+ await new Promise((resolve) => setTimeout(resolve, 500));\\n+\\n+ // Verify the connection was successful\\n+ if (mongoose.connection && mongoose.connection.readyState === 1) {\\n+ console.log(\\n+ \\\"Worker successfully connected to MongoDB database\\\",\\n+ );\\n+ dbInitialized = true;\\n+ connectionAttempts = 0;\\n+ } else {\\n+ throw new Error(\\n+ `Failed to establish MongoDB connection, current state: ${mongoose.connection ? mongoose.connection.readyState : \\\"unknown\\\"}`,\\n+ );\\n+ }\\n+ } catch (error) {\\n+ console.error(\\n+ `Failed to connect to database (attempt ${connectionAttempts}/${MAX_CONNECTION_ATTEMPTS}):`,\\n+ error,\\n+ );\\n+\\n+ if (connectionAttempts >= MAX_CONNECTION_ATTEMPTS) {\\n+ console.error(\\n+ \\\"Maximum connection attempts reached. Giving up.\\\",\\n+ );\\n+ throw new Error(\\n+ `Failed to connect to MongoDB after ${MAX_CONNECTION_ATTEMPTS} attempts: ${error.message}`,\\n+ );\\n+ }\\n+\\n+ // Wait before next attempt with exponential backoff\\n+ const backoffTime = Math.min(\\n+ 1000 * Math.pow(2, connectionAttempts),\\n+ 30000,\\n+ );\\n+ console.log(\\n+ `Waiting ${backoffTime / 1000}s before next connection attempt...`,\\n+ );\\n+ await new Promise((resolve) => setTimeout(resolve, backoffTime));\\n+\\n+ // Recursive call to retry\\n+ return ensureDbConnection();\\n+ }\\n+ }\\n+}\\n+\\n+const worker = new Worker(\\n+ \\\"request-progress\\\",\\n+ async (job) => {\\n+ const { requestId, type, userId, metadata } = job.data;\\n+ const { targetLocaleLabel } = metadata;\\n+ console.log(\\n+ `Starting progress tracking job ${job.id} for ${type} requestId: ${requestId}. userId: ${userId}`,\\n+ );\\n+\\n+ // Ensure DB connection is established\\n+ await ensureDbConnection();\\n+\\n+ const RequestProgress = (\\n+ await import(\\\"../app/api/models/request-progress.mjs\\\")\\n+ ).default;\\n+\\n+ // Check if already cancelled\\n+ const request = await retryDbOperation(() =>\\n+ RequestProgress.findOne({ requestId }),\\n+ );\\n+\\n+ if (request?.status === \\\"cancelled\\\") {\\n+ console.log(`Job ${job.id} was cancelled`);\\n+ return;\\n+ }\\n+\\n+ return new Promise((resolve, reject) => {\\n+ try {\\n+ let timeoutId;\\n+ let subscription;\\n+\\n+ // Add a periodic check for cancellation\\n+ const cancellationCheckInterval = setInterval(async () => {\\n+ try {\\n+ const updatedRequest = await retryDbOperation(() =>\\n+ RequestProgress.findOne({ requestId }),\\n+ );\\n+\\n+ if (updatedRequest?.status === \\\"cancelled\\\") {\\n+ console.log(`Job ${job.id} received cancellation`);\\n+ clearTimeout(timeoutId);\\n+ clearInterval(cancellationCheckInterval);\\n+ subscription?.unsubscribe();\\n+ resolve(); // Resolve without error since this is an expected cancellation\\n+ return;\\n+ }\\n+ } catch (error) {\\n+ console.error(\\\"Error in cancellation check:\\\", error);\\n+ // Don't terminate the job on cancellation check errors\\n+ }\\n+ }, 5000);\\n+\\n+ const resetIdleTimeout = () => {\\n+ clearTimeout(timeoutId);\\n+ timeoutId = setTimeout(\\n+ () => {\\n+ console.warn(\\n+ `Job ${job.id} timed out after 5 minutes of inactivity`,\\n+ );\\n+ subscription?.unsubscribe();\\n+ retryDbOperation(() =>\\n+ RequestProgress.findOneAndUpdate(\\n+ { requestId },\\n+ {\\n+ status: \\\"failed\\\",\\n+ error: \\\"Operation timed out after 5 minutes of inactivity\\\",\\n+ },\\n+ ).exec(),\\n+ ).catch((err) =>\\n+ console.error(\\n+ \\\"Error updating progress on timeout:\\\",\\n+ err,\\n+ ),\\n+ );\\n+ reject(\\n+ new Error(\\n+ \\\"Operation timed out after 5 minutes of inactivity\\\",\\n+ ),\\n+ );\\n+ },\\n+ 5 * 60 * 1000,\\n+ );\\n+ };\\n+\\n+ // Start initial idle timeout\\n+ resetIdleTimeout();\\n+\\n+ subscription = client\\n+ .subscribe({\\n+ query: REQUEST_PROGRESS_SUBSCRIPTION,\\n+ variables: { requestIds: [requestId] },\\n+ })\\n+ .subscribe({\\n+ async next(x) {\\n+ try {\\n+ // Check for cancellation before processing updates\\n+ const currentRequest = await retryDbOperation(\\n+ () =>\\n+ RequestProgress.findOne({ requestId }),\\n+ );\\n+\\n+ if (currentRequest?.status === \\\"cancelled\\\") {\\n+ console.log(\\n+ `Job ${job.id} was cancelled during processing`,\\n+ );\\n+ clearTimeout(timeoutId);\\n+ clearInterval(cancellationCheckInterval);\\n+ subscription.unsubscribe();\\n+ resolve();\\n+ return;\\n+ }\\n+\\n+ const { data } = x;\\n+ // Reset idle timeout on each progress update\\n+ resetIdleTimeout();\\n+\\n+ let progress =\\n+ data?.requestProgress?.progress || 0;\\n+\\n+ // Check current progress and keep higher value\\n+ const currentDoc = await retryDbOperation(() =>\\n+ RequestProgress.findOne({ requestId }),\\n+ );\\n+\\n+ if (\\n+ currentDoc &&\\n+ progress < currentDoc.progress\\n+ ) {\\n+ console.log(\\n+ `Job ${job.id} maintaining higher progress value ${currentDoc.progress} instead of ${progress}`,\\n+ );\\n+ progress = currentDoc.progress;\\n+ }\\n+\\n+ let dataObject;\\n+\\n+ if (data?.requestProgress?.data) {\\n+ try {\\n+ dataObject = JSON.parse(\\n+ JSON.parse(\\n+ data?.requestProgress?.data,\\n+ ),\\n+ );\\n+ } catch (e) {\\n+ console.log(\\n+ \\\"Non-json data\\\",\\n+ data?.requestProgress?.data,\\n+ );\\n+ }\\n+ }\\n+\\n+ // Check for error field directly\\n+ if (data?.requestProgress?.error) {\\n+ const error = data.requestProgress.error;\\n+ console.error(\\n+ \\\"Error in request progress worker\\\",\\n+ error,\\n+ );\\n+\\n+ await retryDbOperation(() =>\\n+ RequestProgress.findOneAndUpdate(\\n+ {\\n+ requestId,\\n+ },\\n+ {\\n+ status: \\\"failed\\\",\\n+ statusText: error,\\n+ },\\n+ ),\\n+ );\\n+\\n+ resolve(dataObject);\\n+ return;\\n+ }\\n+\\n+ // Update progress in database\\n+ await retryDbOperation(() =>\\n+ RequestProgress.findOneAndUpdate(\\n+ { requestId },\\n+ {\\n+ progress,\\n+ statusText:\\n+ data?.requestProgress?.info,\\n+ data: dataObject,\\n+ status: \\\"in_progress\\\",\\n+ metadata: job.data.metadata,\\n+ },\\n+ ),\\n+ );\\n+\\n+ if (progress === 1) {\\n+ console.log(\\n+ `Job ${job.id} reached 100% completion`,\\n+ );\\n+\\n+ // If there's an error at 100%, mark as failed\\n+ if (data?.requestProgress?.error) {\\n+ console.error(\\n+ \\\"Error at 100% completion:\\\",\\n+ data.requestProgress.error,\\n+ );\\n+\\n+ await retryDbOperation(() =>\\n+ RequestProgress.findOneAndUpdate(\\n+ { requestId },\\n+ {\\n+ status: \\\"failed\\\",\\n+ statusText:\\n+ data.requestProgress\\n+ .error,\\n+ },\\n+ ),\\n+ );\\n+ }\\n+ // If we have data, mark as completed with data\\n+ else if (dataObject) {\\n+ // Handle video translation completion if needed\\n+ if (\\n+ type === \\\"video-translate\\\" &&\\n+ userId\\n+ ) {\\n+ try {\\n+ const {\\n+ handleVideoTranslationCompletion,\\n+ } = await import(\\n+ \\\"../app/utils/video-state-handler.js\\\"\\n+ );\\n+ await handleVideoTranslationCompletion(\\n+ userId,\\n+ dataObject,\\n+ targetLocaleLabel,\\n+ );\\n+ } catch (error) {\\n+ console.error(\\n+ \\\"Error handling video translation completion:\\\",\\n+ error,\\n+ );\\n+ }\\n+ }\\n+\\n+ await retryDbOperation(() =>\\n+ RequestProgress.findOneAndUpdate(\\n+ { requestId },\\n+ { status: \\\"completed\\\" },\\n+ ),\\n+ );\\n+ }\\n+ // Just mark as completed if we only have progress = 1\\n+ else {\\n+ await retryDbOperation(() =>\\n+ RequestProgress.findOneAndUpdate(\\n+ { requestId },\\n+ { status: \\\"completed\\\" },\\n+ ),\\n+ );\\n+ }\\n+\\n+ clearTimeout(timeoutId);\\n+ subscription.unsubscribe();\\n+ resolve(dataObject);\\n+ return;\\n+ }\\n+\\n+ job.updateProgress(progress);\\n+ } catch (error) {\\n+ console.error(\\n+ \\\"Error in subscription next handler:\\\",\\n+ error,\\n+ );\\n+ // Don't fail the job on a single update error\\n+ }\\n+ },\\n+ async error(error) {\\n+ console.error(\\n+ `Job ${job.id} subscription error:`,\\n+ error,\\n+ );\\n+ clearTimeout(timeoutId);\\n+ clearInterval(cancellationCheckInterval);\\n+ subscription.unsubscribe();\\n+ try {\\n+ await retryDbOperation(() =>\\n+ RequestProgress.findOneAndUpdate(\\n+ { requestId },\\n+ {\\n+ status: \\\"failed\\\",\\n+ statusText:\\n+ error.message ||\\n+ error.toString(),\\n+ },\\n+ ),\\n+ );\\n+ } catch (dbError) {\\n+ console.error(\\n+ \\\"Failed to update status on error:\\\",\\n+ dbError,\\n+ );\\n+ }\\n+ reject(error);\\n+ },\\n+ });\\n+ } catch (error) {\\n+ console.error(\\n+ `Failed to setup subscription for job ${job.id}:`,\\n+ error,\\n+ );\\n+ retryDbOperation(() =>\\n+ RequestProgress.findOneAndUpdate(\\n+ { requestId },\\n+ { status: \\\"failed\\\", error: error.message },\\n+ ).exec(),\\n+ ).catch((err) =>\\n+ console.error(\\n+ \\\"Error updating progress on setup failure:\\\",\\n+ err,\\n+ ),\\n+ );\\n+\\n+ reject(error);\\n+ }\\n+ });\\n+ },\\n+ {\\n+ connection,\\n+ autorun: false,\\n+ concurrency: 5,\\n+ stalledInterval: 300000, // 5 minutes in milliseconds\\n+ },\\n+);\\n+\\n+worker.on(\\\"completed\\\", (job, result) => {\\n+ console.log(`Job ${job.id} completed with result:`, result);\\n+});\\n+\\n+worker.on(\\\"failed\\\", (job, error) => {\\n+ console.error(`Job ${job.id} failed with error:`, error);\\n+});\\n+\\n+// Safely start the worker after ensuring database connection\\n+async function safelyStartWorker() {\\n+ try {\\n+ console.log(\\\"Ensuring database connection before starting worker...\\\");\\n+ await ensureDbConnection();\\n+\\n+ console.log(\\\"Starting request-progress worker...\\\");\\n+ worker.run();\\n+\\n+ console.log(\\\"Request-progress worker is now running\\\");\\n+ } catch (error) {\\n+ console.error(\\\"Failed to start worker:\\\", error);\\n+\\n+ // Try to restart after a delay if something goes wrong at startup\\n+ console.log(\\\"Will attempt to restart worker in 10 seconds...\\\");\\n+ setTimeout(safelyStartWorker, 10000);\\n+ }\\n+}\\n+\\n+// Export the safelyStartWorker function instead of the raw worker.run\\n+module.exports = {\\n+ run: safelyStartWorker,\\n+};\\ndiff --git a/jobs/worker.js b/jobs/worker.js\\nindex 7403a1b..a142f6c 100644\\n--- a/jobs/worker.js\\n+++ b/jobs/worker.js\\n@@ -9,6 +9,7 @@ const queueName = \\\"digest-build\\\";\\n const { REDIS_CONNECTION_STRING } = process.env;\\n const { Logger } = require(\\\"./logger.js\\\");\\n const { DIGEST_REBUILD_INTERVAL_HOURS = 4 } = process.env;\\n+const requestProgressWorker = require(\\\"./request-progress-worker\\\");\\n \\n const connection = new Redis(\\n REDIS_CONNECTION_STRING || \\\"redis://localhost:6379\\\",\\n@@ -81,29 +82,129 @@ worker.on(\\\"completed\\\", (job) => {\\n logger.log(\\\"job completed\\\");\\n });\\n \\n-worker.on(\\\"failed\\\", (job, err) => {\\n+worker.on(\\\"failed\\\", (job, error) => {\\n const logger = new Logger(job);\\n- logger.log(\\\"job failed\\\", err.message);\\n+ logger.log(\\\"job failed with error: \\\" + error.message);\\n });\\n \\n-worker.on(\\\"error\\\", (err) => {\\n- const logger = new Logger();\\n- logger.log(\\\"worker error\\\", err.message);\\n-});\\n+// Shared database connection management\\n+let dbInitialized = false;\\n+let connectionAttempts = 0;\\n+const MAX_CONNECTION_ATTEMPTS = 5;\\n \\n-console.log(\\\"starting worker\\\");\\n+// Ensure we have a database connection\\n+async function ensureDbConnection(forceReconnect = false) {\\n+ if (forceReconnect) {\\n+ dbInitialized = false;\\n+ }\\n \\n-(async () => {\\n- const connectToDatabase = (await import(\\\"../src/db.mjs\\\")).connectToDatabase;\\n- const closeDatabaseConnection = (await import(\\\"../src/db.mjs\\\"))\\n- .closeDatabaseConnection;\\n+ if (!dbInitialized) {\\n+ try {\\n+ connectionAttempts++;\\n+ console.log(\\n+ `Connecting to database (attempt ${connectionAttempts}/${MAX_CONNECTION_ATTEMPTS})...`,\\n+ );\\n \\n- console.log(\\n- \\\"Connecting to database\\\",\\n- connectToDatabase,\\n- closeDatabaseConnection,\\n- );\\n- await connectToDatabase();\\n- console.log(\\\"Connected to database\\\");\\n-})();\\n-worker.run();\\n+ const connectToDatabase = (await import(\\\"../src/db.mjs\\\"))\\n+ .connectToDatabase;\\n+ await connectToDatabase();\\n+\\n+ // Give the connection a moment to fully establish\\n+ await new Promise((resolve) => setTimeout(resolve, 500));\\n+\\n+ // Get mongoose to check connection state\\n+ const mongoose = (await import(\\\"mongoose\\\")).default;\\n+\\n+ if (mongoose.connection && mongoose.connection.readyState === 1) {\\n+ console.log(\\\"Successfully connected to MongoDB database\\\");\\n+ dbInitialized = true;\\n+ connectionAttempts = 0;\\n+ } else {\\n+ throw new Error(\\n+ `Failed to establish MongoDB connection, current state: ${mongoose.connection ? mongoose.connection.readyState : \\\"unknown\\\"}`,\\n+ );\\n+ }\\n+ } catch (error) {\\n+ console.error(\\n+ `Failed to connect to database (attempt ${connectionAttempts}/${MAX_CONNECTION_ATTEMPTS}):`,\\n+ error,\\n+ );\\n+\\n+ if (connectionAttempts >= MAX_CONNECTION_ATTEMPTS) {\\n+ console.error(\\n+ \\\"Maximum connection attempts reached. Giving up.\\\",\\n+ );\\n+ throw new Error(\\n+ `Failed to connect to MongoDB after ${MAX_CONNECTION_ATTEMPTS} attempts: ${error.message}`,\\n+ );\\n+ }\\n+\\n+ // Wait before next attempt with exponential backoff\\n+ const backoffTime = Math.min(\\n+ 1000 * Math.pow(2, connectionAttempts),\\n+ 30000,\\n+ );\\n+ console.log(\\n+ `Waiting ${backoffTime / 1000}s before next connection attempt...`,\\n+ );\\n+ await new Promise((resolve) => setTimeout(resolve, backoffTime));\\n+\\n+ // Recursive call to retry\\n+ return ensureDbConnection();\\n+ }\\n+ }\\n+}\\n+\\n+// Graceful shutdown handler\\n+const cleanupAndExit = async () => {\\n+ console.log(\\\"Shutting down workers...\\\");\\n+\\n+ try {\\n+ // Stop processing new jobs\\n+ await worker.close();\\n+ console.log(\\\"Digest worker stopped\\\");\\n+\\n+ // Close database connection\\n+ if (dbInitialized) {\\n+ const closeDatabaseConnection = (await import(\\\"../src/db.mjs\\\"))\\n+ .closeDatabaseConnection;\\n+ await closeDatabaseConnection();\\n+ console.log(\\\"Database connection closed\\\");\\n+ }\\n+\\n+ console.log(\\\"Cleanup completed, exiting\\\");\\n+ process.exit(0);\\n+ } catch (error) {\\n+ console.error(\\\"Error during shutdown:\\\", error);\\n+ process.exit(1);\\n+ }\\n+};\\n+\\n+// Register shutdown handlers\\n+process.on(\\\"SIGTERM\\\", cleanupAndExit);\\n+process.on(\\\"SIGINT\\\", cleanupAndExit);\\n+\\n+// Safely start all workers after ensuring database connection\\n+async function startWorkers() {\\n+ try {\\n+ // Initialize database connection\\n+ console.log(\\\"Initializing connection to database...\\\");\\n+ await ensureDbConnection();\\n+\\n+ // Start workers\\n+ console.log(\\\"Starting workers...\\\");\\n+ await requestProgressWorker.run();\\n+ worker.run();\\n+\\n+ console.log(\\\"All workers are running\\\");\\n+ } catch (error) {\\n+ console.error(\\\"Failed to initialize:\\\", error);\\n+\\n+ // Try to restart after a delay\\n+ console.log(\\\"Will attempt to restart workers in 15 seconds...\\\");\\n+ setTimeout(startWorkers, 15000);\\n+ }\\n+}\\n+\\n+// Start the workers\\n+startWorkers();\\ndiff --git a/package-lock.json b/package-lock.json\\nindex f0d4495..3cd29a6 100644\\n--- a/package-lock.json\\n+++ b/package-lock.json\\n@@ -1,13 +1,14 @@\\n {\\n \\\"name\\\": \\\"labeeb\\\",\\n- \\\"version\\\": \\\"2.4.18\\\",\\n+ \\\"version\\\": \\\"2.5.0\\\",\\n \\\"lockfileVersion\\\": 3,\\n \\\"requires\\\": true,\\n \\\"packages\\\": {\\n \\\"\\\": {\\n \\\"name\\\": \\\"labeeb\\\",\\n- \\\"version\\\": \\\"2.4.18\\\",\\n+ \\\"version\\\": \\\"2.5.0\\\",\\n \\\"dependencies\\\": {\\n+ \\\"@aj-archipelago/subvibe\\\": \\\"^1.0.8\\\",\\n \\\"@amplitude/analytics-browser\\\": \\\"^2.3.2\\\",\\n \\\"@apollo/client\\\": \\\"^3.10.4\\\",\\n \\\"@apollo/experimental-nextjs-app-support\\\": \\\"^0.11.0\\\",\\n@@ -15,6 +16,7 @@\\n \\\"@hello-pangea/dnd\\\": \\\"^16.6.0\\\",\\n \\\"@heroicons/react\\\": \\\"^2.0.18\\\",\\n \\\"@radix-ui/react-accordion\\\": \\\"^1.1.2\\\",\\n+ \\\"@radix-ui/react-alert-dialog\\\": \\\"^1.1.4\\\",\\n \\\"@radix-ui/react-checkbox\\\": \\\"^1.1.2\\\",\\n \\\"@radix-ui/react-dialog\\\": \\\"^1.1.2\\\",\\n \\\"@radix-ui/react-dismissable-layer\\\": \\\"^1.1.1\\\",\\n@@ -22,7 +24,7 @@\\n \\\"@radix-ui/react-popover\\\": \\\"^1.0.7\\\",\\n \\\"@radix-ui/react-progress\\\": \\\"^1.0.3\\\",\\n \\\"@radix-ui/react-select\\\": \\\"^2.1.2\\\",\\n- \\\"@radix-ui/react-slot\\\": \\\"^1.0.2\\\",\\n+ \\\"@radix-ui/react-slot\\\": \\\"^1.1.1\\\",\\n \\\"@radix-ui/react-tabs\\\": \\\"^1.0.4\\\",\\n \\\"@radix-ui/react-toast\\\": \\\"^1.2.2\\\",\\n \\\"@radix-ui/react-toggle\\\": \\\"^1.0.3\\\",\\n@@ -69,7 +71,7 @@\\n \\\"react-filepond\\\": \\\"^7.1.2\\\",\\n \\\"react-i18next\\\": \\\"^12.2.0\\\",\\n \\\"react-icons\\\": \\\"^4.7.1\\\",\\n- \\\"react-intersection-observer\\\": \\\"^9.13.1\\\",\\n+ \\\"react-intersection-observer\\\": \\\"^9.15.1\\\",\\n \\\"react-markdown\\\": \\\"^9.0.1\\\",\\n \\\"react-monaco-editor\\\": \\\"^0.55.0\\\",\\n \\\"react-player\\\": \\\"^2.16.0\\\",\\n@@ -78,7 +80,6 @@\\n \\\"react-redux\\\": \\\"^8.0.5\\\",\\n \\\"react-router-dom\\\": \\\"^6.8.1\\\",\\n \\\"react-scripts\\\": \\\"5.0.1\\\",\\n- \\\"react-scroll-to-bottom\\\": \\\"^4.2.0\\\",\\n \\\"react-select\\\": \\\"^5.7.3\\\",\\n \\\"react-textarea-autosize\\\": \\\"^8.4.0\\\",\\n \\\"react-time-ago\\\": \\\"^7.3.1\\\",\\n@@ -99,6 +100,7 @@\\n \\\"xxhash-wasm\\\": \\\"^1.1.0\\\"\\n },\\n \\\"devDependencies\\\": {\\n+ \\\"@babel/plugin-proposal-private-property-in-object\\\": \\\"^7.21.11\\\",\\n \\\"@babel/preset-env\\\": \\\"^7.26.0\\\",\\n \\\"@babel/preset-react\\\": \\\"^7.26.3\\\",\\n \\\"@tailwindcss/forms\\\": \\\"^0.5.7\\\",\\n@@ -106,7 +108,9 @@\\n \\\"babel-jest\\\": \\\"^29.7.0\\\",\\n \\\"customize-cra\\\": \\\"^1.0.0\\\",\\n \\\"jest\\\": \\\"^29.7.0\\\",\\n+ \\\"jest-environment-jsdom\\\": \\\"^29.7.0\\\",\\n \\\"mongodb-memory-server\\\": \\\"^10.1.3\\\",\\n+ \\\"nodemon\\\": \\\"^3.1.9\\\",\\n \\\"postcss\\\": \\\"^8.4.31\\\",\\n \\\"prettier\\\": \\\"^3.2.2\\\",\\n \\\"react-app-rewired\\\": \\\"^2.2.1\\\",\\n@@ -129,6 +133,15 @@\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.3.tgz\\\",\\n \\\"integrity\\\": \\\"sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==\\\"\\n },\\n+ \\\"node_modules/@aj-archipelago/subvibe\\\": {\\n+ \\\"version\\\": \\\"1.0.8\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@aj-archipelago/subvibe/-/subvibe-1.0.8.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-MlD6BeJBMqILXUe8qscmmZGorG60E6M7jXSLy6YJZxhQKu2Lir4qm7riDwRiE12PdL3QnNUnLx2jw9Re+mhJkA==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=14.0.0\\\"\\n+ }\\n+ },\\n \\\"node_modules/@alloc/quick-lru\\\": {\\n \\\"version\\\": \\\"5.2.0\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz\\\",\\n@@ -902,9 +915,18 @@\\n }\\n },\\n \\\"node_modules/@babel/plugin-proposal-private-property-in-object\\\": {\\n- \\\"version\\\": \\\"7.21.0-placeholder-for-preset-env.2\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==\\\",\\n+ \\\"version\\\": \\\"7.21.11\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==\\\",\\n+ \\\"deprecated\\\": \\\"This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-property-in-object instead.\\\",\\n+ \\\"dev\\\": true,\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"@babel/helper-annotate-as-pure\\\": \\\"^7.18.6\\\",\\n+ \\\"@babel/helper-create-class-features-plugin\\\": \\\"^7.21.0\\\",\\n+ \\\"@babel/helper-plugin-utils\\\": \\\"^7.20.2\\\",\\n+ \\\"@babel/plugin-syntax-private-property-in-object\\\": \\\"^7.14.5\\\"\\n+ },\\n \\\"engines\\\": {\\n \\\"node\\\": \\\">=6.9.0\\\"\\n },\\n@@ -2212,6 +2234,18 @@\\n \\\"@babel/core\\\": \\\"^7.4.0 || ^8.0.0-0 <8.0.0\\\"\\n }\\n },\\n+ \\\"node_modules/@babel/preset-env/node_modules/@babel/plugin-proposal-private-property-in-object\\\": {\\n+ \\\"version\\\": \\\"7.21.0-placeholder-for-preset-env.2\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=6.9.0\\\"\\n+ },\\n+ \\\"peerDependencies\\\": {\\n+ \\\"@babel/core\\\": \\\"^7.0.0-0\\\"\\n+ }\\n+ },\\n \\\"node_modules/@babel/preset-env/node_modules/babel-plugin-polyfill-corejs3\\\": {\\n \\\"version\\\": \\\"0.10.6\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.6.tgz\\\",\\n@@ -2307,18 +2341,6 @@\\n \\\"node\\\": \\\">=6.9.0\\\"\\n }\\n },\\n- \\\"node_modules/@babel/runtime-corejs3\\\": {\\n- \\\"version\\\": \\\"7.22.6\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.22.6.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-M+37LLIRBTEVjktoJjbw4KVhupF0U/3PYUCbBwgAd9k17hoKhRu1n935QiG7Tuxv0LJOMrb2vuKEeYUlv0iyiw==\\\",\\n- \\\"dependencies\\\": {\\n- \\\"core-js-pure\\\": \\\"^3.30.2\\\",\\n- \\\"regenerator-runtime\\\": \\\"^0.13.11\\\"\\n- },\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">=6.9.0\\\"\\n- }\\n- },\\n \\\"node_modules/@babel/runtime/node_modules/regenerator-runtime\\\": {\\n \\\"version\\\": \\\"0.14.0\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz\\\",\\n@@ -2683,26 +2705,6 @@\\n \\\"stylis\\\": \\\"4.2.0\\\"\\n }\\n },\\n- \\\"node_modules/@emotion/css\\\": {\\n- \\\"version\\\": \\\"11.1.3\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@emotion/css/-/css-11.1.3.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-RSQP59qtCNTf5NWD6xM08xsQdCZmVYnX/panPYvB6LQAPKQB6GL49Njf0EMbS3CyDtrlWsBcmqBtysFvfWT3rA==\\\",\\n- \\\"dependencies\\\": {\\n- \\\"@emotion/babel-plugin\\\": \\\"^11.0.0\\\",\\n- \\\"@emotion/cache\\\": \\\"^11.1.3\\\",\\n- \\\"@emotion/serialize\\\": \\\"^1.0.0\\\",\\n- \\\"@emotion/sheet\\\": \\\"^1.0.0\\\",\\n- \\\"@emotion/utils\\\": \\\"^1.0.0\\\"\\n- },\\n- \\\"peerDependencies\\\": {\\n- \\\"@babel/core\\\": \\\"^7.0.0\\\"\\n- },\\n- \\\"peerDependenciesMeta\\\": {\\n- \\\"@babel/core\\\": {\\n- \\\"optional\\\": true\\n- }\\n- }\\n- },\\n \\\"node_modules/@emotion/hash\\\": {\\n \\\"version\\\": \\\"0.9.1\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/@emotion/hash/-/hash-0.9.1.tgz\\\",\\n@@ -3882,66 +3884,6 @@\\n \\\"darwin\\\"\\n ]\\n },\\n- \\\"node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64\\\": {\\n- \\\"version\\\": \\\"3.0.3\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==\\\",\\n- \\\"cpu\\\": [\\n- \\\"x64\\\"\\n- ],\\n- \\\"optional\\\": true,\\n- \\\"os\\\": [\\n- \\\"darwin\\\"\\n- ]\\n- },\\n- \\\"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm\\\": {\\n- \\\"version\\\": \\\"3.0.3\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==\\\",\\n- \\\"cpu\\\": [\\n- \\\"arm\\\"\\n- ],\\n- \\\"optional\\\": true,\\n- \\\"os\\\": [\\n- \\\"linux\\\"\\n- ]\\n- },\\n- \\\"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64\\\": {\\n- \\\"version\\\": \\\"3.0.3\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==\\\",\\n- \\\"cpu\\\": [\\n- \\\"arm64\\\"\\n- ],\\n- \\\"optional\\\": true,\\n- \\\"os\\\": [\\n- \\\"linux\\\"\\n- ]\\n- },\\n- \\\"node_modules/@msgpackr-extract/msgpackr-extract-linux-x64\\\": {\\n- \\\"version\\\": \\\"3.0.3\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==\\\",\\n- \\\"cpu\\\": [\\n- \\\"x64\\\"\\n- ],\\n- \\\"optional\\\": true,\\n- \\\"os\\\": [\\n- \\\"linux\\\"\\n- ]\\n- },\\n- \\\"node_modules/@msgpackr-extract/msgpackr-extract-win32-x64\\\": {\\n- \\\"version\\\": \\\"3.0.3\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==\\\",\\n- \\\"cpu\\\": [\\n- \\\"x64\\\"\\n- ],\\n- \\\"optional\\\": true,\\n- \\\"os\\\": [\\n- \\\"win32\\\"\\n- ]\\n- },\\n \\\"node_modules/@next/env\\\": {\\n \\\"version\\\": \\\"14.0.3\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/@next/env/-/env-14.0.3.tgz\\\",\\n@@ -4178,26 +4120,6 @@\\n \\\"@parcel/watcher-win32-x64\\\": \\\"2.5.0\\\"\\n }\\n },\\n- \\\"node_modules/@parcel/watcher-android-arm64\\\": {\\n- \\\"version\\\": \\\"2.5.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-qlX4eS28bUcQCdribHkg/herLe+0A9RyYC+mm2PXpncit8z5b3nSqGVzMNR3CmtAOgRutiZ02eIJJgP/b1iEFQ==\\\",\\n- \\\"cpu\\\": [\\n- \\\"arm64\\\"\\n- ],\\n- \\\"dev\\\": true,\\n- \\\"optional\\\": true,\\n- \\\"os\\\": [\\n- \\\"android\\\"\\n- ],\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">= 10.0.0\\\"\\n- },\\n- \\\"funding\\\": {\\n- \\\"type\\\": \\\"opencollective\\\",\\n- \\\"url\\\": \\\"https://opencollective.com/parcel\\\"\\n- }\\n- },\\n \\\"node_modules/@parcel/watcher-darwin-arm64\\\": {\\n \\\"version\\\": \\\"2.5.0\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.0.tgz\\\",\\n@@ -4218,329 +4140,191 @@\\n \\\"url\\\": \\\"https://opencollective.com/parcel\\\"\\n }\\n },\\n- \\\"node_modules/@parcel/watcher-darwin-x64\\\": {\\n- \\\"version\\\": \\\"2.5.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-9rhlwd78saKf18fT869/poydQK8YqlU26TMiNg7AIu7eBp9adqbJZqmdFOsbZ5cnLp5XvRo9wcFmNHgHdWaGYA==\\\",\\n- \\\"cpu\\\": [\\n- \\\"x64\\\"\\n- ],\\n+ \\\"node_modules/@parcel/watcher/node_modules/detect-libc\\\": {\\n+ \\\"version\\\": \\\"1.0.3\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==\\\",\\n \\\"dev\\\": true,\\n \\\"optional\\\": true,\\n- \\\"os\\\": [\\n- \\\"darwin\\\"\\n- ],\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">= 10.0.0\\\"\\n+ \\\"bin\\\": {\\n+ \\\"detect-libc\\\": \\\"bin/detect-libc.js\\\"\\n },\\n- \\\"funding\\\": {\\n- \\\"type\\\": \\\"opencollective\\\",\\n- \\\"url\\\": \\\"https://opencollective.com/parcel\\\"\\n- }\\n- },\\n- \\\"node_modules/@parcel/watcher-freebsd-x64\\\": {\\n- \\\"version\\\": \\\"2.5.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-syvfhZzyM8kErg3VF0xpV8dixJ+RzbUaaGaeb7uDuz0D3FK97/mZ5AJQ3XNnDsXX7KkFNtyQyFrXZzQIcN49Tw==\\\",\\n- \\\"cpu\\\": [\\n- \\\"x64\\\"\\n- ],\\n- \\\"dev\\\": true,\\n- \\\"optional\\\": true,\\n- \\\"os\\\": [\\n- \\\"freebsd\\\"\\n- ],\\n \\\"engines\\\": {\\n- \\\"node\\\": \\\">= 10.0.0\\\"\\n- },\\n- \\\"funding\\\": {\\n- \\\"type\\\": \\\"opencollective\\\",\\n- \\\"url\\\": \\\"https://opencollective.com/parcel\\\"\\n+ \\\"node\\\": \\\">=0.10\\\"\\n }\\n },\\n- \\\"node_modules/@parcel/watcher-linux-arm-glibc\\\": {\\n- \\\"version\\\": \\\"2.5.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-0VQY1K35DQET3dVYWpOaPFecqOT9dbuCfzjxoQyif1Wc574t3kOSkKevULddcR9znz1TcklCE7Ht6NIxjvTqLA==\\\",\\n- \\\"cpu\\\": [\\n- \\\"arm\\\"\\n- ],\\n+ \\\"node_modules/@parcel/watcher/node_modules/node-addon-api\\\": {\\n+ \\\"version\\\": \\\"7.1.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==\\\",\\n \\\"dev\\\": true,\\n- \\\"optional\\\": true,\\n- \\\"os\\\": [\\n- \\\"linux\\\"\\n- ],\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">= 10.0.0\\\"\\n- },\\n- \\\"funding\\\": {\\n- \\\"type\\\": \\\"opencollective\\\",\\n- \\\"url\\\": \\\"https://opencollective.com/parcel\\\"\\n- }\\n+ \\\"optional\\\": true\\n },\\n- \\\"node_modules/@parcel/watcher-linux-arm-musl\\\": {\\n- \\\"version\\\": \\\"2.5.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-6uHywSIzz8+vi2lAzFeltnYbdHsDm3iIB57d4g5oaB9vKwjb6N6dRIgZMujw4nm5r6v9/BQH0noq6DzHrqr2pA==\\\",\\n- \\\"cpu\\\": [\\n- \\\"arm\\\"\\n- ],\\n- \\\"dev\\\": true,\\n- \\\"optional\\\": true,\\n- \\\"os\\\": [\\n- \\\"linux\\\"\\n- ],\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">= 10.0.0\\\"\\n+ \\\"node_modules/@pmmmwh/react-refresh-webpack-plugin\\\": {\\n+ \\\"version\\\": \\\"0.5.11\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-7j/6vdTym0+qZ6u4XbSAxrWBGYSdCfTzySkj7WAFgDLmSyWlOrWvpyzxlFh5jtw9dn0oL/jtW+06XfFiisN3JQ==\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"ansi-html-community\\\": \\\"^0.0.8\\\",\\n+ \\\"common-path-prefix\\\": \\\"^3.0.0\\\",\\n+ \\\"core-js-pure\\\": \\\"^3.23.3\\\",\\n+ \\\"error-stack-parser\\\": \\\"^2.0.6\\\",\\n+ \\\"find-up\\\": \\\"^5.0.0\\\",\\n+ \\\"html-entities\\\": \\\"^2.1.0\\\",\\n+ \\\"loader-utils\\\": \\\"^2.0.4\\\",\\n+ \\\"schema-utils\\\": \\\"^3.0.0\\\",\\n+ \\\"source-map\\\": \\\"^0.7.3\\\"\\n },\\n- \\\"funding\\\": {\\n- \\\"type\\\": \\\"opencollective\\\",\\n- \\\"url\\\": \\\"https://opencollective.com/parcel\\\"\\n- }\\n- },\\n- \\\"node_modules/@parcel/watcher-linux-arm64-glibc\\\": {\\n- \\\"version\\\": \\\"2.5.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-BfNjXwZKxBy4WibDb/LDCriWSKLz+jJRL3cM/DllnHH5QUyoiUNEp3GmL80ZqxeumoADfCCP19+qiYiC8gUBjA==\\\",\\n- \\\"cpu\\\": [\\n- \\\"arm64\\\"\\n- ],\\n- \\\"dev\\\": true,\\n- \\\"optional\\\": true,\\n- \\\"os\\\": [\\n- \\\"linux\\\"\\n- ],\\n \\\"engines\\\": {\\n- \\\"node\\\": \\\">= 10.0.0\\\"\\n+ \\\"node\\\": \\\">= 10.13\\\"\\n },\\n- \\\"funding\\\": {\\n- \\\"type\\\": \\\"opencollective\\\",\\n- \\\"url\\\": \\\"https://opencollective.com/parcel\\\"\\n+ \\\"peerDependencies\\\": {\\n+ \\\"@types/webpack\\\": \\\"4.x || 5.x\\\",\\n+ \\\"react-refresh\\\": \\\">=0.10.0 <1.0.0\\\",\\n+ \\\"sockjs-client\\\": \\\"^1.4.0\\\",\\n+ \\\"type-fest\\\": \\\">=0.17.0 <5.0.0\\\",\\n+ \\\"webpack\\\": \\\">=4.43.0 <6.0.0\\\",\\n+ \\\"webpack-dev-server\\\": \\\"3.x || 4.x\\\",\\n+ \\\"webpack-hot-middleware\\\": \\\"2.x\\\",\\n+ \\\"webpack-plugin-serve\\\": \\\"0.x || 1.x\\\"\\n+ },\\n+ \\\"peerDependenciesMeta\\\": {\\n+ \\\"@types/webpack\\\": {\\n+ \\\"optional\\\": true\\n+ },\\n+ \\\"sockjs-client\\\": {\\n+ \\\"optional\\\": true\\n+ },\\n+ \\\"type-fest\\\": {\\n+ \\\"optional\\\": true\\n+ },\\n+ \\\"webpack-dev-server\\\": {\\n+ \\\"optional\\\": true\\n+ },\\n+ \\\"webpack-hot-middleware\\\": {\\n+ \\\"optional\\\": true\\n+ },\\n+ \\\"webpack-plugin-serve\\\": {\\n+ \\\"optional\\\": true\\n+ }\\n }\\n },\\n- \\\"node_modules/@parcel/watcher-linux-arm64-musl\\\": {\\n- \\\"version\\\": \\\"2.5.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-S1qARKOphxfiBEkwLUbHjCY9BWPdWnW9j7f7Hb2jPplu8UZ3nes7zpPOW9bkLbHRvWM0WDTsjdOTUgW0xLBN1Q==\\\",\\n- \\\"cpu\\\": [\\n- \\\"arm64\\\"\\n- ],\\n- \\\"dev\\\": true,\\n- \\\"optional\\\": true,\\n- \\\"os\\\": [\\n- \\\"linux\\\"\\n- ],\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">= 10.0.0\\\"\\n- },\\n- \\\"funding\\\": {\\n- \\\"type\\\": \\\"opencollective\\\",\\n- \\\"url\\\": \\\"https://opencollective.com/parcel\\\"\\n+ \\\"node_modules/@radix-ui/number\\\": {\\n+ \\\"version\\\": \\\"1.1.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==\\\"\\n+ },\\n+ \\\"node_modules/@radix-ui/primitive\\\": {\\n+ \\\"version\\\": \\\"1.0.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"@babel/runtime\\\": \\\"^7.13.10\\\"\\n }\\n },\\n- \\\"node_modules/@parcel/watcher-linux-x64-glibc\\\": {\\n- \\\"version\\\": \\\"2.5.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-d9AOkusyXARkFD66S6zlGXyzx5RvY+chTP9Jp0ypSTC9d4lzyRs9ovGf/80VCxjKddcUvnsGwCHWuF2EoPgWjw==\\\",\\n- \\\"cpu\\\": [\\n- \\\"x64\\\"\\n- ],\\n- \\\"dev\\\": true,\\n- \\\"optional\\\": true,\\n- \\\"os\\\": [\\n- \\\"linux\\\"\\n- ],\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">= 10.0.0\\\"\\n+ \\\"node_modules/@radix-ui/react-accordion\\\": {\\n+ \\\"version\\\": \\\"1.1.2\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.1.2.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-fDG7jcoNKVjSK6yfmuAs0EnPDro0WMXIhMtXdTBWqEioVW206ku+4Lw07e+13lUkFkpoEQ2PdeMIAGpdqEAmDg==\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"@babel/runtime\\\": \\\"^7.13.10\\\",\\n+ \\\"@radix-ui/primitive\\\": \\\"1.0.1\\\",\\n+ \\\"@radix-ui/react-collapsible\\\": \\\"1.0.3\\\",\\n+ \\\"@radix-ui/react-collection\\\": \\\"1.0.3\\\",\\n+ \\\"@radix-ui/react-compose-refs\\\": \\\"1.0.1\\\",\\n+ \\\"@radix-ui/react-context\\\": \\\"1.0.1\\\",\\n+ \\\"@radix-ui/react-direction\\\": \\\"1.0.1\\\",\\n+ \\\"@radix-ui/react-id\\\": \\\"1.0.1\\\",\\n+ \\\"@radix-ui/react-primitive\\\": \\\"1.0.3\\\",\\n+ \\\"@radix-ui/react-use-controllable-state\\\": \\\"1.0.1\\\"\\n },\\n- \\\"funding\\\": {\\n- \\\"type\\\": \\\"opencollective\\\",\\n- \\\"url\\\": \\\"https://opencollective.com/parcel\\\"\\n- }\\n- },\\n- \\\"node_modules/@parcel/watcher-linux-x64-musl\\\": {\\n- \\\"version\\\": \\\"2.5.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-iqOC+GoTDoFyk/VYSFHwjHhYrk8bljW6zOhPuhi5t9ulqiYq1togGJB5e3PwYVFFfeVgc6pbz3JdQyDoBszVaA==\\\",\\n- \\\"cpu\\\": [\\n- \\\"x64\\\"\\n- ],\\n- \\\"dev\\\": true,\\n- \\\"optional\\\": true,\\n- \\\"os\\\": [\\n- \\\"linux\\\"\\n- ],\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">= 10.0.0\\\"\\n+ \\\"peerDependencies\\\": {\\n+ \\\"@types/react\\\": \\\"*\\\",\\n+ \\\"@types/react-dom\\\": \\\"*\\\",\\n+ \\\"react\\\": \\\"^16.8 || ^17.0 || ^18.0\\\",\\n+ \\\"react-dom\\\": \\\"^16.8 || ^17.0 || ^18.0\\\"\\n },\\n- \\\"funding\\\": {\\n- \\\"type\\\": \\\"opencollective\\\",\\n- \\\"url\\\": \\\"https://opencollective.com/parcel\\\"\\n+ \\\"peerDependenciesMeta\\\": {\\n+ \\\"@types/react\\\": {\\n+ \\\"optional\\\": true\\n+ },\\n+ \\\"@types/react-dom\\\": {\\n+ \\\"optional\\\": true\\n+ }\\n }\\n },\\n- \\\"node_modules/@parcel/watcher-win32-arm64\\\": {\\n- \\\"version\\\": \\\"2.5.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-twtft1d+JRNkM5YbmexfcH/N4znDtjgysFaV9zvZmmJezQsKpkfLYJ+JFV3uygugK6AtIM2oADPkB2AdhBrNig==\\\",\\n- \\\"cpu\\\": [\\n- \\\"arm64\\\"\\n- ],\\n- \\\"dev\\\": true,\\n- \\\"optional\\\": true,\\n- \\\"os\\\": [\\n- \\\"win32\\\"\\n- ],\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">= 10.0.0\\\"\\n+ \\\"node_modules/@radix-ui/react-alert-dialog\\\": {\\n+ \\\"version\\\": \\\"1.1.4\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.1.4.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-A6Kh23qZDLy3PSU4bh2UJZznOrUdHImIXqF8YtUa6CN73f8EOO9XlXSCd9IHyPvIquTaa/kwaSWzZTtUvgXVGw==\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"@radix-ui/primitive\\\": \\\"1.1.1\\\",\\n+ \\\"@radix-ui/react-compose-refs\\\": \\\"1.1.1\\\",\\n+ \\\"@radix-ui/react-context\\\": \\\"1.1.1\\\",\\n+ \\\"@radix-ui/react-dialog\\\": \\\"1.1.4\\\",\\n+ \\\"@radix-ui/react-primitive\\\": \\\"2.0.1\\\",\\n+ \\\"@radix-ui/react-slot\\\": \\\"1.1.1\\\"\\n },\\n- \\\"funding\\\": {\\n- \\\"type\\\": \\\"opencollective\\\",\\n- \\\"url\\\": \\\"https://opencollective.com/parcel\\\"\\n- }\\n- },\\n- \\\"node_modules/@parcel/watcher-win32-ia32\\\": {\\n- \\\"version\\\": \\\"2.5.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-+rgpsNRKwo8A53elqbbHXdOMtY/tAtTzManTWShB5Kk54N8Q9mzNWV7tV+IbGueCbcj826MfWGU3mprWtuf1TA==\\\",\\n- \\\"cpu\\\": [\\n- \\\"ia32\\\"\\n- ],\\n- \\\"dev\\\": true,\\n- \\\"optional\\\": true,\\n- \\\"os\\\": [\\n- \\\"win32\\\"\\n- ],\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">= 10.0.0\\\"\\n+ \\\"peerDependencies\\\": {\\n+ \\\"@types/react\\\": \\\"*\\\",\\n+ \\\"@types/react-dom\\\": \\\"*\\\",\\n+ \\\"react\\\": \\\"^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\\\",\\n+ \\\"react-dom\\\": \\\"^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\\\"\\n },\\n- \\\"funding\\\": {\\n- \\\"type\\\": \\\"opencollective\\\",\\n- \\\"url\\\": \\\"https://opencollective.com/parcel\\\"\\n+ \\\"peerDependenciesMeta\\\": {\\n+ \\\"@types/react\\\": {\\n+ \\\"optional\\\": true\\n+ },\\n+ \\\"@types/react-dom\\\": {\\n+ \\\"optional\\\": true\\n+ }\\n }\\n },\\n- \\\"node_modules/@parcel/watcher-win32-x64\\\": {\\n- \\\"version\\\": \\\"2.5.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-lPrxve92zEHdgeff3aiu4gDOIt4u7sJYha6wbdEZDCDUhtjTsOMiaJzG5lMY4GkWH8p0fMmO2Ppq5G5XXG+DQw==\\\",\\n- \\\"cpu\\\": [\\n- \\\"x64\\\"\\n- ],\\n- \\\"dev\\\": true,\\n- \\\"optional\\\": true,\\n- \\\"os\\\": [\\n- \\\"win32\\\"\\n- ],\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">= 10.0.0\\\"\\n- },\\n- \\\"funding\\\": {\\n- \\\"type\\\": \\\"opencollective\\\",\\n- \\\"url\\\": \\\"https://opencollective.com/parcel\\\"\\n- }\\n+ \\\"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/primitive\\\": {\\n+ \\\"version\\\": \\\"1.1.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==\\\"\\n },\\n- \\\"node_modules/@parcel/watcher/node_modules/detect-libc\\\": {\\n- \\\"version\\\": \\\"1.0.3\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==\\\",\\n- \\\"dev\\\": true,\\n- \\\"optional\\\": true,\\n- \\\"bin\\\": {\\n- \\\"detect-libc\\\": \\\"bin/detect-libc.js\\\"\\n+ \\\"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-compose-refs\\\": {\\n+ \\\"version\\\": \\\"1.1.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==\\\",\\n+ \\\"peerDependencies\\\": {\\n+ \\\"@types/react\\\": \\\"*\\\",\\n+ \\\"react\\\": \\\"^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\\\"\\n },\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">=0.10\\\"\\n+ \\\"peerDependenciesMeta\\\": {\\n+ \\\"@types/react\\\": {\\n+ \\\"optional\\\": true\\n+ }\\n }\\n },\\n- \\\"node_modules/@parcel/watcher/node_modules/node-addon-api\\\": {\\n- \\\"version\\\": \\\"7.1.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==\\\",\\n- \\\"dev\\\": true,\\n- \\\"optional\\\": true\\n- },\\n- \\\"node_modules/@pmmmwh/react-refresh-webpack-plugin\\\": {\\n- \\\"version\\\": \\\"0.5.11\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.11.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-7j/6vdTym0+qZ6u4XbSAxrWBGYSdCfTzySkj7WAFgDLmSyWlOrWvpyzxlFh5jtw9dn0oL/jtW+06XfFiisN3JQ==\\\",\\n- \\\"dependencies\\\": {\\n- \\\"ansi-html-community\\\": \\\"^0.0.8\\\",\\n- \\\"common-path-prefix\\\": \\\"^3.0.0\\\",\\n- \\\"core-js-pure\\\": \\\"^3.23.3\\\",\\n- \\\"error-stack-parser\\\": \\\"^2.0.6\\\",\\n- \\\"find-up\\\": \\\"^5.0.0\\\",\\n- \\\"html-entities\\\": \\\"^2.1.0\\\",\\n- \\\"loader-utils\\\": \\\"^2.0.4\\\",\\n- \\\"schema-utils\\\": \\\"^3.0.0\\\",\\n- \\\"source-map\\\": \\\"^0.7.3\\\"\\n- },\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">= 10.13\\\"\\n- },\\n+ \\\"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-context\\\": {\\n+ \\\"version\\\": \\\"1.1.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-UASk9zi+crv9WteK/NU4PLvOoL3OuE6BWVKNF6hPRBtYBDXQ2u5iu3O59zUlJiTVvkyuycnqrztsHVJwcK9K+Q==\\\",\\n \\\"peerDependencies\\\": {\\n- \\\"@types/webpack\\\": \\\"4.x || 5.x\\\",\\n- \\\"react-refresh\\\": \\\">=0.10.0 <1.0.0\\\",\\n- \\\"sockjs-client\\\": \\\"^1.4.0\\\",\\n- \\\"type-fest\\\": \\\">=0.17.0 <5.0.0\\\",\\n- \\\"webpack\\\": \\\">=4.43.0 <6.0.0\\\",\\n- \\\"webpack-dev-server\\\": \\\"3.x || 4.x\\\",\\n- \\\"webpack-hot-middleware\\\": \\\"2.x\\\",\\n- \\\"webpack-plugin-serve\\\": \\\"0.x || 1.x\\\"\\n+ \\\"@types/react\\\": \\\"*\\\",\\n+ \\\"react\\\": \\\"^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\\\"\\n },\\n \\\"peerDependenciesMeta\\\": {\\n- \\\"@types/webpack\\\": {\\n- \\\"optional\\\": true\\n- },\\n- \\\"sockjs-client\\\": {\\n- \\\"optional\\\": true\\n- },\\n- \\\"type-fest\\\": {\\n- \\\"optional\\\": true\\n- },\\n- \\\"webpack-dev-server\\\": {\\n- \\\"optional\\\": true\\n- },\\n- \\\"webpack-hot-middleware\\\": {\\n- \\\"optional\\\": true\\n- },\\n- \\\"webpack-plugin-serve\\\": {\\n+ \\\"@types/react\\\": {\\n \\\"optional\\\": true\\n }\\n }\\n },\\n- \\\"node_modules/@radix-ui/number\\\": {\\n- \\\"version\\\": \\\"1.1.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/number/-/number-1.1.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==\\\"\\n- },\\n- \\\"node_modules/@radix-ui/primitive\\\": {\\n- \\\"version\\\": \\\"1.0.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==\\\",\\n- \\\"dependencies\\\": {\\n- \\\"@babel/runtime\\\": \\\"^7.13.10\\\"\\n- }\\n- },\\n- \\\"node_modules/@radix-ui/react-accordion\\\": {\\n- \\\"version\\\": \\\"1.1.2\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-accordion/-/react-accordion-1.1.2.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-fDG7jcoNKVjSK6yfmuAs0EnPDro0WMXIhMtXdTBWqEioVW206ku+4Lw07e+13lUkFkpoEQ2PdeMIAGpdqEAmDg==\\\",\\n+ \\\"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-primitive\\\": {\\n+ \\\"version\\\": \\\"2.0.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==\\\",\\n \\\"dependencies\\\": {\\n- \\\"@babel/runtime\\\": \\\"^7.13.10\\\",\\n- \\\"@radix-ui/primitive\\\": \\\"1.0.1\\\",\\n- \\\"@radix-ui/react-collapsible\\\": \\\"1.0.3\\\",\\n- \\\"@radix-ui/react-collection\\\": \\\"1.0.3\\\",\\n- \\\"@radix-ui/react-compose-refs\\\": \\\"1.0.1\\\",\\n- \\\"@radix-ui/react-context\\\": \\\"1.0.1\\\",\\n- \\\"@radix-ui/react-direction\\\": \\\"1.0.1\\\",\\n- \\\"@radix-ui/react-id\\\": \\\"1.0.1\\\",\\n- \\\"@radix-ui/react-primitive\\\": \\\"1.0.3\\\",\\n- \\\"@radix-ui/react-use-controllable-state\\\": \\\"1.0.1\\\"\\n+ \\\"@radix-ui/react-slot\\\": \\\"1.1.1\\\"\\n },\\n \\\"peerDependencies\\\": {\\n \\\"@types/react\\\": \\\"*\\\",\\n \\\"@types/react-dom\\\": \\\"*\\\",\\n- \\\"react\\\": \\\"^16.8 || ^17.0 || ^18.0\\\",\\n- \\\"react-dom\\\": \\\"^16.8 || ^17.0 || ^18.0\\\"\\n+ \\\"react\\\": \\\"^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\\\",\\n+ \\\"react-dom\\\": \\\"^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\\\"\\n },\\n \\\"peerDependenciesMeta\\\": {\\n \\\"@types/react\\\": {\\n@@ -4816,6 +4600,24 @@\\n }\\n }\\n },\\n+ \\\"node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot\\\": {\\n+ \\\"version\\\": \\\"1.0.2\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"@babel/runtime\\\": \\\"^7.13.10\\\",\\n+ \\\"@radix-ui/react-compose-refs\\\": \\\"1.0.1\\\"\\n+ },\\n+ \\\"peerDependencies\\\": {\\n+ \\\"@types/react\\\": \\\"*\\\",\\n+ \\\"react\\\": \\\"^16.8 || ^17.0 || ^18.0\\\"\\n+ },\\n+ \\\"peerDependenciesMeta\\\": {\\n+ \\\"@types/react\\\": {\\n+ \\\"optional\\\": true\\n+ }\\n+ }\\n+ },\\n \\\"node_modules/@radix-ui/react-compose-refs\\\": {\\n \\\"version\\\": \\\"1.0.1\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz\\\",\\n@@ -4851,24 +4653,24 @@\\n }\\n },\\n \\\"node_modules/@radix-ui/react-dialog\\\": {\\n- \\\"version\\\": \\\"1.1.2\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.2.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-Yj4dZtqa2o+kG61fzB0H2qUvmwBA2oyQroGLyNtBj1beo1khoQ3q1a2AO8rrQYjd8256CO9+N8L9tvsS+bnIyA==\\\",\\n+ \\\"version\\\": \\\"1.1.4\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.4.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-Ur7EV1IwQGCyaAuyDRiOLA5JIUZxELJljF+MbM/2NC0BYwfuRrbpS30BiQBJrVruscgUkieKkqXYDOoByaxIoA==\\\",\\n \\\"dependencies\\\": {\\n- \\\"@radix-ui/primitive\\\": \\\"1.1.0\\\",\\n- \\\"@radix-ui/react-compose-refs\\\": \\\"1.1.0\\\",\\n+ \\\"@radix-ui/primitive\\\": \\\"1.1.1\\\",\\n+ \\\"@radix-ui/react-compose-refs\\\": \\\"1.1.1\\\",\\n \\\"@radix-ui/react-context\\\": \\\"1.1.1\\\",\\n- \\\"@radix-ui/react-dismissable-layer\\\": \\\"1.1.1\\\",\\n+ \\\"@radix-ui/react-dismissable-layer\\\": \\\"1.1.3\\\",\\n \\\"@radix-ui/react-focus-guards\\\": \\\"1.1.1\\\",\\n- \\\"@radix-ui/react-focus-scope\\\": \\\"1.1.0\\\",\\n+ \\\"@radix-ui/react-focus-scope\\\": \\\"1.1.1\\\",\\n \\\"@radix-ui/react-id\\\": \\\"1.1.0\\\",\\n- \\\"@radix-ui/react-portal\\\": \\\"1.1.2\\\",\\n- \\\"@radix-ui/react-presence\\\": \\\"1.1.1\\\",\\n- \\\"@radix-ui/react-primitive\\\": \\\"2.0.0\\\",\\n- \\\"@radix-ui/react-slot\\\": \\\"1.1.0\\\",\\n+ \\\"@radix-ui/react-portal\\\": \\\"1.1.3\\\",\\n+ \\\"@radix-ui/react-presence\\\": \\\"1.1.2\\\",\\n+ \\\"@radix-ui/react-primitive\\\": \\\"2.0.1\\\",\\n+ \\\"@radix-ui/react-slot\\\": \\\"1.1.1\\\",\\n \\\"@radix-ui/react-use-controllable-state\\\": \\\"1.1.0\\\",\\n \\\"aria-hidden\\\": \\\"^1.1.1\\\",\\n- \\\"react-remove-scroll\\\": \\\"2.6.0\\\"\\n+ \\\"react-remove-scroll\\\": \\\"^2.6.1\\\"\\n },\\n \\\"peerDependencies\\\": {\\n \\\"@types/react\\\": \\\"*\\\",\\n@@ -4886,14 +4688,14 @@\\n }\\n },\\n \\\"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/primitive\\\": {\\n- \\\"version\\\": \\\"1.1.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-4Z8dn6Upk0qk4P74xBhZ6Hd/w0mPEzOOLxy4xiPXOXqjF7jZS0VAKk7/x/H6FyY2zCkYJqePf1G5KmkmNJ4RBA==\\\"\\n+ \\\"version\\\": \\\"1.1.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-SJ31y+Q/zAyShtXJc8x83i9TYdbAfHZ++tUZnvjJJqFjzsdUnKsxPL6IEtBlxKkU7yzer//GQtZSV4GbldL3YA==\\\"\\n },\\n \\\"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-compose-refs\\\": {\\n- \\\"version\\\": \\\"1.1.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==\\\",\\n+ \\\"version\\\": \\\"1.1.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==\\\",\\n \\\"peerDependencies\\\": {\\n \\\"@types/react\\\": \\\"*\\\",\\n \\\"react\\\": \\\"^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\\\"\\n@@ -4918,6 +4720,32 @@\\n }\\n }\\n },\\n+ \\\"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-dismissable-layer\\\": {\\n+ \\\"version\\\": \\\"1.1.3\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.3.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-onrWn/72lQoEucDmJnr8uczSNTujT0vJnA/X5+3AkChVPowr8n1yvIKIabhWyMQeMvvmdpsvcyDqx3X1LEXCPg==\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"@radix-ui/primitive\\\": \\\"1.1.1\\\",\\n+ \\\"@radix-ui/react-compose-refs\\\": \\\"1.1.1\\\",\\n+ \\\"@radix-ui/react-primitive\\\": \\\"2.0.1\\\",\\n+ \\\"@radix-ui/react-use-callback-ref\\\": \\\"1.1.0\\\",\\n+ \\\"@radix-ui/react-use-escape-keydown\\\": \\\"1.1.0\\\"\\n+ },\\n+ \\\"peerDependencies\\\": {\\n+ \\\"@types/react\\\": \\\"*\\\",\\n+ \\\"@types/react-dom\\\": \\\"*\\\",\\n+ \\\"react\\\": \\\"^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\\\",\\n+ \\\"react-dom\\\": \\\"^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\\\"\\n+ },\\n+ \\\"peerDependenciesMeta\\\": {\\n+ \\\"@types/react\\\": {\\n+ \\\"optional\\\": true\\n+ },\\n+ \\\"@types/react-dom\\\": {\\n+ \\\"optional\\\": true\\n+ }\\n+ }\\n+ },\\n \\\"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-guards\\\": {\\n \\\"version\\\": \\\"1.1.1\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.1.tgz\\\",\\n@@ -4933,12 +4761,12 @@\\n }\\n },\\n \\\"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-focus-scope\\\": {\\n- \\\"version\\\": \\\"1.1.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-200UD8zylvEyL8Bx+z76RJnASR2gRMuxlgFCPAe/Q/679a/r0eK3MBVYMb7vZODZcffZBdob1EGnky78xmVvcA==\\\",\\n+ \\\"version\\\": \\\"1.1.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-01omzJAYRxXdG2/he/+xy+c8a8gCydoQ1yOxnWNcRhrrBW5W+RQJ22EK1SaO8tb3WoUsuEw7mJjBozPzihDFjA==\\\",\\n \\\"dependencies\\\": {\\n- \\\"@radix-ui/react-compose-refs\\\": \\\"1.1.0\\\",\\n- \\\"@radix-ui/react-primitive\\\": \\\"2.0.0\\\",\\n+ \\\"@radix-ui/react-compose-refs\\\": \\\"1.1.1\\\",\\n+ \\\"@radix-ui/react-primitive\\\": \\\"2.0.1\\\",\\n \\\"@radix-ui/react-use-callback-ref\\\": \\\"1.1.0\\\"\\n },\\n \\\"peerDependencies\\\": {\\n@@ -4974,11 +4802,11 @@\\n }\\n },\\n \\\"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-portal\\\": {\\n- \\\"version\\\": \\\"1.1.2\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.2.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-WeDYLGPxJb/5EGBoedyJbT0MpoULmwnIPMJMSldkuiMsBAv7N1cRdsTWZWht9vpPOiN3qyiGAtbK2is47/uMFg==\\\",\\n+ \\\"version\\\": \\\"1.1.3\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.3.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-NciRqhXnGojhT93RPyDaMPfLH3ZSl4jjIFbZQ1b/vxvZEdHsBZ49wP9w8L3HzUQwep01LcWtkUvm0OVB5JAHTw==\\\",\\n \\\"dependencies\\\": {\\n- \\\"@radix-ui/react-primitive\\\": \\\"2.0.0\\\",\\n+ \\\"@radix-ui/react-primitive\\\": \\\"2.0.1\\\",\\n \\\"@radix-ui/react-use-layout-effect\\\": \\\"1.1.0\\\"\\n },\\n \\\"peerDependencies\\\": {\\n@@ -4997,11 +4825,11 @@\\n }\\n },\\n \\\"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-presence\\\": {\\n- \\\"version\\\": \\\"1.1.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-IeFXVi4YS1K0wVZzXNrbaaUvIJ3qdY+/Ih4eHFhWA9SwGR9UDX7Ck8abvL57C4cv3wwMvUE0OG69Qc3NCcTe/A==\\\",\\n+ \\\"version\\\": \\\"1.1.2\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.2.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-18TFr80t5EVgL9x1SwF/YGtfG+l0BS0PRAlCWBDoBEiDQjeKgnNZRVJp/oVBl24sr3Gbfwc/Qpj4OcWTQMsAEg==\\\",\\n \\\"dependencies\\\": {\\n- \\\"@radix-ui/react-compose-refs\\\": \\\"1.1.0\\\",\\n+ \\\"@radix-ui/react-compose-refs\\\": \\\"1.1.1\\\",\\n \\\"@radix-ui/react-use-layout-effect\\\": \\\"1.1.0\\\"\\n },\\n \\\"peerDependencies\\\": {\\n@@ -5020,11 +4848,11 @@\\n }\\n },\\n \\\"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive\\\": {\\n- \\\"version\\\": \\\"2.0.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==\\\",\\n+ \\\"version\\\": \\\"2.0.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-sHCWTtxwNn3L3fH8qAfnF3WbUZycW93SM1j3NFDzXBiz8D6F5UTTy8G1+WFEaiCdvCVRJWj6N2R4Xq6HdiHmDg==\\\",\\n \\\"dependencies\\\": {\\n- \\\"@radix-ui/react-slot\\\": \\\"1.1.0\\\"\\n+ \\\"@radix-ui/react-slot\\\": \\\"1.1.1\\\"\\n },\\n \\\"peerDependencies\\\": {\\n \\\"@types/react\\\": \\\"*\\\",\\n@@ -5041,23 +4869,6 @@\\n }\\n }\\n },\\n- \\\"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot\\\": {\\n- \\\"version\\\": \\\"1.1.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==\\\",\\n- \\\"dependencies\\\": {\\n- \\\"@radix-ui/react-compose-refs\\\": \\\"1.1.0\\\"\\n- },\\n- \\\"peerDependencies\\\": {\\n- \\\"@types/react\\\": \\\"*\\\",\\n- \\\"react\\\": \\\"^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\\\"\\n- },\\n- \\\"peerDependenciesMeta\\\": {\\n- \\\"@types/react\\\": {\\n- \\\"optional\\\": true\\n- }\\n- }\\n- },\\n \\\"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-use-callback-ref\\\": {\\n \\\"version\\\": \\\"1.1.0\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.0.tgz\\\",\\n@@ -5104,22 +4915,22 @@\\n }\\n },\\n \\\"node_modules/@radix-ui/react-dialog/node_modules/react-remove-scroll\\\": {\\n- \\\"version\\\": \\\"2.6.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-I2U4JVEsQenxDAKaVa3VZ/JeJZe0/2DxPWL8Tj8yLKctQJQiZM52pn/GWFpSp8dftjM3pSAHVJZscAnC/y+ySQ==\\\",\\n+ \\\"version\\\": \\\"2.6.2\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.6.2.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-KmONPx5fnlXYJQqC62Q+lwIeAk64ws/cUw6omIumRzMRPqgnYqhSSti99nbj0Ry13bv7dF+BKn7NB+OqkdZGTw==\\\",\\n \\\"dependencies\\\": {\\n- \\\"react-remove-scroll-bar\\\": \\\"^2.3.6\\\",\\n+ \\\"react-remove-scroll-bar\\\": \\\"^2.3.7\\\",\\n \\\"react-style-singleton\\\": \\\"^2.2.1\\\",\\n \\\"tslib\\\": \\\"^2.1.0\\\",\\n- \\\"use-callback-ref\\\": \\\"^1.3.0\\\",\\n+ \\\"use-callback-ref\\\": \\\"^1.3.3\\\",\\n \\\"use-sidecar\\\": \\\"^1.1.2\\\"\\n },\\n \\\"engines\\\": {\\n \\\"node\\\": \\\">=10\\\"\\n },\\n \\\"peerDependencies\\\": {\\n- \\\"@types/react\\\": \\\"^16.8.0 || ^17.0.0 || ^18.0.0\\\",\\n- \\\"react\\\": \\\"^16.8.0 || ^17.0.0 || ^18.0.0\\\"\\n+ \\\"@types/react\\\": \\\"*\\\",\\n+ \\\"react\\\": \\\"^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc\\\"\\n },\\n \\\"peerDependenciesMeta\\\": {\\n \\\"@types/react\\\": {\\n@@ -5996,6 +5807,24 @@\\n }\\n }\\n },\\n+ \\\"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot\\\": {\\n+ \\\"version\\\": \\\"1.0.2\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"@babel/runtime\\\": \\\"^7.13.10\\\",\\n+ \\\"@radix-ui/react-compose-refs\\\": \\\"1.0.1\\\"\\n+ },\\n+ \\\"peerDependencies\\\": {\\n+ \\\"@types/react\\\": \\\"*\\\",\\n+ \\\"react\\\": \\\"^16.8 || ^17.0 || ^18.0\\\"\\n+ },\\n+ \\\"peerDependenciesMeta\\\": {\\n+ \\\"@types/react\\\": {\\n+ \\\"optional\\\": true\\n+ }\\n+ }\\n+ },\\n \\\"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-use-escape-keydown\\\": {\\n \\\"version\\\": \\\"1.0.3\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz\\\",\\n@@ -6116,6 +5945,24 @@\\n }\\n }\\n },\\n+ \\\"node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot\\\": {\\n+ \\\"version\\\": \\\"1.0.2\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"@babel/runtime\\\": \\\"^7.13.10\\\",\\n+ \\\"@radix-ui/react-compose-refs\\\": \\\"1.0.1\\\"\\n+ },\\n+ \\\"peerDependencies\\\": {\\n+ \\\"@types/react\\\": \\\"*\\\",\\n+ \\\"react\\\": \\\"^16.8 || ^17.0 || ^18.0\\\"\\n+ },\\n+ \\\"peerDependenciesMeta\\\": {\\n+ \\\"@types/react\\\": {\\n+ \\\"optional\\\": true\\n+ }\\n+ }\\n+ },\\n \\\"node_modules/@radix-ui/react-progress\\\": {\\n \\\"version\\\": \\\"1.0.3\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.0.3.tgz\\\",\\n@@ -6614,16 +6461,29 @@\\n }\\n },\\n \\\"node_modules/@radix-ui/react-slot\\\": {\\n- \\\"version\\\": \\\"1.0.2\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==\\\",\\n+ \\\"version\\\": \\\"1.1.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-RApLLOcINYJA+dMVbOju7MYv1Mb2EBp2nH4HdDzXTSyaR5optlm6Otrz1euW3HbdOR8UmmFK06TD+A9frYWv+g==\\\",\\n \\\"dependencies\\\": {\\n- \\\"@babel/runtime\\\": \\\"^7.13.10\\\",\\n- \\\"@radix-ui/react-compose-refs\\\": \\\"1.0.1\\\"\\n+ \\\"@radix-ui/react-compose-refs\\\": \\\"1.1.1\\\"\\n },\\n \\\"peerDependencies\\\": {\\n \\\"@types/react\\\": \\\"*\\\",\\n- \\\"react\\\": \\\"^16.8 || ^17.0 || ^18.0\\\"\\n+ \\\"react\\\": \\\"^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\\\"\\n+ },\\n+ \\\"peerDependenciesMeta\\\": {\\n+ \\\"@types/react\\\": {\\n+ \\\"optional\\\": true\\n+ }\\n+ }\\n+ },\\n+ \\\"node_modules/@radix-ui/react-slot/node_modules/@radix-ui/react-compose-refs\\\": {\\n+ \\\"version\\\": \\\"1.1.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-Y9VzoRDSJtgFMUCoiZBDVo084VQ5hfpXxVE+NgkdNsjiDBByiImMZKKhxMwCbdHvhlENG6a833CbFkOQvTricw==\\\",\\n+ \\\"peerDependencies\\\": {\\n+ \\\"@types/react\\\": \\\"*\\\",\\n+ \\\"react\\\": \\\"^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc\\\"\\n },\\n \\\"peerDependenciesMeta\\\": {\\n \\\"@types/react\\\": {\\n@@ -7258,6 +7118,24 @@\\n }\\n }\\n },\\n+ \\\"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot\\\": {\\n+ \\\"version\\\": \\\"1.0.2\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"@babel/runtime\\\": \\\"^7.13.10\\\",\\n+ \\\"@radix-ui/react-compose-refs\\\": \\\"1.0.1\\\"\\n+ },\\n+ \\\"peerDependencies\\\": {\\n+ \\\"@types/react\\\": \\\"*\\\",\\n+ \\\"react\\\": \\\"^16.8 || ^17.0 || ^18.0\\\"\\n+ },\\n+ \\\"peerDependenciesMeta\\\": {\\n+ \\\"@types/react\\\": {\\n+ \\\"optional\\\": true\\n+ }\\n+ }\\n+ },\\n \\\"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-use-escape-keydown\\\": {\\n \\\"version\\\": \\\"1.0.3\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz\\\",\\n@@ -7996,12 +7874,13 @@\\n }\\n },\\n \\\"node_modules/@tootallnate/once\\\": {\\n- \\\"version\\\": \\\"1.1.2\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==\\\",\\n+ \\\"version\\\": \\\"2.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==\\\",\\n+ \\\"dev\\\": true,\\n \\\"license\\\": \\\"MIT\\\",\\n \\\"engines\\\": {\\n- \\\"node\\\": \\\">= 6\\\"\\n+ \\\"node\\\": \\\">= 10\\\"\\n }\\n },\\n \\\"node_modules/@trysound/sax\\\": {\\n@@ -8255,6 +8134,18 @@\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz\\\",\\n \\\"integrity\\\": \\\"sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==\\\"\\n },\\n+ \\\"node_modules/@types/jsdom\\\": {\\n+ \\\"version\\\": \\\"20.0.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@types/jsdom/-/jsdom-20.0.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==\\\",\\n+ \\\"dev\\\": true,\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"@types/node\\\": \\\"*\\\",\\n+ \\\"@types/tough-cookie\\\": \\\"*\\\",\\n+ \\\"parse5\\\": \\\"^7.0.0\\\"\\n+ }\\n+ },\\n \\\"node_modules/@types/json-schema\\\": {\\n \\\"version\\\": \\\"7.0.15\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz\\\",\\n@@ -8437,6 +8328,13 @@\\n \\\"@types/jest\\\": \\\"*\\\"\\n }\\n },\\n+ \\\"node_modules/@types/tough-cookie\\\": {\\n+ \\\"version\\\": \\\"4.0.5\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==\\\",\\n+ \\\"dev\\\": true,\\n+ \\\"license\\\": \\\"MIT\\\"\\n+ },\\n \\\"node_modules/@types/trusted-types\\\": {\\n \\\"version\\\": \\\"2.0.7\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz\\\",\\n@@ -8935,25 +8833,14 @@\\n }\\n },\\n \\\"node_modules/acorn-globals\\\": {\\n- \\\"version\\\": \\\"6.0.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==\\\",\\n+ \\\"version\\\": \\\"7.0.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/acorn-globals/-/acorn-globals-7.0.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==\\\",\\n+ \\\"dev\\\": true,\\n \\\"license\\\": \\\"MIT\\\",\\n \\\"dependencies\\\": {\\n- \\\"acorn\\\": \\\"^7.1.1\\\",\\n- \\\"acorn-walk\\\": \\\"^7.1.1\\\"\\n- }\\n- },\\n- \\\"node_modules/acorn-globals/node_modules/acorn\\\": {\\n- \\\"version\\\": \\\"7.4.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==\\\",\\n- \\\"license\\\": \\\"MIT\\\",\\n- \\\"bin\\\": {\\n- \\\"acorn\\\": \\\"bin/acorn\\\"\\n- },\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">=0.4.0\\\"\\n+ \\\"acorn\\\": \\\"^8.1.0\\\",\\n+ \\\"acorn-walk\\\": \\\"^8.0.2\\\"\\n }\\n },\\n \\\"node_modules/acorn-import-assertions\\\": {\\n@@ -8973,10 +8860,14 @@\\n }\\n },\\n \\\"node_modules/acorn-walk\\\": {\\n- \\\"version\\\": \\\"7.2.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==\\\",\\n+ \\\"version\\\": \\\"8.3.4\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==\\\",\\n+ \\\"dev\\\": true,\\n \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"acorn\\\": \\\"^8.11.0\\\"\\n+ },\\n \\\"engines\\\": {\\n \\\"node\\\": \\\">=0.4.0\\\"\\n }\\n@@ -11223,9 +11114,10 @@\\n }\\n },\\n \\\"node_modules/cssom\\\": {\\n- \\\"version\\\": \\\"0.4.4\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==\\\",\\n+ \\\"version\\\": \\\"0.5.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==\\\",\\n+ \\\"dev\\\": true,\\n \\\"license\\\": \\\"MIT\\\"\\n },\\n \\\"node_modules/cssstyle\\\": {\\n@@ -11266,17 +11158,18 @@\\n \\\"integrity\\\": \\\"sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==\\\"\\n },\\n \\\"node_modules/data-urls\\\": {\\n- \\\"version\\\": \\\"2.0.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==\\\",\\n+ \\\"version\\\": \\\"3.0.2\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==\\\",\\n+ \\\"dev\\\": true,\\n \\\"license\\\": \\\"MIT\\\",\\n \\\"dependencies\\\": {\\n- \\\"abab\\\": \\\"^2.0.3\\\",\\n- \\\"whatwg-mimetype\\\": \\\"^2.3.0\\\",\\n- \\\"whatwg-url\\\": \\\"^8.0.0\\\"\\n+ \\\"abab\\\": \\\"^2.0.6\\\",\\n+ \\\"whatwg-mimetype\\\": \\\"^3.0.0\\\",\\n+ \\\"whatwg-url\\\": \\\"^11.0.0\\\"\\n },\\n \\\"engines\\\": {\\n- \\\"node\\\": \\\">=10\\\"\\n+ \\\"node\\\": \\\">=12\\\"\\n }\\n },\\n \\\"node_modules/dayjs\\\": {\\n@@ -11302,9 +11195,9 @@\\n }\\n },\\n \\\"node_modules/decimal.js\\\": {\\n- \\\"version\\\": \\\"10.4.3\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==\\\",\\n+ \\\"version\\\": \\\"10.5.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==\\\",\\n \\\"license\\\": \\\"MIT\\\"\\n },\\n \\\"node_modules/decode-named-character-reference\\\": {\\n@@ -11654,25 +11547,17 @@\\n ]\\n },\\n \\\"node_modules/domexception\\\": {\\n- \\\"version\\\": \\\"2.0.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==\\\",\\n+ \\\"version\\\": \\\"4.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==\\\",\\n \\\"deprecated\\\": \\\"Use your platform's native DOMException instead\\\",\\n+ \\\"dev\\\": true,\\n \\\"license\\\": \\\"MIT\\\",\\n \\\"dependencies\\\": {\\n- \\\"webidl-conversions\\\": \\\"^5.0.0\\\"\\n+ \\\"webidl-conversions\\\": \\\"^7.0.0\\\"\\n },\\n \\\"engines\\\": {\\n- \\\"node\\\": \\\">=8\\\"\\n- }\\n- },\\n- \\\"node_modules/domexception/node_modules/webidl-conversions\\\": {\\n- \\\"version\\\": \\\"5.0.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==\\\",\\n- \\\"license\\\": \\\"BSD-2-Clause\\\",\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">=8\\\"\\n+ \\\"node\\\": \\\">=12\\\"\\n }\\n },\\n \\\"node_modules/domhandler\\\": {\\n@@ -13927,15 +13812,16 @@\\n }\\n },\\n \\\"node_modules/html-encoding-sniffer\\\": {\\n- \\\"version\\\": \\\"2.0.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==\\\",\\n+ \\\"version\\\": \\\"3.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==\\\",\\n+ \\\"dev\\\": true,\\n \\\"license\\\": \\\"MIT\\\",\\n \\\"dependencies\\\": {\\n- \\\"whatwg-encoding\\\": \\\"^1.0.5\\\"\\n+ \\\"whatwg-encoding\\\": \\\"^2.0.0\\\"\\n },\\n \\\"engines\\\": {\\n- \\\"node\\\": \\\">=10\\\"\\n+ \\\"node\\\": \\\">=12\\\"\\n }\\n },\\n \\\"node_modules/html-entities\\\": {\\n@@ -14096,12 +13982,13 @@\\n }\\n },\\n \\\"node_modules/http-proxy-agent\\\": {\\n- \\\"version\\\": \\\"4.0.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==\\\",\\n+ \\\"version\\\": \\\"5.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==\\\",\\n+ \\\"dev\\\": true,\\n \\\"license\\\": \\\"MIT\\\",\\n \\\"dependencies\\\": {\\n- \\\"@tootallnate/once\\\": \\\"1\\\",\\n+ \\\"@tootallnate/once\\\": \\\"2\\\",\\n \\\"agent-base\\\": \\\"6\\\",\\n \\\"debug\\\": \\\"4\\\"\\n },\\n@@ -14264,6 +14151,13 @@\\n \\\"node\\\": \\\">= 4\\\"\\n }\\n },\\n+ \\\"node_modules/ignore-by-default\\\": {\\n+ \\\"version\\\": \\\"1.0.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==\\\",\\n+ \\\"dev\\\": true,\\n+ \\\"license\\\": \\\"ISC\\\"\\n+ },\\n \\\"node_modules/immer\\\": {\\n \\\"version\\\": \\\"9.0.21\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/immer/-/immer-9.0.21.tgz\\\",\\n@@ -14374,14 +14268,6 @@\\n \\\"node\\\": \\\">= 0.4\\\"\\n }\\n },\\n- \\\"node_modules/invariant\\\": {\\n- \\\"version\\\": \\\"2.2.4\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==\\\",\\n- \\\"dependencies\\\": {\\n- \\\"loose-envify\\\": \\\"^1.0.0\\\"\\n- }\\n- },\\n \\\"node_modules/ioredis\\\": {\\n \\\"version\\\": \\\"5.4.1\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz\\\",\\n@@ -15643,162 +15529,31 @@\\n \\\"license\\\": \\\"MIT\\\"\\n },\\n \\\"node_modules/jest-environment-jsdom\\\": {\\n- \\\"version\\\": \\\"27.5.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==\\\",\\n- \\\"license\\\": \\\"MIT\\\",\\n- \\\"dependencies\\\": {\\n- \\\"@jest/environment\\\": \\\"^27.5.1\\\",\\n- \\\"@jest/fake-timers\\\": \\\"^27.5.1\\\",\\n- \\\"@jest/types\\\": \\\"^27.5.1\\\",\\n- \\\"@types/node\\\": \\\"*\\\",\\n- \\\"jest-mock\\\": \\\"^27.5.1\\\",\\n- \\\"jest-util\\\": \\\"^27.5.1\\\",\\n- \\\"jsdom\\\": \\\"^16.6.0\\\"\\n- },\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\"^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0\\\"\\n- }\\n- },\\n- \\\"node_modules/jest-environment-jsdom/node_modules/@jest/environment\\\": {\\n- \\\"version\\\": \\\"27.5.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@jest/environment/-/environment-27.5.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==\\\",\\n- \\\"license\\\": \\\"MIT\\\",\\n- \\\"dependencies\\\": {\\n- \\\"@jest/fake-timers\\\": \\\"^27.5.1\\\",\\n- \\\"@jest/types\\\": \\\"^27.5.1\\\",\\n- \\\"@types/node\\\": \\\"*\\\",\\n- \\\"jest-mock\\\": \\\"^27.5.1\\\"\\n- },\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\"^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0\\\"\\n- }\\n- },\\n- \\\"node_modules/jest-environment-jsdom/node_modules/@jest/fake-timers\\\": {\\n- \\\"version\\\": \\\"27.5.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-27.5.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==\\\",\\n- \\\"license\\\": \\\"MIT\\\",\\n- \\\"dependencies\\\": {\\n- \\\"@jest/types\\\": \\\"^27.5.1\\\",\\n- \\\"@sinonjs/fake-timers\\\": \\\"^8.0.1\\\",\\n- \\\"@types/node\\\": \\\"*\\\",\\n- \\\"jest-message-util\\\": \\\"^27.5.1\\\",\\n- \\\"jest-mock\\\": \\\"^27.5.1\\\",\\n- \\\"jest-util\\\": \\\"^27.5.1\\\"\\n- },\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\"^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0\\\"\\n- }\\n- },\\n- \\\"node_modules/jest-environment-jsdom/node_modules/@jest/types\\\": {\\n- \\\"version\\\": \\\"27.5.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@jest/types/-/types-27.5.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==\\\",\\n+ \\\"version\\\": \\\"29.7.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-29.7.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==\\\",\\n+ \\\"dev\\\": true,\\n \\\"license\\\": \\\"MIT\\\",\\n \\\"dependencies\\\": {\\n- \\\"@types/istanbul-lib-coverage\\\": \\\"^2.0.0\\\",\\n- \\\"@types/istanbul-reports\\\": \\\"^3.0.0\\\",\\n+ \\\"@jest/environment\\\": \\\"^29.7.0\\\",\\n+ \\\"@jest/fake-timers\\\": \\\"^29.7.0\\\",\\n+ \\\"@jest/types\\\": \\\"^29.6.3\\\",\\n+ \\\"@types/jsdom\\\": \\\"^20.0.0\\\",\\n \\\"@types/node\\\": \\\"*\\\",\\n- \\\"@types/yargs\\\": \\\"^16.0.0\\\",\\n- \\\"chalk\\\": \\\"^4.0.0\\\"\\n- },\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\"^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0\\\"\\n- }\\n- },\\n- \\\"node_modules/jest-environment-jsdom/node_modules/@sinonjs/commons\\\": {\\n- \\\"version\\\": \\\"1.8.6\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==\\\",\\n- \\\"license\\\": \\\"BSD-3-Clause\\\",\\n- \\\"dependencies\\\": {\\n- \\\"type-detect\\\": \\\"4.0.8\\\"\\n- }\\n- },\\n- \\\"node_modules/jest-environment-jsdom/node_modules/@sinonjs/fake-timers\\\": {\\n- \\\"version\\\": \\\"8.1.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==\\\",\\n- \\\"license\\\": \\\"BSD-3-Clause\\\",\\n- \\\"dependencies\\\": {\\n- \\\"@sinonjs/commons\\\": \\\"^1.7.0\\\"\\n- }\\n- },\\n- \\\"node_modules/jest-environment-jsdom/node_modules/@types/yargs\\\": {\\n- \\\"version\\\": \\\"16.0.9\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-tHhzvkFXZQeTECenFoRljLBYPZJ7jAVxqqtEI0qTLOmuultnFp4I9yKE17vTuhf7BkhCu7I4XuemPgikDVuYqA==\\\",\\n- \\\"license\\\": \\\"MIT\\\",\\n- \\\"dependencies\\\": {\\n- \\\"@types/yargs-parser\\\": \\\"*\\\"\\n- }\\n- },\\n- \\\"node_modules/jest-environment-jsdom/node_modules/chalk\\\": {\\n- \\\"version\\\": \\\"4.1.2\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==\\\",\\n- \\\"license\\\": \\\"MIT\\\",\\n- \\\"dependencies\\\": {\\n- \\\"ansi-styles\\\": \\\"^4.1.0\\\",\\n- \\\"supports-color\\\": \\\"^7.1.0\\\"\\n- },\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">=10\\\"\\n- },\\n- \\\"funding\\\": {\\n- \\\"url\\\": \\\"https://github.com/chalk/chalk?sponsor=1\\\"\\n- }\\n- },\\n- \\\"node_modules/jest-environment-jsdom/node_modules/jest-message-util\\\": {\\n- \\\"version\\\": \\\"27.5.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.5.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==\\\",\\n- \\\"license\\\": \\\"MIT\\\",\\n- \\\"dependencies\\\": {\\n- \\\"@babel/code-frame\\\": \\\"^7.12.13\\\",\\n- \\\"@jest/types\\\": \\\"^27.5.1\\\",\\n- \\\"@types/stack-utils\\\": \\\"^2.0.0\\\",\\n- \\\"chalk\\\": \\\"^4.0.0\\\",\\n- \\\"graceful-fs\\\": \\\"^4.2.9\\\",\\n- \\\"micromatch\\\": \\\"^4.0.4\\\",\\n- \\\"pretty-format\\\": \\\"^27.5.1\\\",\\n- \\\"slash\\\": \\\"^3.0.0\\\",\\n- \\\"stack-utils\\\": \\\"^2.0.3\\\"\\n+ \\\"jest-mock\\\": \\\"^29.7.0\\\",\\n+ \\\"jest-util\\\": \\\"^29.7.0\\\",\\n+ \\\"jsdom\\\": \\\"^20.0.0\\\"\\n },\\n \\\"engines\\\": {\\n- \\\"node\\\": \\\"^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0\\\"\\n- }\\n- },\\n- \\\"node_modules/jest-environment-jsdom/node_modules/jest-mock\\\": {\\n- \\\"version\\\": \\\"27.5.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/jest-mock/-/jest-mock-27.5.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==\\\",\\n- \\\"license\\\": \\\"MIT\\\",\\n- \\\"dependencies\\\": {\\n- \\\"@jest/types\\\": \\\"^27.5.1\\\",\\n- \\\"@types/node\\\": \\\"*\\\"\\n+ \\\"node\\\": \\\"^14.15.0 || ^16.10.0 || >=18.0.0\\\"\\n },\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\"^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0\\\"\\n- }\\n- },\\n- \\\"node_modules/jest-environment-jsdom/node_modules/jest-util\\\": {\\n- \\\"version\\\": \\\"27.5.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/jest-util/-/jest-util-27.5.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==\\\",\\n- \\\"license\\\": \\\"MIT\\\",\\n- \\\"dependencies\\\": {\\n- \\\"@jest/types\\\": \\\"^27.5.1\\\",\\n- \\\"@types/node\\\": \\\"*\\\",\\n- \\\"chalk\\\": \\\"^4.0.0\\\",\\n- \\\"ci-info\\\": \\\"^3.2.0\\\",\\n- \\\"graceful-fs\\\": \\\"^4.2.9\\\",\\n- \\\"picomatch\\\": \\\"^2.2.3\\\"\\n+ \\\"peerDependencies\\\": {\\n+ \\\"canvas\\\": \\\"^2.5.0\\\"\\n },\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\"^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0\\\"\\n+ \\\"peerDependenciesMeta\\\": {\\n+ \\\"canvas\\\": {\\n+ \\\"optional\\\": true\\n+ }\\n }\\n },\\n \\\"node_modules/jest-environment-node\\\": {\\n@@ -17683,41 +17438,41 @@\\n }\\n },\\n \\\"node_modules/jsdom\\\": {\\n- \\\"version\\\": \\\"16.7.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==\\\",\\n+ \\\"version\\\": \\\"20.0.3\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/jsdom/-/jsdom-20.0.3.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==\\\",\\n+ \\\"dev\\\": true,\\n \\\"license\\\": \\\"MIT\\\",\\n \\\"dependencies\\\": {\\n- \\\"abab\\\": \\\"^2.0.5\\\",\\n- \\\"acorn\\\": \\\"^8.2.4\\\",\\n- \\\"acorn-globals\\\": \\\"^6.0.0\\\",\\n- \\\"cssom\\\": \\\"^0.4.4\\\",\\n+ \\\"abab\\\": \\\"^2.0.6\\\",\\n+ \\\"acorn\\\": \\\"^8.8.1\\\",\\n+ \\\"acorn-globals\\\": \\\"^7.0.0\\\",\\n+ \\\"cssom\\\": \\\"^0.5.0\\\",\\n \\\"cssstyle\\\": \\\"^2.3.0\\\",\\n- \\\"data-urls\\\": \\\"^2.0.0\\\",\\n- \\\"decimal.js\\\": \\\"^10.2.1\\\",\\n- \\\"domexception\\\": \\\"^2.0.1\\\",\\n+ \\\"data-urls\\\": \\\"^3.0.2\\\",\\n+ \\\"decimal.js\\\": \\\"^10.4.2\\\",\\n+ \\\"domexception\\\": \\\"^4.0.0\\\",\\n \\\"escodegen\\\": \\\"^2.0.0\\\",\\n- \\\"form-data\\\": \\\"^3.0.0\\\",\\n- \\\"html-encoding-sniffer\\\": \\\"^2.0.1\\\",\\n- \\\"http-proxy-agent\\\": \\\"^4.0.1\\\",\\n- \\\"https-proxy-agent\\\": \\\"^5.0.0\\\",\\n+ \\\"form-data\\\": \\\"^4.0.0\\\",\\n+ \\\"html-encoding-sniffer\\\": \\\"^3.0.0\\\",\\n+ \\\"http-proxy-agent\\\": \\\"^5.0.0\\\",\\n+ \\\"https-proxy-agent\\\": \\\"^5.0.1\\\",\\n \\\"is-potential-custom-element-name\\\": \\\"^1.0.1\\\",\\n- \\\"nwsapi\\\": \\\"^2.2.0\\\",\\n- \\\"parse5\\\": \\\"6.0.1\\\",\\n- \\\"saxes\\\": \\\"^5.0.1\\\",\\n+ \\\"nwsapi\\\": \\\"^2.2.2\\\",\\n+ \\\"parse5\\\": \\\"^7.1.1\\\",\\n+ \\\"saxes\\\": \\\"^6.0.0\\\",\\n \\\"symbol-tree\\\": \\\"^3.2.4\\\",\\n- \\\"tough-cookie\\\": \\\"^4.0.0\\\",\\n- \\\"w3c-hr-time\\\": \\\"^1.0.2\\\",\\n- \\\"w3c-xmlserializer\\\": \\\"^2.0.0\\\",\\n- \\\"webidl-conversions\\\": \\\"^6.1.0\\\",\\n- \\\"whatwg-encoding\\\": \\\"^1.0.5\\\",\\n- \\\"whatwg-mimetype\\\": \\\"^2.3.0\\\",\\n- \\\"whatwg-url\\\": \\\"^8.5.0\\\",\\n- \\\"ws\\\": \\\"^7.4.6\\\",\\n- \\\"xml-name-validator\\\": \\\"^3.0.0\\\"\\n+ \\\"tough-cookie\\\": \\\"^4.1.2\\\",\\n+ \\\"w3c-xmlserializer\\\": \\\"^4.0.0\\\",\\n+ \\\"webidl-conversions\\\": \\\"^7.0.0\\\",\\n+ \\\"whatwg-encoding\\\": \\\"^2.0.0\\\",\\n+ \\\"whatwg-mimetype\\\": \\\"^3.0.0\\\",\\n+ \\\"whatwg-url\\\": \\\"^11.0.0\\\",\\n+ \\\"ws\\\": \\\"^8.11.0\\\",\\n+ \\\"xml-name-validator\\\": \\\"^4.0.0\\\"\\n },\\n \\\"engines\\\": {\\n- \\\"node\\\": \\\">=10\\\"\\n+ \\\"node\\\": \\\">=14\\\"\\n },\\n \\\"peerDependencies\\\": {\\n \\\"canvas\\\": \\\"^2.5.0\\\"\\n@@ -17728,26 +17483,6 @@\\n }\\n }\\n },\\n- \\\"node_modules/jsdom/node_modules/form-data\\\": {\\n- \\\"version\\\": \\\"3.0.2\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/form-data/-/form-data-3.0.2.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-sJe+TQb2vIaIyO783qN6BlMYWMw3WBOHA1Ay2qxsnjuafEOQFJ2JakedOQirT6D5XPRxDvS7AHYyem9fTpb4LQ==\\\",\\n- \\\"license\\\": \\\"MIT\\\",\\n- \\\"dependencies\\\": {\\n- \\\"asynckit\\\": \\\"^0.4.0\\\",\\n- \\\"combined-stream\\\": \\\"^1.0.8\\\",\\n- \\\"mime-types\\\": \\\"^2.1.12\\\"\\n- },\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">= 6\\\"\\n- }\\n- },\\n- \\\"node_modules/jsdom/node_modules/parse5\\\": {\\n- \\\"version\\\": \\\"6.0.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==\\\",\\n- \\\"license\\\": \\\"MIT\\\"\\n- },\\n \\\"node_modules/jsesc\\\": {\\n \\\"version\\\": \\\"3.1.0\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz\\\",\\n@@ -18167,11 +17902,6 @@\\n \\\"node\\\": \\\">= 18\\\"\\n }\\n },\\n- \\\"node_modules/math-random\\\": {\\n- \\\"version\\\": \\\"2.0.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/math-random/-/math-random-2.0.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-oIEbWiVDxDpl5tIF4S6zYS9JExhh3bun3uLb3YAinHPTlRtW4g1S66LtJrJ4Npq8dgIa8CLK5iPVah5n4n0s2w==\\\"\\n- },\\n \\\"node_modules/mdast-util-directive\\\": {\\n \\\"version\\\": \\\"3.0.0\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/mdast-util-directive/-/mdast-util-directive-3.0.0.tgz\\\",\\n@@ -19365,14 +19095,6 @@\\n \\\"node\\\": \\\">=14\\\"\\n }\\n },\\n- \\\"node_modules/mongodb-connection-string-url/node_modules/webidl-conversions\\\": {\\n- \\\"version\\\": \\\"7.0.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==\\\",\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">=12\\\"\\n- }\\n- },\\n \\\"node_modules/mongodb-connection-string-url/node_modules/whatwg-url\\\": {\\n \\\"version\\\": \\\"13.0.0\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz\\\",\\n@@ -19789,6 +19511,58 @@\\n \\\"integrity\\\": \\\"sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==\\\",\\n \\\"license\\\": \\\"MIT\\\"\\n },\\n+ \\\"node_modules/nodemon\\\": {\\n+ \\\"version\\\": \\\"3.1.9\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==\\\",\\n+ \\\"dev\\\": true,\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"chokidar\\\": \\\"^3.5.2\\\",\\n+ \\\"debug\\\": \\\"^4\\\",\\n+ \\\"ignore-by-default\\\": \\\"^1.0.1\\\",\\n+ \\\"minimatch\\\": \\\"^3.1.2\\\",\\n+ \\\"pstree.remy\\\": \\\"^1.1.8\\\",\\n+ \\\"semver\\\": \\\"^7.5.3\\\",\\n+ \\\"simple-update-notifier\\\": \\\"^2.0.0\\\",\\n+ \\\"supports-color\\\": \\\"^5.5.0\\\",\\n+ \\\"touch\\\": \\\"^3.1.0\\\",\\n+ \\\"undefsafe\\\": \\\"^2.0.5\\\"\\n+ },\\n+ \\\"bin\\\": {\\n+ \\\"nodemon\\\": \\\"bin/nodemon.js\\\"\\n+ },\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=10\\\"\\n+ },\\n+ \\\"funding\\\": {\\n+ \\\"type\\\": \\\"opencollective\\\",\\n+ \\\"url\\\": \\\"https://opencollective.com/nodemon\\\"\\n+ }\\n+ },\\n+ \\\"node_modules/nodemon/node_modules/has-flag\\\": {\\n+ \\\"version\\\": \\\"3.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==\\\",\\n+ \\\"dev\\\": true,\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=4\\\"\\n+ }\\n+ },\\n+ \\\"node_modules/nodemon/node_modules/supports-color\\\": {\\n+ \\\"version\\\": \\\"5.5.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==\\\",\\n+ \\\"dev\\\": true,\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"has-flag\\\": \\\"^3.0.0\\\"\\n+ },\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=4\\\"\\n+ }\\n+ },\\n \\\"node_modules/normalize-path\\\": {\\n \\\"version\\\": \\\"3.0.0\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz\\\",\\n@@ -21889,6 +21663,13 @@\\n \\\"url\\\": \\\"https://github.com/sponsors/lupomontero\\\"\\n }\\n },\\n+ \\\"node_modules/pstree.remy\\\": {\\n+ \\\"version\\\": \\\"1.1.8\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==\\\",\\n+ \\\"dev\\\": true,\\n+ \\\"license\\\": \\\"MIT\\\"\\n+ },\\n \\\"node_modules/pump\\\": {\\n \\\"version\\\": \\\"3.0.0\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/pump/-/pump-3.0.0.tgz\\\",\\n@@ -22269,12 +22050,13 @@\\n }\\n },\\n \\\"node_modules/react-intersection-observer\\\": {\\n- \\\"version\\\": \\\"9.13.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.13.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-tSzDaTy0qwNPLJHg8XZhlyHTgGW6drFKTtvjdL+p6um12rcnp8Z5XstE+QNBJ7c64n5o0Lj4ilUleA41bmDoMw==\\\",\\n+ \\\"version\\\": \\\"9.15.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.15.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-vGrqYEVWXfH+AGu241uzfUpNK4HAdhCkSAyFdkMb9VWWXs6mxzBLpWCxEy9YcnDNY2g9eO6z7qUtTBdA9hc8pA==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n \\\"peerDependencies\\\": {\\n- \\\"react\\\": \\\"^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0\\\",\\n- \\\"react-dom\\\": \\\"^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0\\\"\\n+ \\\"react\\\": \\\"^17.0.0 || ^18.0.0 || ^19.0.0\\\",\\n+ \\\"react-dom\\\": \\\"^17.0.0 || ^18.0.0 || ^19.0.0\\\"\\n },\\n \\\"peerDependenciesMeta\\\": {\\n \\\"react-dom\\\": {\\n@@ -22444,19 +22226,19 @@\\n }\\n },\\n \\\"node_modules/react-remove-scroll-bar\\\": {\\n- \\\"version\\\": \\\"2.3.6\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==\\\",\\n+ \\\"version\\\": \\\"2.3.8\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==\\\",\\n \\\"dependencies\\\": {\\n- \\\"react-style-singleton\\\": \\\"^2.2.1\\\",\\n+ \\\"react-style-singleton\\\": \\\"^2.2.2\\\",\\n \\\"tslib\\\": \\\"^2.0.0\\\"\\n },\\n \\\"engines\\\": {\\n \\\"node\\\": \\\">=10\\\"\\n },\\n \\\"peerDependencies\\\": {\\n- \\\"@types/react\\\": \\\"^16.8.0 || ^17.0.0 || ^18.0.0\\\",\\n- \\\"react\\\": \\\"^16.8.0 || ^17.0.0 || ^18.0.0\\\"\\n+ \\\"@types/react\\\": \\\"*\\\",\\n+ \\\"react\\\": \\\"^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0\\\"\\n },\\n \\\"peerDependenciesMeta\\\": {\\n \\\"@types/react\\\": {\\n@@ -22824,6 +22606,15 @@\\n \\\"@sinonjs/commons\\\": \\\"^1.7.0\\\"\\n }\\n },\\n+ \\\"node_modules/react-scripts/node_modules/@tootallnate/once\\\": {\\n+ \\\"version\\\": \\\"1.1.2\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">= 6\\\"\\n+ }\\n+ },\\n \\\"node_modules/react-scripts/node_modules/@types/yargs\\\": {\\n \\\"version\\\": \\\"16.0.9\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/@types/yargs/-/yargs-16.0.9.tgz\\\",\\n@@ -22833,6 +22624,37 @@\\n \\\"@types/yargs-parser\\\": \\\"*\\\"\\n }\\n },\\n+ \\\"node_modules/react-scripts/node_modules/acorn-globals\\\": {\\n+ \\\"version\\\": \\\"6.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"acorn\\\": \\\"^7.1.1\\\",\\n+ \\\"acorn-walk\\\": \\\"^7.1.1\\\"\\n+ }\\n+ },\\n+ \\\"node_modules/react-scripts/node_modules/acorn-globals/node_modules/acorn\\\": {\\n+ \\\"version\\\": \\\"7.4.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"bin\\\": {\\n+ \\\"acorn\\\": \\\"bin/acorn\\\"\\n+ },\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=0.4.0\\\"\\n+ }\\n+ },\\n+ \\\"node_modules/react-scripts/node_modules/acorn-walk\\\": {\\n+ \\\"version\\\": \\\"7.2.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=0.4.0\\\"\\n+ }\\n+ },\\n \\\"node_modules/react-scripts/node_modules/babel-jest\\\": {\\n \\\"version\\\": \\\"27.5.1\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/babel-jest/-/babel-jest-27.5.1.tgz\\\",\\n@@ -22924,6 +22746,26 @@\\n \\\"wrap-ansi\\\": \\\"^7.0.0\\\"\\n }\\n },\\n+ \\\"node_modules/react-scripts/node_modules/cssom\\\": {\\n+ \\\"version\\\": \\\"0.4.4\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==\\\",\\n+ \\\"license\\\": \\\"MIT\\\"\\n+ },\\n+ \\\"node_modules/react-scripts/node_modules/data-urls\\\": {\\n+ \\\"version\\\": \\\"2.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"abab\\\": \\\"^2.0.3\\\",\\n+ \\\"whatwg-mimetype\\\": \\\"^2.3.0\\\",\\n+ \\\"whatwg-url\\\": \\\"^8.0.0\\\"\\n+ },\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=10\\\"\\n+ }\\n+ },\\n \\\"node_modules/react-scripts/node_modules/dedent\\\": {\\n \\\"version\\\": \\\"0.7.0\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz\\\",\\n@@ -22939,6 +22781,28 @@\\n \\\"node\\\": \\\"^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0\\\"\\n }\\n },\\n+ \\\"node_modules/react-scripts/node_modules/domexception\\\": {\\n+ \\\"version\\\": \\\"2.0.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==\\\",\\n+ \\\"deprecated\\\": \\\"Use your platform's native DOMException instead\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"webidl-conversions\\\": \\\"^5.0.0\\\"\\n+ },\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=8\\\"\\n+ }\\n+ },\\n+ \\\"node_modules/react-scripts/node_modules/domexception/node_modules/webidl-conversions\\\": {\\n+ \\\"version\\\": \\\"5.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==\\\",\\n+ \\\"license\\\": \\\"BSD-2-Clause\\\",\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=8\\\"\\n+ }\\n+ },\\n \\\"node_modules/react-scripts/node_modules/emittery\\\": {\\n \\\"version\\\": \\\"0.8.1\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz\\\",\\n@@ -22966,6 +22830,58 @@\\n \\\"node\\\": \\\"^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0\\\"\\n }\\n },\\n+ \\\"node_modules/react-scripts/node_modules/form-data\\\": {\\n+ \\\"version\\\": \\\"3.0.2\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/form-data/-/form-data-3.0.2.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-sJe+TQb2vIaIyO783qN6BlMYWMw3WBOHA1Ay2qxsnjuafEOQFJ2JakedOQirT6D5XPRxDvS7AHYyem9fTpb4LQ==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"asynckit\\\": \\\"^0.4.0\\\",\\n+ \\\"combined-stream\\\": \\\"^1.0.8\\\",\\n+ \\\"mime-types\\\": \\\"^2.1.12\\\"\\n+ },\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">= 6\\\"\\n+ }\\n+ },\\n+ \\\"node_modules/react-scripts/node_modules/html-encoding-sniffer\\\": {\\n+ \\\"version\\\": \\\"2.0.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"whatwg-encoding\\\": \\\"^1.0.5\\\"\\n+ },\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=10\\\"\\n+ }\\n+ },\\n+ \\\"node_modules/react-scripts/node_modules/http-proxy-agent\\\": {\\n+ \\\"version\\\": \\\"4.0.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"@tootallnate/once\\\": \\\"1\\\",\\n+ \\\"agent-base\\\": \\\"6\\\",\\n+ \\\"debug\\\": \\\"4\\\"\\n+ },\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">= 6\\\"\\n+ }\\n+ },\\n+ \\\"node_modules/react-scripts/node_modules/iconv-lite\\\": {\\n+ \\\"version\\\": \\\"0.4.24\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"safer-buffer\\\": \\\">= 2.1.2 < 3\\\"\\n+ },\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=0.10.0\\\"\\n+ }\\n+ },\\n \\\"node_modules/react-scripts/node_modules/jest\\\": {\\n \\\"version\\\": \\\"27.5.1\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/jest/-/jest-27.5.1.tgz\\\",\\n@@ -23155,6 +23071,24 @@\\n \\\"node\\\": \\\"^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0\\\"\\n }\\n },\\n+ \\\"node_modules/react-scripts/node_modules/jest-environment-jsdom\\\": {\\n+ \\\"version\\\": \\\"27.5.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"@jest/environment\\\": \\\"^27.5.1\\\",\\n+ \\\"@jest/fake-timers\\\": \\\"^27.5.1\\\",\\n+ \\\"@jest/types\\\": \\\"^27.5.1\\\",\\n+ \\\"@types/node\\\": \\\"*\\\",\\n+ \\\"jest-mock\\\": \\\"^27.5.1\\\",\\n+ \\\"jest-util\\\": \\\"^27.5.1\\\",\\n+ \\\"jsdom\\\": \\\"^16.6.0\\\"\\n+ },\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\"^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0\\\"\\n+ }\\n+ },\\n \\\"node_modules/react-scripts/node_modules/jest-environment-node\\\": {\\n \\\"version\\\": \\\"27.5.1\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-27.5.1.tgz\\\",\\n@@ -23386,9 +23320,61 @@\\n \\\"string-length\\\": \\\"^4.0.1\\\"\\n },\\n \\\"engines\\\": {\\n- \\\"node\\\": \\\"^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0\\\"\\n+ \\\"node\\\": \\\"^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0\\\"\\n+ }\\n+ },\\n+ \\\"node_modules/react-scripts/node_modules/jsdom\\\": {\\n+ \\\"version\\\": \\\"16.7.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/jsdom/-/jsdom-16.7.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"abab\\\": \\\"^2.0.5\\\",\\n+ \\\"acorn\\\": \\\"^8.2.4\\\",\\n+ \\\"acorn-globals\\\": \\\"^6.0.0\\\",\\n+ \\\"cssom\\\": \\\"^0.4.4\\\",\\n+ \\\"cssstyle\\\": \\\"^2.3.0\\\",\\n+ \\\"data-urls\\\": \\\"^2.0.0\\\",\\n+ \\\"decimal.js\\\": \\\"^10.2.1\\\",\\n+ \\\"domexception\\\": \\\"^2.0.1\\\",\\n+ \\\"escodegen\\\": \\\"^2.0.0\\\",\\n+ \\\"form-data\\\": \\\"^3.0.0\\\",\\n+ \\\"html-encoding-sniffer\\\": \\\"^2.0.1\\\",\\n+ \\\"http-proxy-agent\\\": \\\"^4.0.1\\\",\\n+ \\\"https-proxy-agent\\\": \\\"^5.0.0\\\",\\n+ \\\"is-potential-custom-element-name\\\": \\\"^1.0.1\\\",\\n+ \\\"nwsapi\\\": \\\"^2.2.0\\\",\\n+ \\\"parse5\\\": \\\"6.0.1\\\",\\n+ \\\"saxes\\\": \\\"^5.0.1\\\",\\n+ \\\"symbol-tree\\\": \\\"^3.2.4\\\",\\n+ \\\"tough-cookie\\\": \\\"^4.0.0\\\",\\n+ \\\"w3c-hr-time\\\": \\\"^1.0.2\\\",\\n+ \\\"w3c-xmlserializer\\\": \\\"^2.0.0\\\",\\n+ \\\"webidl-conversions\\\": \\\"^6.1.0\\\",\\n+ \\\"whatwg-encoding\\\": \\\"^1.0.5\\\",\\n+ \\\"whatwg-mimetype\\\": \\\"^2.3.0\\\",\\n+ \\\"whatwg-url\\\": \\\"^8.5.0\\\",\\n+ \\\"ws\\\": \\\"^7.4.6\\\",\\n+ \\\"xml-name-validator\\\": \\\"^3.0.0\\\"\\n+ },\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=10\\\"\\n+ },\\n+ \\\"peerDependencies\\\": {\\n+ \\\"canvas\\\": \\\"^2.5.0\\\"\\n+ },\\n+ \\\"peerDependenciesMeta\\\": {\\n+ \\\"canvas\\\": {\\n+ \\\"optional\\\": true\\n+ }\\n }\\n },\\n+ \\\"node_modules/react-scripts/node_modules/parse5\\\": {\\n+ \\\"version\\\": \\\"6.0.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==\\\",\\n+ \\\"license\\\": \\\"MIT\\\"\\n+ },\\n \\\"node_modules/react-scripts/node_modules/sass-loader\\\": {\\n \\\"version\\\": \\\"12.6.0\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/sass-loader/-/sass-loader-12.6.0.tgz\\\",\\n@@ -23426,6 +23412,18 @@\\n }\\n }\\n },\\n+ \\\"node_modules/react-scripts/node_modules/saxes\\\": {\\n+ \\\"version\\\": \\\"5.0.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==\\\",\\n+ \\\"license\\\": \\\"ISC\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"xmlchars\\\": \\\"^2.2.0\\\"\\n+ },\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=10\\\"\\n+ }\\n+ },\\n \\\"node_modules/react-scripts/node_modules/source-map\\\": {\\n \\\"version\\\": \\\"0.6.1\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz\\\",\\n@@ -23435,6 +23433,18 @@\\n \\\"node\\\": \\\">=0.10.0\\\"\\n }\\n },\\n+ \\\"node_modules/react-scripts/node_modules/tr46\\\": {\\n+ \\\"version\\\": \\\"2.1.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"punycode\\\": \\\"^2.1.1\\\"\\n+ },\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=8\\\"\\n+ }\\n+ },\\n \\\"node_modules/react-scripts/node_modules/v8-to-istanbul\\\": {\\n \\\"version\\\": \\\"8.1.1\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz\\\",\\n@@ -23458,6 +23468,56 @@\\n \\\"node\\\": \\\">= 8\\\"\\n }\\n },\\n+ \\\"node_modules/react-scripts/node_modules/w3c-xmlserializer\\\": {\\n+ \\\"version\\\": \\\"2.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"xml-name-validator\\\": \\\"^3.0.0\\\"\\n+ },\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=10\\\"\\n+ }\\n+ },\\n+ \\\"node_modules/react-scripts/node_modules/webidl-conversions\\\": {\\n+ \\\"version\\\": \\\"6.1.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==\\\",\\n+ \\\"license\\\": \\\"BSD-2-Clause\\\",\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=10.4\\\"\\n+ }\\n+ },\\n+ \\\"node_modules/react-scripts/node_modules/whatwg-encoding\\\": {\\n+ \\\"version\\\": \\\"1.0.5\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"iconv-lite\\\": \\\"0.4.24\\\"\\n+ }\\n+ },\\n+ \\\"node_modules/react-scripts/node_modules/whatwg-mimetype\\\": {\\n+ \\\"version\\\": \\\"2.3.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==\\\",\\n+ \\\"license\\\": \\\"MIT\\\"\\n+ },\\n+ \\\"node_modules/react-scripts/node_modules/whatwg-url\\\": {\\n+ \\\"version\\\": \\\"8.7.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"lodash\\\": \\\"^4.7.0\\\",\\n+ \\\"tr46\\\": \\\"^2.1.0\\\",\\n+ \\\"webidl-conversions\\\": \\\"^6.1.0\\\"\\n+ },\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=10\\\"\\n+ }\\n+ },\\n \\\"node_modules/react-scripts/node_modules/write-file-atomic\\\": {\\n \\\"version\\\": \\\"3.0.3\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz\\\",\\n@@ -23470,6 +23530,33 @@\\n \\\"typedarray-to-buffer\\\": \\\"^3.1.5\\\"\\n }\\n },\\n+ \\\"node_modules/react-scripts/node_modules/ws\\\": {\\n+ \\\"version\\\": \\\"7.5.10\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/ws/-/ws-7.5.10.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==\\\",\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=8.3.0\\\"\\n+ },\\n+ \\\"peerDependencies\\\": {\\n+ \\\"bufferutil\\\": \\\"^4.0.1\\\",\\n+ \\\"utf-8-validate\\\": \\\"^5.0.2\\\"\\n+ },\\n+ \\\"peerDependenciesMeta\\\": {\\n+ \\\"bufferutil\\\": {\\n+ \\\"optional\\\": true\\n+ },\\n+ \\\"utf-8-validate\\\": {\\n+ \\\"optional\\\": true\\n+ }\\n+ }\\n+ },\\n+ \\\"node_modules/react-scripts/node_modules/xml-name-validator\\\": {\\n+ \\\"version\\\": \\\"3.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==\\\",\\n+ \\\"license\\\": \\\"Apache-2.0\\\"\\n+ },\\n \\\"node_modules/react-scripts/node_modules/yargs\\\": {\\n \\\"version\\\": \\\"16.2.0\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz\\\",\\n@@ -23497,49 +23584,6 @@\\n \\\"node\\\": \\\">=10\\\"\\n }\\n },\\n- \\\"node_modules/react-scroll-to-bottom\\\": {\\n- \\\"version\\\": \\\"4.2.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/react-scroll-to-bottom/-/react-scroll-to-bottom-4.2.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-1WweuumQc5JLzeAR81ykRdK/cEv9NlCPEm4vSwOGN1qS2qlpGVTyMgdI8Y7ZmaqRmzYBGV5/xPuJQtekYzQFGg==\\\",\\n- \\\"dependencies\\\": {\\n- \\\"@babel/runtime-corejs3\\\": \\\"^7.15.4\\\",\\n- \\\"@emotion/css\\\": \\\"11.1.3\\\",\\n- \\\"classnames\\\": \\\"2.3.1\\\",\\n- \\\"core-js\\\": \\\"3.18.3\\\",\\n- \\\"math-random\\\": \\\"2.0.1\\\",\\n- \\\"prop-types\\\": \\\"15.7.2\\\",\\n- \\\"simple-update-in\\\": \\\"2.2.0\\\"\\n- },\\n- \\\"peerDependencies\\\": {\\n- \\\"react\\\": \\\">= 16.8.6\\\"\\n- }\\n- },\\n- \\\"node_modules/react-scroll-to-bottom/node_modules/classnames\\\": {\\n- \\\"version\\\": \\\"2.3.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/classnames/-/classnames-2.3.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==\\\"\\n- },\\n- \\\"node_modules/react-scroll-to-bottom/node_modules/core-js\\\": {\\n- \\\"version\\\": \\\"3.18.3\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/core-js/-/core-js-3.18.3.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-tReEhtMReZaPFVw7dajMx0vlsz3oOb8ajgPoHVYGxr8ErnZ6PcYEvvmjGmXlfpnxpkYSdOQttjB+MvVbCGfvLw==\\\",\\n- \\\"deprecated\\\": \\\"core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.\\\",\\n- \\\"hasInstallScript\\\": true,\\n- \\\"funding\\\": {\\n- \\\"type\\\": \\\"opencollective\\\",\\n- \\\"url\\\": \\\"https://opencollective.com/core-js\\\"\\n- }\\n- },\\n- \\\"node_modules/react-scroll-to-bottom/node_modules/prop-types\\\": {\\n- \\\"version\\\": \\\"15.7.2\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==\\\",\\n- \\\"dependencies\\\": {\\n- \\\"loose-envify\\\": \\\"^1.4.0\\\",\\n- \\\"object-assign\\\": \\\"^4.1.1\\\",\\n- \\\"react-is\\\": \\\"^16.8.1\\\"\\n- }\\n- },\\n \\\"node_modules/react-select\\\": {\\n \\\"version\\\": \\\"5.7.4\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/react-select/-/react-select-5.7.4.tgz\\\",\\n@@ -23561,20 +23605,19 @@\\n }\\n },\\n \\\"node_modules/react-style-singleton\\\": {\\n- \\\"version\\\": \\\"2.2.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==\\\",\\n+ \\\"version\\\": \\\"2.2.3\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==\\\",\\n \\\"dependencies\\\": {\\n \\\"get-nonce\\\": \\\"^1.0.0\\\",\\n- \\\"invariant\\\": \\\"^2.2.4\\\",\\n \\\"tslib\\\": \\\"^2.0.0\\\"\\n },\\n \\\"engines\\\": {\\n \\\"node\\\": \\\">=10\\\"\\n },\\n \\\"peerDependencies\\\": {\\n- \\\"@types/react\\\": \\\"^16.8.0 || ^17.0.0 || ^18.0.0\\\",\\n- \\\"react\\\": \\\"^16.8.0 || ^17.0.0 || ^18.0.0\\\"\\n+ \\\"@types/react\\\": \\\"*\\\",\\n+ \\\"react\\\": \\\"^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc\\\"\\n },\\n \\\"peerDependenciesMeta\\\": {\\n \\\"@types/react\\\": {\\n@@ -24475,15 +24518,16 @@\\n \\\"integrity\\\": \\\"sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==\\\"\\n },\\n \\\"node_modules/saxes\\\": {\\n- \\\"version\\\": \\\"5.0.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==\\\",\\n+ \\\"version\\\": \\\"6.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==\\\",\\n+ \\\"dev\\\": true,\\n \\\"license\\\": \\\"ISC\\\",\\n \\\"dependencies\\\": {\\n \\\"xmlchars\\\": \\\"^2.2.0\\\"\\n },\\n \\\"engines\\\": {\\n- \\\"node\\\": \\\">=10\\\"\\n+ \\\"node\\\": \\\">=v12.22.7\\\"\\n }\\n },\\n \\\"node_modules/scheduler\\\": {\\n@@ -24798,10 +24842,18 @@\\n \\\"simple-concat\\\": \\\"^1.0.0\\\"\\n }\\n },\\n- \\\"node_modules/simple-update-in\\\": {\\n- \\\"version\\\": \\\"2.2.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/simple-update-in/-/simple-update-in-2.2.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-FrW41lLiOs82jKxwq39UrE1HDAHOvirKWk4Nv8tqnFFFknVbTxcHZzDS4vt02qqdU/5+KNsQHWzhKHznDBmrww==\\\"\\n+ \\\"node_modules/simple-update-notifier\\\": {\\n+ \\\"version\\\": \\\"2.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==\\\",\\n+ \\\"dev\\\": true,\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"dependencies\\\": {\\n+ \\\"semver\\\": \\\"^7.5.3\\\"\\n+ },\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=10\\\"\\n+ }\\n },\\n \\\"node_modules/sisteransi\\\": {\\n \\\"version\\\": \\\"1.0.5\\\",\\n@@ -25998,6 +26050,16 @@\\n \\\"node\\\": \\\">=0.6\\\"\\n }\\n },\\n+ \\\"node_modules/touch\\\": {\\n+ \\\"version\\\": \\\"3.1.1\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/touch/-/touch-3.1.1.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==\\\",\\n+ \\\"dev\\\": true,\\n+ \\\"license\\\": \\\"ISC\\\",\\n+ \\\"bin\\\": {\\n+ \\\"nodetouch\\\": \\\"bin/nodetouch.js\\\"\\n+ }\\n+ },\\n \\\"node_modules/tough-cookie\\\": {\\n \\\"version\\\": \\\"4.1.4\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz\\\",\\n@@ -26023,15 +26085,16 @@\\n }\\n },\\n \\\"node_modules/tr46\\\": {\\n- \\\"version\\\": \\\"2.1.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/tr46/-/tr46-2.1.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==\\\",\\n+ \\\"version\\\": \\\"3.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==\\\",\\n+ \\\"dev\\\": true,\\n \\\"license\\\": \\\"MIT\\\",\\n \\\"dependencies\\\": {\\n \\\"punycode\\\": \\\"^2.1.1\\\"\\n },\\n \\\"engines\\\": {\\n- \\\"node\\\": \\\">=8\\\"\\n+ \\\"node\\\": \\\">=12\\\"\\n }\\n },\\n \\\"node_modules/trim-lines\\\": {\\n@@ -26317,6 +26380,13 @@\\n \\\"url\\\": \\\"https://github.com/sponsors/ljharb\\\"\\n }\\n },\\n+ \\\"node_modules/undefsafe\\\": {\\n+ \\\"version\\\": \\\"2.0.5\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==\\\",\\n+ \\\"dev\\\": true,\\n+ \\\"license\\\": \\\"MIT\\\"\\n+ },\\n \\\"node_modules/underscore\\\": {\\n \\\"version\\\": \\\"1.12.1\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/underscore/-/underscore-1.12.1.tgz\\\",\\n@@ -26570,9 +26640,9 @@\\n }\\n },\\n \\\"node_modules/use-callback-ref\\\": {\\n- \\\"version\\\": \\\"1.3.1\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.1.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==\\\",\\n+ \\\"version\\\": \\\"1.3.3\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==\\\",\\n \\\"dependencies\\\": {\\n \\\"tslib\\\": \\\"^2.0.0\\\"\\n },\\n@@ -26580,8 +26650,8 @@\\n \\\"node\\\": \\\">=10\\\"\\n },\\n \\\"peerDependencies\\\": {\\n- \\\"@types/react\\\": \\\"^16.8.0 || ^17.0.0 || ^18.0.0\\\",\\n- \\\"react\\\": \\\"^16.8.0 || ^17.0.0 || ^18.0.0\\\"\\n+ \\\"@types/react\\\": \\\"*\\\",\\n+ \\\"react\\\": \\\"^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc\\\"\\n },\\n \\\"peerDependenciesMeta\\\": {\\n \\\"@types/react\\\": {\\n@@ -26800,15 +26870,16 @@\\n }\\n },\\n \\\"node_modules/w3c-xmlserializer\\\": {\\n- \\\"version\\\": \\\"2.0.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==\\\",\\n+ \\\"version\\\": \\\"4.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==\\\",\\n+ \\\"dev\\\": true,\\n \\\"license\\\": \\\"MIT\\\",\\n \\\"dependencies\\\": {\\n- \\\"xml-name-validator\\\": \\\"^3.0.0\\\"\\n+ \\\"xml-name-validator\\\": \\\"^4.0.0\\\"\\n },\\n \\\"engines\\\": {\\n- \\\"node\\\": \\\">=10\\\"\\n+ \\\"node\\\": \\\">=14\\\"\\n }\\n },\\n \\\"node_modules/walker\\\": {\\n@@ -26854,12 +26925,12 @@\\n \\\"integrity\\\": \\\"sha512-sVWcwhU5mX6crfI5Vd2dC4qchyTqxV8URinzt25XqVh+bHEPGH4C3NPrNionCP7Obx59wrYEbNlw4Z8sjALzZg==\\\"\\n },\\n \\\"node_modules/webidl-conversions\\\": {\\n- \\\"version\\\": \\\"6.1.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==\\\",\\n+ \\\"version\\\": \\\"7.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==\\\",\\n \\\"license\\\": \\\"BSD-2-Clause\\\",\\n \\\"engines\\\": {\\n- \\\"node\\\": \\\">=10.4\\\"\\n+ \\\"node\\\": \\\">=12\\\"\\n }\\n },\\n \\\"node_modules/webpack\\\": {\\n@@ -27086,26 +27157,6 @@\\n \\\"url\\\": \\\"https://opencollective.com/webpack\\\"\\n }\\n },\\n- \\\"node_modules/webpack-dev-server/node_modules/ws\\\": {\\n- \\\"version\\\": \\\"8.14.2\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/ws/-/ws-8.14.2.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-wEBG1ftX4jcglPxgFCMJmZ2PLtSbJ2Peg6TmpJFTbe9GZYOQCDPdMYu/Tm0/bGZkw8paZnJY45J4K2PZrLYq8g==\\\",\\n- \\\"engines\\\": {\\n- \\\"node\\\": \\\">=10.0.0\\\"\\n- },\\n- \\\"peerDependencies\\\": {\\n- \\\"bufferutil\\\": \\\"^4.0.1\\\",\\n- \\\"utf-8-validate\\\": \\\">=5.0.2\\\"\\n- },\\n- \\\"peerDependenciesMeta\\\": {\\n- \\\"bufferutil\\\": {\\n- \\\"optional\\\": true\\n- },\\n- \\\"utf-8-validate\\\": {\\n- \\\"optional\\\": true\\n- }\\n- }\\n- },\\n \\\"node_modules/webpack-manifest-plugin\\\": {\\n \\\"version\\\": \\\"4.1.1\\\",\\n \\\"resolved\\\": \\\"https://registry.npmjs.org/webpack-manifest-plugin/-/webpack-manifest-plugin-4.1.1.tgz\\\",\\n@@ -27191,24 +27242,16 @@\\n }\\n },\\n \\\"node_modules/whatwg-encoding\\\": {\\n- \\\"version\\\": \\\"1.0.5\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==\\\",\\n- \\\"license\\\": \\\"MIT\\\",\\n- \\\"dependencies\\\": {\\n- \\\"iconv-lite\\\": \\\"0.4.24\\\"\\n- }\\n- },\\n- \\\"node_modules/whatwg-encoding/node_modules/iconv-lite\\\": {\\n- \\\"version\\\": \\\"0.4.24\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==\\\",\\n+ \\\"version\\\": \\\"2.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==\\\",\\n+ \\\"dev\\\": true,\\n \\\"license\\\": \\\"MIT\\\",\\n \\\"dependencies\\\": {\\n- \\\"safer-buffer\\\": \\\">= 2.1.2 < 3\\\"\\n+ \\\"iconv-lite\\\": \\\"0.6.3\\\"\\n },\\n \\\"engines\\\": {\\n- \\\"node\\\": \\\">=0.10.0\\\"\\n+ \\\"node\\\": \\\">=12\\\"\\n }\\n },\\n \\\"node_modules/whatwg-fetch\\\": {\\n@@ -27217,23 +27260,27 @@\\n \\\"integrity\\\": \\\"sha512-d67JP4dHSbm2TrpFj8AbO8DnL1JXL5J9u0Kq2xW6d0TFDbCA3Muhdt8orXC22utleTVj7Prqt82baN6RBvnEgw==\\\"\\n },\\n \\\"node_modules/whatwg-mimetype\\\": {\\n- \\\"version\\\": \\\"2.3.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==\\\",\\n- \\\"license\\\": \\\"MIT\\\"\\n+ \\\"version\\\": \\\"3.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==\\\",\\n+ \\\"dev\\\": true,\\n+ \\\"license\\\": \\\"MIT\\\",\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=12\\\"\\n+ }\\n },\\n \\\"node_modules/whatwg-url\\\": {\\n- \\\"version\\\": \\\"8.7.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.7.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==\\\",\\n+ \\\"version\\\": \\\"11.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==\\\",\\n+ \\\"dev\\\": true,\\n \\\"license\\\": \\\"MIT\\\",\\n \\\"dependencies\\\": {\\n- \\\"lodash\\\": \\\"^4.7.0\\\",\\n- \\\"tr46\\\": \\\"^2.1.0\\\",\\n- \\\"webidl-conversions\\\": \\\"^6.1.0\\\"\\n+ \\\"tr46\\\": \\\"^3.0.0\\\",\\n+ \\\"webidl-conversions\\\": \\\"^7.0.0\\\"\\n },\\n \\\"engines\\\": {\\n- \\\"node\\\": \\\">=10\\\"\\n+ \\\"node\\\": \\\">=12\\\"\\n }\\n },\\n \\\"node_modules/which\\\": {\\n@@ -27646,16 +27693,16 @@\\n }\\n },\\n \\\"node_modules/ws\\\": {\\n- \\\"version\\\": \\\"7.5.10\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/ws/-/ws-7.5.10.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==\\\",\\n+ \\\"version\\\": \\\"8.18.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/ws/-/ws-8.18.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==\\\",\\n \\\"license\\\": \\\"MIT\\\",\\n \\\"engines\\\": {\\n- \\\"node\\\": \\\">=8.3.0\\\"\\n+ \\\"node\\\": \\\">=10.0.0\\\"\\n },\\n \\\"peerDependencies\\\": {\\n \\\"bufferutil\\\": \\\"^4.0.1\\\",\\n- \\\"utf-8-validate\\\": \\\"^5.0.2\\\"\\n+ \\\"utf-8-validate\\\": \\\">=5.0.2\\\"\\n },\\n \\\"peerDependenciesMeta\\\": {\\n \\\"bufferutil\\\": {\\n@@ -27667,10 +27714,14 @@\\n }\\n },\\n \\\"node_modules/xml-name-validator\\\": {\\n- \\\"version\\\": \\\"3.0.0\\\",\\n- \\\"resolved\\\": \\\"https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz\\\",\\n- \\\"integrity\\\": \\\"sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==\\\",\\n- \\\"license\\\": \\\"Apache-2.0\\\"\\n+ \\\"version\\\": \\\"4.0.0\\\",\\n+ \\\"resolved\\\": \\\"https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz\\\",\\n+ \\\"integrity\\\": \\\"sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==\\\",\\n+ \\\"dev\\\": true,\\n+ \\\"license\\\": \\\"Apache-2.0\\\",\\n+ \\\"engines\\\": {\\n+ \\\"node\\\": \\\">=12\\\"\\n+ }\\n },\\n \\\"node_modules/xmlchars\\\": {\\n \\\"version\\\": \\\"2.2.0\\\",\\ndiff --git a/package.json b/package.json\\nindex ddd8f68..02a70c4 100644\\n--- a/package.json\\n+++ b/package.json\\n@@ -1,8 +1,9 @@\\n {\\n \\\"name\\\": \\\"labeeb\\\",\\n- \\\"version\\\": \\\"2.4.22\\\",\\n+ \\\"version\\\": \\\"2.5.0\\\",\\n \\\"private\\\": true,\\n \\\"dependencies\\\": {\\n+ \\\"@aj-archipelago/subvibe\\\": \\\"^1.0.8\\\",\\n \\\"@amplitude/analytics-browser\\\": \\\"^2.3.2\\\",\\n \\\"@apollo/client\\\": \\\"^3.10.4\\\",\\n \\\"@apollo/experimental-nextjs-app-support\\\": \\\"^0.11.0\\\",\\n@@ -10,6 +11,7 @@\\n \\\"@hello-pangea/dnd\\\": \\\"^16.6.0\\\",\\n \\\"@heroicons/react\\\": \\\"^2.0.18\\\",\\n \\\"@radix-ui/react-accordion\\\": \\\"^1.1.2\\\",\\n+ \\\"@radix-ui/react-alert-dialog\\\": \\\"^1.1.4\\\",\\n \\\"@radix-ui/react-checkbox\\\": \\\"^1.1.2\\\",\\n \\\"@radix-ui/react-dialog\\\": \\\"^1.1.2\\\",\\n \\\"@radix-ui/react-dismissable-layer\\\": \\\"^1.1.1\\\",\\n@@ -17,7 +19,7 @@\\n \\\"@radix-ui/react-popover\\\": \\\"^1.0.7\\\",\\n \\\"@radix-ui/react-progress\\\": \\\"^1.0.3\\\",\\n \\\"@radix-ui/react-select\\\": \\\"^2.1.2\\\",\\n- \\\"@radix-ui/react-slot\\\": \\\"^1.0.2\\\",\\n+ \\\"@radix-ui/react-slot\\\": \\\"^1.1.1\\\",\\n \\\"@radix-ui/react-tabs\\\": \\\"^1.0.4\\\",\\n \\\"@radix-ui/react-toast\\\": \\\"^1.2.2\\\",\\n \\\"@radix-ui/react-toggle\\\": \\\"^1.0.3\\\",\\n@@ -64,7 +66,7 @@\\n \\\"react-filepond\\\": \\\"^7.1.2\\\",\\n \\\"react-i18next\\\": \\\"^12.2.0\\\",\\n \\\"react-icons\\\": \\\"^4.7.1\\\",\\n- \\\"react-intersection-observer\\\": \\\"^9.13.1\\\",\\n+ \\\"react-intersection-observer\\\": \\\"^9.15.1\\\",\\n \\\"react-markdown\\\": \\\"^9.0.1\\\",\\n \\\"react-monaco-editor\\\": \\\"^0.55.0\\\",\\n \\\"react-player\\\": \\\"^2.16.0\\\",\\n@@ -73,7 +75,6 @@\\n \\\"react-redux\\\": \\\"^8.0.5\\\",\\n \\\"react-router-dom\\\": \\\"^6.8.1\\\",\\n \\\"react-scripts\\\": \\\"5.0.1\\\",\\n- \\\"react-scroll-to-bottom\\\": \\\"^4.2.0\\\",\\n \\\"react-select\\\": \\\"^5.7.3\\\",\\n \\\"react-textarea-autosize\\\": \\\"^8.4.0\\\",\\n \\\"react-time-ago\\\": \\\"^7.3.1\\\",\\n@@ -101,6 +102,7 @@\\n \\\"lint\\\": \\\"next lint && npx prettier --check .\\\",\\n \\\"format\\\": \\\"npx prettier --write .\\\",\\n \\\"worker\\\": \\\"node ./jobs/worker.js\\\",\\n+ \\\"worker:dev\\\": \\\"nodemon --watch ./jobs/worker.js --exec 'node ./jobs/worker.js'\\\",\\n \\\"test\\\": \\\"jest\\\"\\n },\\n \\\"eslintConfig\\\": {\\n@@ -122,6 +124,7 @@\\n ]\\n },\\n \\\"devDependencies\\\": {\\n+ \\\"@babel/plugin-proposal-private-property-in-object\\\": \\\"^7.21.11\\\",\\n \\\"@babel/preset-env\\\": \\\"^7.26.0\\\",\\n \\\"@babel/preset-react\\\": \\\"^7.26.3\\\",\\n \\\"@tailwindcss/forms\\\": \\\"^0.5.7\\\",\\n@@ -129,7 +132,9 @@\\n \\\"babel-jest\\\": \\\"^29.7.0\\\",\\n \\\"customize-cra\\\": \\\"^1.0.0\\\",\\n \\\"jest\\\": \\\"^29.7.0\\\",\\n+ \\\"jest-environment-jsdom\\\": \\\"^29.7.0\\\",\\n \\\"mongodb-memory-server\\\": \\\"^10.1.3\\\",\\n+ \\\"nodemon\\\": \\\"^3.1.9\\\",\\n \\\"postcss\\\": \\\"^8.4.31\\\",\\n \\\"prettier\\\": \\\"^3.2.2\\\",\\n \\\"react-app-rewired\\\": \\\"^2.2.1\\\",\\ndiff --git a/src/App.js b/src/App.js\\nindex 69ec584..82cd750 100644\\n--- a/src/App.js\\n+++ b/src/App.js\\n@@ -18,6 +18,7 @@ import \\\"./App.scss\\\";\\n import StoreProvider from \\\"./StoreProvider\\\";\\n import { LanguageContext, LanguageProvider } from \\\"./contexts/LanguageProvider\\\";\\n import { ThemeProvider } from \\\"./contexts/ThemeProvider\\\";\\n+import { AutoTranscribeProvider } from \\\"./contexts/AutoTranscribeContext\\\";\\n import Layout from \\\"./layout/Layout\\\";\\n import \\\"./tailwind.css\\\";\\n \\n@@ -42,17 +43,29 @@ const App = ({\\n neuralspaceEnabled,\\n }) => {\\n const { data: currentUser } = useCurrentUser();\\n- const { data: serverUserState } = useUserState();\\n+ const { data: serverUserState, refetch: refetchServerUserState } =\\n+ useUserState();\\n const updateUserState = useUpdateUserState();\\n- const [userState, setUserState] = useState(serverUserState);\\n+ const [userState, setUserState] = useState(null);\\n const debouncedUserState = useDebounce(userState, STATE_DEBOUNCE_TIME);\\n+ const [refetchCalled, setRefetchCalled] = useState(false);\\n+\\n+ const refetchUserState = () => {\\n+ setRefetchCalled(true);\\n+ refetchServerUserState();\\n+ };\\n \\n useEffect(() => {\\n- // set user state from server if it exists\\n- if (!userState && serverUserState) {\\n+ // set user state from server if it exists, but only if there's no client\\n+ // state yet\\n+ if (\\n+ (!userState || refetchCalled) &&\\n+ JSON.stringify(serverUserState) !== JSON.stringify(userState)\\n+ ) {\\n setUserState(serverUserState);\\n+ setRefetchCalled(false);\\n }\\n- }, [userState, serverUserState]);\\n+ }, [userState, serverUserState, refetchCalled]);\\n \\n useEffect(() => {\\n if (i18next.language !== language) {\\n@@ -94,19 +107,22 @@ const App = ({\\n \\n \\n \\n- \\n- \\n- \\n- {children}\\n- \\n- \\n- \\n+ \\n+ \\n+ \\n+ \\n+ {children}\\n+ \\n+ \\n+ \\n+ \\n \\n \\n \\ndiff --git a/src/__tests__/App.test.js b/src/__tests__/App.test.js\\nnew file mode 100644\\nindex 0000000..b8cff1e\\n--- /dev/null\\n+++ b/src/__tests__/App.test.js\\n@@ -0,0 +1,434 @@\\n+import { act, render, waitFor } from \\\"@testing-library/react\\\";\\n+import React from \\\"react\\\";\\n+import {\\n+ useCurrentUser,\\n+ useUpdateUserState,\\n+ useUserState,\\n+} from \\\"../../app/queries/users\\\";\\n+import { AuthContext } from \\\"../App\\\";\\n+import {\\n+ LanguageContext,\\n+ LanguageProvider,\\n+} from \\\"../contexts/LanguageProvider\\\";\\n+\\n+// Create a mock language context before mocking\\n+const mockLanguageContext = {\\n+ language: \\\"en\\\",\\n+ direction: \\\"ltr\\\",\\n+ changeLanguage: jest.fn(),\\n+};\\n+\\n+// Mock style imports - removing virtual: true option\\n+jest.mock(\\\"../App.scss\\\", () => ({}));\\n+jest.mock(\\\"../tailwind.css\\\", () => ({}));\\n+\\n+// Mock React's useContext to return our mockLanguageContext when LanguageContext is requested\\n+const originalUseContext = React.useContext;\\n+React.useContext = jest.fn((context) => {\\n+ // Check if this is the LanguageContext\\n+ if (context === LanguageContext) {\\n+ return mockLanguageContext;\\n+ }\\n+ // Otherwise use the original implementation\\n+ return originalUseContext(context);\\n+});\\n+\\n+// Mock the modules and hooks before importing App\\n+jest.mock(\\\"@apollo/experimental-nextjs-app-support\\\", () => ({\\n+ ApolloNextAppProvider: ({ children }) => children,\\n+}));\\n+\\n+jest.mock(\\\"../graphql\\\", () => ({\\n+ getClient: jest.fn(() => ({})),\\n+}));\\n+\\n+jest.mock(\\\"../i18n\\\", () => ({}));\\n+\\n+jest.mock(\\\"@amplitude/analytics-browser\\\", () => ({\\n+ init: jest.fn(),\\n+}));\\n+\\n+// Mock useDebounce hook\\n+jest.mock(\\\"@uidotdev/usehooks\\\", () => ({\\n+ useDebounce: jest.fn((val) => val), // By default, return the value immediately without debouncing\\n+}));\\n+\\n+jest.mock(\\\"../../app/queries/users\\\", () => ({\\n+ useCurrentUser: jest.fn(),\\n+ useUserState: jest.fn(),\\n+ useUpdateUserState: jest.fn(),\\n+}));\\n+\\n+jest.mock(\\\"../StoreProvider\\\", () => ({\\n+ __esModule: true,\\n+ default: ({ children }) => (\\n+
{children}
\\n+ ),\\n+}));\\n+\\n+jest.mock(\\\"../contexts/LanguageProvider\\\", () => {\\n+ return {\\n+ LanguageProvider: ({ children }) => {\\n+ const mockContext = {\\n+ language: \\\"en\\\",\\n+ direction: \\\"ltr\\\",\\n+ changeLanguage: jest.fn(),\\n+ };\\n+\\n+ // Import the actual context\\n+ const { LanguageContext } = jest.requireActual(\\n+ \\\"../contexts/LanguageProvider\\\",\\n+ );\\n+\\n+ // Return the Provider with our mock value\\n+ return (\\n+ \\n+ {children}\\n+ \\n+ );\\n+ },\\n+ // Export the actual LanguageContext\\n+ LanguageContext: jest.requireActual(\\\"../contexts/LanguageProvider\\\")\\n+ .LanguageContext,\\n+ };\\n+});\\n+\\n+jest.mock(\\\"../contexts/ThemeProvider\\\", () => ({\\n+ ThemeProvider: ({ children }) => (\\n+
{children}
\\n+ ),\\n+}));\\n+\\n+jest.mock(\\\"../contexts/AutoTranscribeContext\\\", () => ({\\n+ AutoTranscribeProvider: ({ children }) => (\\n+
{children}
\\n+ ),\\n+}));\\n+\\n+jest.mock(\\\"../layout/Layout\\\", () => ({\\n+ __esModule: true,\\n+ default: ({ children }) =>
{children}
,\\n+}));\\n+\\n+// Mock classNames utility\\n+jest.mock(\\\"../../app/utils/class-names\\\", () => ({\\n+ __esModule: true,\\n+ default: (...classes) => classes.filter(Boolean).join(\\\" \\\"),\\n+}));\\n+\\n+// Create a mock for dayjs\\n+jest.mock(\\\"dayjs\\\", () => {\\n+ const originalDayjs = jest.requireActual(\\\"dayjs\\\");\\n+ return Object.assign(\\n+ jest.fn(() => originalDayjs()),\\n+ {\\n+ locale: jest.fn(),\\n+ },\\n+ );\\n+});\\n+\\n+// Create a mock for i18next\\n+jest.mock(\\\"i18next\\\", () => ({\\n+ language: \\\"en\\\",\\n+ changeLanguage: jest.fn(),\\n+}));\\n+\\n+// Import App after all mocks are set up\\n+// eslint-disable-next-line import/first\\n+import { useDebounce } from \\\"@uidotdev/usehooks\\\";\\n+// eslint-disable-next-line import/first\\n+import App from \\\"../App\\\";\\n+\\n+// Mock process.env\\n+process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY = \\\"test-api-key\\\";\\n+\\n+describe(\\\"App Component\\\", () => {\\n+ // Setup for all tests\\n+ const mockRefetch = jest.fn();\\n+ const mockMutate = jest.fn();\\n+\\n+ beforeEach(() => {\\n+ jest.clearAllMocks();\\n+\\n+ // Default mock implementations\\n+ useCurrentUser.mockReturnValue({\\n+ data: { id: \\\"user1\\\", name: \\\"Test User\\\" },\\n+ });\\n+ useUserState.mockReturnValue({\\n+ data: { preferences: { theme: \\\"light\\\" } },\\n+ refetch: mockRefetch,\\n+ });\\n+ useUpdateUserState.mockReturnValue({ mutate: mockMutate });\\n+\\n+ // Reset useDebounce to pass through values by default\\n+ useDebounce.mockImplementation((val) => val);\\n+ });\\n+\\n+ describe(\\\"User State Management\\\", () => {\\n+ it(\\\"should initialize userState from server when client state is null\\\", async () => {\\n+ // Setup initial state\\n+ const serverState = {\\n+ preferences: { theme: \\\"dark\\\", fontSize: \\\"medium\\\" },\\n+ };\\n+ useUserState.mockReturnValue({\\n+ data: serverState,\\n+ refetch: mockRefetch,\\n+ });\\n+\\n+ // Render component\\n+ render(\\n+ \\n+ \\n+ Test Content\\n+ \\n+ ,\\n+ );\\n+\\n+ // First render should set userState from server\\n+ await waitFor(() => {\\n+ expect(useUserState().data).toEqual(serverState);\\n+ });\\n+ });\\n+\\n+ it(\\\"should not overwrite client userState with server state on re-render\\\", async () => {\\n+ // Mock useState to capture state updates\\n+ const setUserStateMock = jest.fn();\\n+ let userStateValue = null;\\n+\\n+ // Save the original useState\\n+ const originalUseState = React.useState;\\n+\\n+ // Create a mock implementation that tracks userState specifically\\n+ const mockUseState = jest.fn((initialValue) => {\\n+ // Only intercept the userState (null initial value)\\n+ if (initialValue === null) {\\n+ return [userStateValue, setUserStateMock];\\n+ }\\n+ // For all other useState calls, use the original implementation\\n+ return originalUseState(initialValue);\\n+ });\\n+\\n+ // Apply our mock implementation\\n+ jest.spyOn(React, \\\"useState\\\").mockImplementation(mockUseState);\\n+\\n+ // Initial server state\\n+ const serverState = { preferences: { theme: \\\"light\\\" } };\\n+ useUserState.mockReturnValue({\\n+ data: serverState,\\n+ refetch: mockRefetch,\\n+ });\\n+\\n+ // Initial render\\n+ const { rerender } = render(\\n+ \\n+ \\n+ Test Content\\n+ \\n+ ,\\n+ );\\n+\\n+ // Manually trigger the effect that would set the state\\n+ act(() => {\\n+ // Find the useState call for userState and call its setter\\n+ setUserStateMock(serverState);\\n+ });\\n+\\n+ // Verify setUserState was called with server state\\n+ await waitFor(() => {\\n+ expect(setUserStateMock).toHaveBeenCalledWith(serverState);\\n+ });\\n+\\n+ // Now simulate client state being set\\n+ userStateValue = {\\n+ preferences: { theme: \\\"dark\\\", fontSize: \\\"large\\\" },\\n+ };\\n+ setUserStateMock.mockClear();\\n+\\n+ // Change server state\\n+ useUserState.mockReturnValue({\\n+ data: { preferences: { theme: \\\"system\\\" } }, // Different server state\\n+ refetch: mockRefetch,\\n+ });\\n+\\n+ // Re-render\\n+ rerender(\\n+ \\n+ \\n+ Test Content\\n+ \\n+ ,\\n+ );\\n+\\n+ // Verify setUserState was NOT called again (client state preserved)\\n+ expect(setUserStateMock).not.toHaveBeenCalled();\\n+\\n+ // Restore original useState\\n+ React.useState.mockRestore();\\n+ });\\n+\\n+ it(\\\"should update server when client state changes\\\", async () => {\\n+ // Setup for testing debounce\\n+ let debouncedValue = null;\\n+ useDebounce.mockImplementation((value) => {\\n+ debouncedValue = value;\\n+ return value;\\n+ });\\n+\\n+ // Setup state mock\\n+ const setUserStateMock = jest.fn();\\n+ let userStateValue = null;\\n+\\n+ const originalUseState = React.useState;\\n+ jest.spyOn(React, \\\"useState\\\").mockImplementation((initialValue) => {\\n+ if (initialValue === null) {\\n+ return [userStateValue, setUserStateMock];\\n+ }\\n+ return originalUseState(initialValue);\\n+ });\\n+\\n+ // Render component\\n+ render(\\n+ \\n+ \\n+ Test Content\\n+ \\n+ ,\\n+ );\\n+\\n+ // Simulate state update\\n+ const updatedState = { preferences: { theme: \\\"dark\\\" } };\\n+ userStateValue = updatedState;\\n+\\n+ // Trigger useEffect that watches debouncedUserState\\n+ // eslint-disable-next-line testing-library/no-unnecessary-act\\n+ act(() => {\\n+ // Force re-render by updating a prop\\n+ render(\\n+ \\n+ \\n+ Test Content\\n+ \\n+ ,\\n+ );\\n+ });\\n+\\n+ // Check if updateUserState.mutate was called with the updated state\\n+ await waitFor(() => {\\n+ expect(mockMutate).toHaveBeenCalledWith(debouncedValue);\\n+ });\\n+\\n+ // Restore original useState\\n+ React.useState.mockRestore();\\n+ });\\n+\\n+ it(\\\"should call refetch and set refetchCalled when refetchUserState is called\\\", async () => {\\n+ // Mock the refetch function\\n+ const mockRefetch = jest.fn();\\n+\\n+ // Setup server state\\n+ const serverState = { preferences: { theme: \\\"light\\\" } };\\n+ useUserState.mockReturnValue({\\n+ data: serverState,\\n+ refetch: mockRefetch,\\n+ });\\n+\\n+ // Create a container to store the captured context value\\n+ let capturedContextValue = null;\\n+\\n+ // Mock the AuthContext.Provider to capture its value\\n+ const originalProvider = AuthContext.Provider;\\n+ AuthContext.Provider = ({ value, children }) => {\\n+ capturedContextValue = value;\\n+ return React.createElement(originalProvider, {\\n+ value,\\n+ children,\\n+ });\\n+ };\\n+\\n+ // Render the component\\n+ const { rerender } = render(\\n+ \\n+ \\n+ Test Content\\n+ \\n+ ,\\n+ );\\n+\\n+ // Store the initial userState\\n+ const initialUserState = capturedContextValue.userState;\\n+\\n+ // Clear previous calls\\n+ mockRefetch.mockClear();\\n+\\n+ // Call refetchUserState directly - no need for act() here\\n+ capturedContextValue.refetchUserState();\\n+\\n+ // Verify the refetch function was called\\n+ expect(mockRefetch).toHaveBeenCalled();\\n+\\n+ // Update the server state to simulate a successful refetch\\n+ const updatedServerState = { preferences: { theme: \\\"dark\\\" } };\\n+ useUserState.mockReturnValue({\\n+ data: updatedServerState,\\n+ refetch: mockRefetch,\\n+ });\\n+\\n+ // Re-render to trigger the useEffect that depends on serverUserState\\n+ rerender(\\n+ \\n+ \\n+ Test Content\\n+ \\n+ ,\\n+ );\\n+\\n+ // First, wait for the userState to be different from the initial state\\n+ await waitFor(() => {\\n+ expect(capturedContextValue.userState).not.toEqual(\\n+ initialUserState,\\n+ );\\n+ });\\n+\\n+ // Then, verify it matches the updated server state\\n+ expect(capturedContextValue.userState).toEqual(updatedServerState);\\n+\\n+ // Restore the original provider\\n+ AuthContext.Provider = originalProvider;\\n+ });\\n+ });\\n+});\\ndiff --git a/src/components/CopyButton.js b/src/components/CopyButton.js\\nindex b51f2c3..64b3891 100644\\n--- a/src/components/CopyButton.js\\n+++ b/src/components/CopyButton.js\\n@@ -15,17 +15,26 @@ function CopyButton({ item, className = \\\"absolute top-1 end-1 \\\" }) {\\n }, [copied]);\\n \\n const copyFormattedText = async (text) => {\\n+ // If text is undefined or null, use an empty string instead\\n+ const textToCopy = text || \\\"\\\";\\n+\\n try {\\n- const html = marked(text);\\n+ const html = marked(textToCopy);\\n const blob = new Blob([html], { type: \\\"text/html\\\" });\\n const clipboardItem = new ClipboardItem({\\n \\\"text/html\\\": blob,\\n- \\\"text/plain\\\": new Blob([text], { type: \\\"text/plain\\\" }),\\n+ \\\"text/plain\\\": new Blob([textToCopy], { type: \\\"text/plain\\\" }),\\n });\\n await navigator.clipboard.write([clipboardItem]);\\n setCopied(true);\\n } catch (err) {\\n- console.error(\\\"Failed to copy text: \\\", err);\\n+ // Fallback to basic clipboard API if rich text copy fails\\n+ try {\\n+ await navigator.clipboard.writeText(textToCopy);\\n+ setCopied(true);\\n+ } catch (clipboardErr) {\\n+ console.error(\\\"Failed to copy text: \\\", clipboardErr);\\n+ }\\n }\\n };\\n \\ndiff --git a/src/components/UserOptions.js b/src/components/UserOptions.js\\nindex 2784565..dabf7f0 100644\\n--- a/src/components/UserOptions.js\\n+++ b/src/components/UserOptions.js\\n@@ -1,7 +1,8 @@\\n import { Modal } from \\\"@/components/ui/modal\\\";\\n import { useApolloClient, useQuery } from \\\"@apollo/client\\\";\\n-import { useContext, useEffect, useState } from \\\"react\\\";\\n+import { useContext, useEffect, useRef, useState } from \\\"react\\\";\\n import { useTranslation } from \\\"react-i18next\\\";\\n+import { FiDownload, FiUpload } from \\\"react-icons/fi\\\";\\n import { useUpdateAiOptions } from \\\"../../app/queries/options\\\";\\n import { QUERIES } from \\\"../../src/graphql\\\";\\n import { AuthContext } from \\\"../App\\\";\\n@@ -9,18 +10,24 @@ import { AuthContext } from \\\"../App\\\";\\n const UserOptions = ({ show, handleClose }) => {\\n const { t } = useTranslation();\\n const { user } = useContext(AuthContext);\\n+ const fileInputRef = useRef();\\n const [aiMemorySelfModify, setAiMemorySelfModify] = useState(\\n user.aiMemorySelfModify || false,\\n );\\n const [aiName, setAiName] = useState(user.aiName || \\\"Labeeb\\\");\\n const [aiStyle, setAiStyle] = useState(user.aiStyle || \\\"OpenAI\\\");\\n+ const [streamingEnabled, setStreamingEnabled] = useState(\\n+ user.streamingEnabled || false,\\n+ );\\n const [activeMemoryTab, setActiveMemoryTab] = useState(\\\"user\\\");\\n const [parsedMemory, setParsedMemory] = useState({\\n memorySelf: \\\"\\\",\\n memoryDirectives: \\\"\\\",\\n memoryUser: \\\"\\\",\\n memoryTopics: \\\"\\\",\\n+ memoryVersion: \\\"\\\",\\n });\\n+ const [uploadError, setUploadError] = useState(\\\"\\\");\\n \\n const updateAiOptionsMutation = useUpdateAiOptions();\\n const apolloClient = useApolloClient();\\n@@ -52,6 +59,7 @@ const UserOptions = ({ show, handleClose }) => {\\n memoryDirectives: parsed.memoryDirectives || \\\"\\\",\\n memoryUser: parsed.memoryUser || \\\"\\\",\\n memoryTopics: parsed.memoryTopics || \\\"\\\",\\n+ memoryVersion: parsed.memoryVersion || \\\"\\\",\\n });\\n } catch (e) {\\n // If parsing fails, put everything in memoryUser\\n@@ -60,6 +68,7 @@ const UserOptions = ({ show, handleClose }) => {\\n memoryDirectives: \\\"\\\",\\n memoryUser: memoryData.sys_read_memory.result || \\\"\\\",\\n memoryTopics: \\\"\\\",\\n+ memoryVersion: \\\"\\\",\\n });\\n }\\n }\\n@@ -75,6 +84,7 @@ const UserOptions = ({ show, handleClose }) => {\\n memoryDirectives: \\\"\\\",\\n memoryUser: \\\"\\\",\\n memoryTopics: \\\"\\\",\\n+ memoryVersion: \\\"\\\",\\n });\\n };\\n \\n@@ -90,6 +100,7 @@ const UserOptions = ({ show, handleClose }) => {\\n aiMemorySelfModify,\\n aiName,\\n aiStyle,\\n+ streamingEnabled,\\n });\\n \\n const combinedMemory = JSON.stringify(parsedMemory);\\n@@ -112,6 +123,65 @@ const UserOptions = ({ show, handleClose }) => {\\n handleClose();\\n };\\n \\n+ const handleDownloadMemory = () => {\\n+ const blob = new Blob([JSON.stringify(parsedMemory, null, 2)], {\\n+ type: \\\"application/json\\\",\\n+ });\\n+ const url = URL.createObjectURL(blob);\\n+ const a = document.createElement(\\\"a\\\");\\n+ a.href = url;\\n+ const now = new Date();\\n+ const date = now.toISOString().split(\\\"T\\\")[0];\\n+ const time = now.toTimeString().split(\\\" \\\")[0].replace(/:/g, \\\"-\\\");\\n+ a.download = `${aiName.toLowerCase()}-memory-${date}-${time}.json`;\\n+ document.body.appendChild(a);\\n+ a.click();\\n+ document.body.removeChild(a);\\n+ URL.revokeObjectURL(url);\\n+ };\\n+\\n+ const handleUploadMemory = (event) => {\\n+ const file = event.target.files[0];\\n+ setUploadError(\\\"\\\"); // Clear any previous errors\\n+ if (file) {\\n+ const reader = new FileReader();\\n+ reader.onload = (e) => {\\n+ try {\\n+ const uploaded = JSON.parse(e.target.result);\\n+ // Validate the required memory structure\\n+ if (!uploaded || typeof uploaded !== \\\"object\\\") {\\n+ throw new Error(t(\\\"Invalid memory file format\\\"));\\n+ }\\n+ setParsedMemory({\\n+ memorySelf: uploaded.memorySelf || \\\"\\\",\\n+ memoryDirectives: uploaded.memoryDirectives || \\\"\\\",\\n+ memoryUser: uploaded.memoryUser || \\\"\\\",\\n+ memoryTopics: uploaded.memoryTopics || \\\"\\\",\\n+ memoryVersion: uploaded.memoryVersion || \\\"\\\",\\n+ });\\n+ } catch (error) {\\n+ console.error(\\\"Failed to parse memory file:\\\", error);\\n+ setUploadError(\\n+ t(\\n+ \\\"Failed to parse memory file. Please ensure it is a valid JSON file with the correct memory structure.\\\",\\n+ ),\\n+ );\\n+ // Reset the file input so the same file can be selected again\\n+ if (fileInputRef.current) {\\n+ fileInputRef.current.value = \\\"\\\";\\n+ }\\n+ }\\n+ };\\n+ reader.onerror = () => {\\n+ setUploadError(t(\\\"Failed to read the file. Please try again.\\\"));\\n+ if (fileInputRef.current) {\\n+ fileInputRef.current.value = \\\"\\\";\\n+ }\\n+ };\\n+ reader.readAsText(file);\\n+ }\\n+ };\\n+\\n const memoryTabs = [\\n { id: \\\"user\\\", label: \\\"User Memory\\\" },\\n { id: \\\"self\\\", label: \\\"Self Memory\\\" },\\n@@ -171,6 +241,24 @@ const UserOptions = ({ show, handleClose }) => {\\n \\n \\n \\n+

\\n+ {t(\\\"Chat Options\\\")}\\n+

\\n+
\\n+ setStreamingEnabled(e.target.checked)}\\n+ style={{ margin: \\\"0.5rem 0\\\" }}\\n+ />\\n+ \\n+
\\n+\\n

\\n {t(\\\"AI Memory\\\")}\\n

\\n@@ -203,19 +291,51 @@ const UserOptions = ({ show, handleClose }) => {\\n

{t(\\\"Loading memory...\\\")}

\\n ) : (\\n <>\\n-
\\n- \\n- {t(\\\"Clear Memory\\\")}\\n- \\n- \\n- {t(\\\"Memory size: {{size}} characters\\\", {\\n- size: JSON.stringify(parsedMemory)\\n- .length,\\n- })}\\n- \\n+
\\n+
\\n+ \\n+ {t(\\\"Clear Memory\\\")}\\n+ \\n+ \\n+ \\n+ \\n+ \\n+ fileInputRef.current?.click()\\n+ }\\n+ title={t(\\\"Upload memory from backup\\\")}\\n+ >\\n+ \\n+ \\n+ \\n+
\\n+
\\n+ \\n+ {t(\\\"Memory size: {{size}} characters\\\", {\\n+ size: JSON.stringify(parsedMemory)\\n+ .length,\\n+ })}\\n+ \\n+ {parsedMemory.memoryVersion && (\\n+ \\n+ (v{parsedMemory.memoryVersion})\\n+ \\n+ )}\\n+
\\n
\\n
\\n
\\ndiff --git a/src/components/chat/Chat.js b/src/components/chat/Chat.js\\nindex 1197a99..8a684a5 100644\\n--- a/src/components/chat/Chat.js\\n+++ b/src/components/chat/Chat.js\\n@@ -7,6 +7,8 @@ import {\\n useUpdateActiveChat,\\n useGetActiveChat,\\n } from \\\"../../../app/queries/chats\\\";\\n+import { useContext } from \\\"react\\\";\\n+import { AuthContext } from \\\"../../App\\\";\\n \\n const ChatTopMenuDynamic = dynamic(() => import(\\\"./ChatTopMenu\\\"), {\\n loading: () =>
,\\n@@ -16,6 +18,7 @@ function Chat({ viewingChat = null }) {\\n const { t } = useTranslation();\\n const updateActiveChat = useUpdateActiveChat();\\n const { data: chat } = useGetActiveChat();\\n+ const { user } = useContext(AuthContext);\\n const { readOnly } = viewingChat || {};\\n const publicChatOwner = viewingChat?.owner;\\n \\n@@ -74,7 +77,10 @@ function Chat({ viewingChat = null }) {\\n
\\n
\\n
\\n- \\n+ \\n
\\n
\\n );\\ndiff --git a/src/components/chat/Chat.scss b/src/components/chat/Chat.scss\\nindex 266ad61..3e7a328 100644\\n--- a/src/components/chat/Chat.scss\\n+++ b/src/components/chat/Chat.scss\\n@@ -213,24 +213,6 @@ html[dir=\\\"ltr\\\"] {\\n height: 87px;\\n background-color: $chat-secondary;\\n }\\n-\\n- // .message-list-container {\\n- // &::before {\\n- // content: '';\\n- // position: absolute;\\n- // width: 5px;\\n- // height: calc(100vh - 30px - 100px);\\n- // cursor: ew-resize;\\n- // }\\n- // }\\n-\\n- .message-container {\\n- // height: calc(100vh - 194px);\\n- }\\n-\\n- // .message-list-container {\\n- // border-inline-start: 1px solid;\\n- // }\\n }\\n \\n .chat-message-container {\\n@@ -694,3 +676,13 @@ html[dir=\\\"ltr\\\"] {\\n box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);\\n }\\n }\\n+\\n+@keyframes pulse {\\n+ 0%,\\n+ 100% {\\n+ opacity: 1;\\n+ }\\n+ 50% {\\n+ opacity: 0.3;\\n+ }\\n+}\\ndiff --git a/src/components/chat/ChatContent.js b/src/components/chat/ChatContent.js\\nindex c0d5396..6b9d747 100644\\n--- a/src/components/chat/ChatContent.js\\n+++ b/src/components/chat/ChatContent.js\\n@@ -7,7 +7,9 @@ import ChatMessages from \\\"./ChatMessages\\\";\\n import { QUERIES } from \\\"../../graphql\\\";\\n import { useGetActiveChat, useUpdateChat } from \\\"../../../app/queries/chats\\\";\\n import { useDeleteAutogenRun } from \\\"../../../app/queries/autogen.js\\\";\\n-import { processImageUrls } from \\\"../../utils/imageUtils\\\";\\n+import { processImageUrls } from \\\"../../utils/imageUtils.mjs\\\";\\n+import { useStreamingMessages } from \\\"../../hooks/useStreamingMessages\\\";\\n+import { useQueryClient } from \\\"@tanstack/react-query\\\";\\n \\n const contextMessageCount = 50;\\n \\n@@ -15,24 +17,48 @@ function ChatContent({\\n displayState = \\\"full\\\",\\n container = \\\"chatpage\\\",\\n viewingChat = null,\\n+ streamingEnabled = false,\\n }) {\\n const { t } = useTranslation();\\n const client = useApolloClient();\\n const { user } = useContext(AuthContext);\\n- const activeChat = useGetActiveChat()?.data;\\n+ const activeChat = useGetActiveChat();\\n+ const updateChatHook = useUpdateChat();\\n+ const deleteAutogenRun = useDeleteAutogenRun();\\n+ const queryClient = useQueryClient();\\n \\n const viewingReadOnlyChat = useMemo(\\n () => displayState === \\\"full\\\" && viewingChat && viewingChat.readOnly,\\n [displayState, viewingChat],\\n );\\n \\n- const chat = viewingReadOnlyChat ? viewingChat : activeChat;\\n+ const chat = viewingReadOnlyChat ? viewingChat : activeChat?.data;\\n const chatId = String(chat?._id);\\n+\\n+ // Simple approach - if we have a chat ID but no messages, refetch once\\n+ useEffect(() => {\\n+ if (\\n+ chat &&\\n+ chat._id &&\\n+ (!chat.messages || chat.messages.length === 0)\\n+ ) {\\n+ queryClient.refetchQueries({ queryKey: [\\\"chat\\\", chat._id] });\\n+ }\\n+ // eslint-disable-next-line react-hooks/exhaustive-deps\\n+ }, [chat?._id]); // Only run when the chat ID changes\\n+\\n const memoizedMessages = useMemo(() => chat?.messages || [], [chat]);\\n- const updateChatHook = useUpdateChat();\\n const publicChatOwner = viewingChat?.owner;\\n const isChatLoading = chat?.isChatLoading;\\n- const deleteAutogenRun = useDeleteAutogenRun();\\n+\\n+ const {\\n+ isStreaming,\\n+ streamingContent,\\n+ stopStreaming,\\n+ setIsStreaming,\\n+ setSubscriptionId,\\n+ clearStreamingState,\\n+ } = useStreamingMessages({ chat, updateChatHook });\\n \\n const handleError = useCallback((error) => {\\n toast.error(error.message);\\n@@ -41,6 +67,9 @@ function ChatContent({\\n const handleSend = useCallback(\\n async (text) => {\\n try {\\n+ // Reset streaming state\\n+ clearStreamingState();\\n+\\n // Optimistic update for the user's message\\n const optimisticUserMessage = {\\n payload: text,\\n@@ -50,13 +79,16 @@ function ChatContent({\\n position: \\\"single\\\",\\n };\\n \\n+ // Use messages directly without processing\\n+ const userMessages = [\\n+ ...(chat?.messages || []),\\n+ optimisticUserMessage,\\n+ ];\\n+\\n // Show the user message immediately\\n await updateChatHook.mutateAsync({\\n chatId: String(chat?._id),\\n- messages: [\\n- ...(chat?.messages || []),\\n- optimisticUserMessage,\\n- ],\\n+ messages: userMessages,\\n isChatLoading: true,\\n });\\n \\n@@ -101,14 +133,31 @@ function ChatContent({\\n title: chat?.title,\\n chatId,\\n codeRequestId: codeRequestIdParam,\\n+ stream: streamingEnabled,\\n };\\n \\n // Perform RAG start query\\n const result = await client.query({\\n- query: QUERIES.RAG_START,\\n+ query: QUERIES.SYS_ENTITY_START,\\n variables,\\n });\\n \\n+ // If streaming is enabled, handle subscription setup\\n+ if (streamingEnabled) {\\n+ const subscriptionId =\\n+ result.data?.sys_entity_start?.result;\\n+ if (subscriptionId) {\\n+ // Set streaming state BEFORE setting subscription ID\\n+ setIsStreaming(true);\\n+\\n+ // Finally set the subscription ID which will trigger the subscription\\n+ setSubscriptionId(subscriptionId);\\n+\\n+ return; // Make sure we return here to prevent non-streaming handling\\n+ }\\n+ }\\n+\\n+ // Non-streaming response handling\\n let resultMessage = \\\"\\\";\\n let tool = null;\\n let newTitle = null;\\n@@ -118,12 +167,14 @@ function ChatContent({\\n try {\\n let resultObj;\\n try {\\n- resultObj = JSON.parse(result.data.rag_start.result);\\n+ resultObj = JSON.parse(\\n+ result.data.sys_entity_start.result,\\n+ );\\n } catch {\\n- resultObj = { response: result.data.rag_start.result };\\n+ resultObj = result.data.sys_entity_start.result;\\n }\\n- resultMessage = resultObj?.response || resultObj;\\n- tool = result.data.rag_start.tool;\\n+ resultMessage = resultObj;\\n+ tool = result.data.sys_entity_start.tool;\\n if (tool) {\\n const toolObj = JSON.parse(tool);\\n toolCallbackName = toolObj?.toolCallbackName;\\n@@ -189,9 +240,12 @@ function ChatContent({\\n sender: \\\"labeeb\\\",\\n });\\n \\n+ // Use messages directly without processing\\n+ const currentMessagesToUpdate = currentMessages;\\n+\\n await updateChatHook.mutateAsync({\\n chatId: String(chat?._id),\\n- messages: currentMessages,\\n+ messages: currentMessagesToUpdate,\\n ...(newTitle && { title: newTitle }),\\n isChatLoading: !!toolCallbackName,\\n ...(toolCallbackId && { toolCallbackId }),\\n@@ -249,41 +303,47 @@ function ChatContent({\\n sender: \\\"labeeb\\\",\\n });\\n \\n+ // Use messages directly without processing\\n+ const finalMessagesToUpdate = finalMessages;\\n+\\n await updateChatHook.mutateAsync({\\n chatId: String(chat?._id),\\n- messages: finalMessages,\\n+ messages: finalMessagesToUpdate,\\n isChatLoading: false,\\n });\\n }\\n } catch (error) {\\n+ setIsStreaming(false);\\n handleError(error);\\n- // Update to include both the original user message and the error message\\n+\\n+ // Use error messages directly without processing\\n+ const errorMessagesToUpdate = [\\n+ ...(chat?.messages || []),\\n+ {\\n+ payload: text,\\n+ sender: \\\"user\\\",\\n+ sentTime: \\\"just now\\\",\\n+ direction: \\\"outgoing\\\",\\n+ position: \\\"single\\\",\\n+ },\\n+ {\\n+ payload: t(\\n+ \\\"Something went wrong trying to respond to your request. Please try something else or start over to continue.\\\",\\n+ ),\\n+ sender: \\\"labeeb\\\",\\n+ sentTime: \\\"just now\\\",\\n+ direction: \\\"incoming\\\",\\n+ position: \\\"single\\\",\\n+ },\\n+ ];\\n+\\n await updateChatHook.mutateAsync({\\n chatId: String(chat?._id),\\n- messages: [\\n- ...(chat?.messages || []),\\n- {\\n- payload: text,\\n- sender: \\\"user\\\",\\n- sentTime: \\\"just now\\\",\\n- direction: \\\"outgoing\\\",\\n- position: \\\"single\\\",\\n- },\\n- {\\n- payload: t(\\n- \\\"Something went wrong trying to respond to your request. Please try something else or start over to continue.\\\",\\n- ),\\n- sender: \\\"labeeb\\\",\\n- sentTime: \\\"just now\\\",\\n- direction: \\\"incoming\\\",\\n- position: \\\"single\\\",\\n- },\\n- ],\\n+ messages: errorMessagesToUpdate,\\n isChatLoading: false,\\n });\\n }\\n },\\n- // eslint-disable-next-line react-hooks/exhaustive-deps\\n [\\n chat,\\n updateChatHook,\\n@@ -291,8 +351,13 @@ function ChatContent({\\n user,\\n memoizedMessages,\\n handleError,\\n- chatId,\\n t,\\n+ chatId,\\n+ clearStreamingState,\\n+ deleteAutogenRun,\\n+ setIsStreaming,\\n+ setSubscriptionId,\\n+ streamingEnabled,\\n ],\\n );\\n \\n@@ -323,6 +388,9 @@ function ChatContent({\\n container={container}\\n displayState={displayState}\\n chatId={chatId}\\n+ isStreaming={isStreaming}\\n+ streamingContent={streamingContent}\\n+ onStopStreaming={stopStreaming}\\n />\\n );\\n }\\ndiff --git a/src/components/chat/ChatMessage.js b/src/components/chat/ChatMessage.js\\nindex 033f7c0..7a6334c 100644\\n--- a/src/components/chat/ChatMessage.js\\n+++ b/src/components/chat/ChatMessage.js\\n@@ -12,6 +12,7 @@ import rehypeRaw from \\\"rehype-raw\\\";\\n import remarkMath from \\\"remark-math\\\";\\n import \\\"katex/dist/katex.min.css\\\";\\n import { visit } from \\\"unist-util-visit\\\";\\n+import ChatImage from \\\"../images/ChatImage\\\";\\n \\n function transformToCitation(content) {\\n return content\\n@@ -56,10 +57,8 @@ function customMarkdownDirective() {\\n \\n function convertMessageToMarkdown(message) {\\n const { payload, tool } = message;\\n-\\n const citations = tool ? JSON.parse(tool).citations : null;\\n-\\n- let componentIndex = 0;\\n+ let componentIndex = 0; // Counter for code blocks\\n \\n if (typeof payload !== \\\"string\\\") {\\n return payload;\\n@@ -93,6 +92,7 @@ function convertMessageToMarkdown(message) {\\n p({ node, ...rest }) {\\n return
;\\n },\\n+ img: ChatImage,\\n cd_inline_emotion({ children, emotion }) {\\n return (\\n \\ndiff --git a/src/components/chat/ChatMessages.js b/src/components/chat/ChatMessages.js\\nindex 1035a06..5bb49b3 100644\\n--- a/src/components/chat/ChatMessages.js\\n+++ b/src/components/chat/ChatMessages.js\\n@@ -1,15 +1,9 @@\\n-import React, { useContext, useCallback, useMemo } from \\\"react\\\";\\n+import React, { useContext, useCallback, useMemo, useRef } from \\\"react\\\";\\n import { useTranslation } from \\\"react-i18next\\\";\\n-import { AiOutlineReload, AiOutlineSave } from \\\"react-icons/ai\\\";\\n import dynamic from \\\"next/dynamic\\\";\\n-import { useApolloClient } from \\\"@apollo/client\\\";\\n-import { useAddChat } from \\\"../../../app/queries/chats\\\";\\n-import { handleSaveChat } from \\\"./SaveChat\\\";\\n import { AuthContext } from \\\"../../App.js\\\";\\n import MessageInput from \\\"./MessageInput\\\";\\n import MessageList from \\\"./MessageList\\\";\\n-import config from \\\"../../../config\\\";\\n-import { convertMessageToMarkdown } from \\\"./ChatMessage\\\";\\n \\n const ChatTopMenuDynamic = dynamic(() => import(\\\"./ChatTopMenu\\\"));\\n \\n@@ -22,89 +16,50 @@ const ChatMessages = React.memo(function ChatMessages({\\n viewingReadOnlyChat,\\n publicChatOwner,\\n chatId,\\n+ streamingContent,\\n+ isStreaming,\\n+ onStopStreaming,\\n }) {\\n const { user } = useContext(AuthContext);\\n- const { aiName } = user;\\n const { t } = useTranslation();\\n- const client = useApolloClient();\\n- const addChat = useAddChat();\\n-\\n- const processedMessages = useMemo(() => {\\n- return messages.map((m, index) => {\\n- const baseMessage = {\\n- ...m,\\n- text: m.payload,\\n- };\\n-\\n- if (m.sender === \\\"labeeb\\\") {\\n- return {\\n- ...baseMessage,\\n- payload: (\\n- \\n- {convertMessageToMarkdown(m)}\\n- \\n- ),\\n- };\\n- }\\n- return baseMessage;\\n- });\\n- }, [messages]);\\n-\\n- const handleSaveChatCallback = useCallback(() => {\\n- handleSaveChat(messages, client, addChat);\\n- }, [messages, client, addChat]);\\n+ const { aiName } = user;\\n+ const messageListRef = useRef(null);\\n \\n const handleSendCallback = useCallback(\\n- (message) => {\\n- onSend(message);\\n+ (text) => {\\n+ // Reset scroll state when user sends a message\\n+ messageListRef.current?.scrollBottomRef?.current?.resetScrollState();\\n+ onSend(text);\\n },\\n [onSend],\\n );\\n \\n const inputPlaceholder = useMemo(() => {\\n- return container === \\\"chatbox\\\"\\n- ? t(`Send message`)\\n- : `${t(\\\"Send a message to\\\")} ${t(aiName || config?.chat?.botName)}`;\\n- }, [container, t, aiName]);\\n+ if (container === \\\"codebox\\\") {\\n+ return t(\\\"Ask me to write, explain, or fix code\\\");\\n+ }\\n+ return t(\\\"Send a message\\\");\\n+ }, [container, t]);\\n \\n return (\\n-
\\n-
\\n-
\\n- \\n- {false && processedMessages.length > 0 && (\\n-
\\n- {\\n- if (window.confirm(t(\\\"Are you sure?\\\"))) {\\n- console.log(\\\"Reset chat\\\");\\n- }\\n- }}\\n- >\\n- \\n- {t(\\\"Reset chat\\\")}\\n- \\n- \\n- \\n- {t(\\\"Save chat\\\")}\\n- \\n-
\\n- )}\\n-
\\n-
\\n- \\n-
\\n+
\\n+
\\n+ \\n+
\\n+
\\n+ \\n
\\n
\\n \\n
\\n
\\ndiff --git a/src/components/chat/MessageInput.js b/src/components/chat/MessageInput.js\\nindex a73a74d..db40b1f 100644\\n--- a/src/components/chat/MessageInput.js\\n+++ b/src/components/chat/MessageInput.js\\n@@ -1,5 +1,5 @@\\n import \\\"highlight.js/styles/github.css\\\";\\n-import { useContext, useState } from \\\"react\\\";\\n+import { useContext, useState, useEffect } from \\\"react\\\";\\n import { RiSendPlane2Fill } from \\\"react-icons/ri\\\";\\n import TextareaAutosize from \\\"react-textarea-autosize\\\";\\n import classNames from \\\"../../../app/utils/class-names\\\";\\n@@ -14,8 +14,8 @@ import {\\n loadingError,\\n } from \\\"../../stores/fileUploadSlice\\\";\\n import { FaFileCirclePlus } from \\\"react-icons/fa6\\\";\\n-import { IoCloseCircle } from \\\"react-icons/io5\\\";\\n-import { getFilename, isDocumentUrl, isMediaUrl } from \\\"./MyFilePond\\\";\\n+import { IoCloseCircle, IoStopCircle } from \\\"react-icons/io5\\\";\\n+import { getFilename, isDocumentUrl, isMediaUrl } from \\\"../../utils/mediaUtils\\\";\\n import { AuthContext } from \\\"../../App\\\";\\n import { useAddDocument } from \\\"../../../app/queries/uploadedDocs\\\";\\n import {\\n@@ -34,25 +34,41 @@ function MessageInput({\\n enableRag,\\n placeholder,\\n viewingReadOnlyChat,\\n+ isStreaming,\\n+ onStopStreaming,\\n }) {\\n- const [inputValue, setInputValue] = useState(\\\"\\\");\\n- const [urlsData, setUrlsData] = useState([]);\\n- const [files, setFiles] = useState([]);\\n- const [showFileUpload, setShowFileUpload] = useState(false);\\n- const client = useApolloClient();\\n- const { user } = useContext(AuthContext);\\n+ const activeChatId = useGetActiveChatId();\\n+ const activeChat = useGetActiveChat().data;\\n+\\n+ const { user, userState, debouncedUpdateUserState } =\\n+ useContext(AuthContext);\\n const contextId = user?.contextId;\\n const dispatch = useDispatch();\\n+ const client = useApolloClient();\\n const [isUploadingMedia, setIsUploadingMedia] = useState(false);\\n const addDocument = useAddDocument();\\n- const handleInputChange = (event) => {\\n- setInputValue(event.target.value);\\n- };\\n- const activeChatId = useGetActiveChatId();\\n- const activeChat = useGetActiveChat().data;\\n const codeRequestId = activeChat?.codeRequestId;\\n const apolloClient = useApolloClient();\\n \\n+ // Only set input value on initial mount or chat change\\n+ useEffect(() => {\\n+ if (\\n+ activeChatId &&\\n+ userState?.chatInputs &&\\n+ userState.chatInputs[activeChatId]\\n+ ) {\\n+ setInputValue(userState.chatInputs[activeChatId]);\\n+ } else {\\n+ setInputValue(\\\"\\\");\\n+ }\\n+ // eslint-disable-next-line react-hooks/exhaustive-deps\\n+ }, [activeChatId]); // Only depend on activeChatId, not userState\\n+\\n+ const [inputValue, setInputValue] = useState(\\\"\\\");\\n+ const [urlsData, setUrlsData] = useState([]);\\n+ const [files, setFiles] = useState([]);\\n+ const [showFileUpload, setShowFileUpload] = useState(false);\\n+\\n const prepareMessage = (inputText) => {\\n return [\\n JSON.stringify({ type: \\\"text\\\", text: inputText }),\\n@@ -76,6 +92,20 @@ function MessageInput({\\n ];\\n };\\n \\n+ const handleInputChange = (event) => {\\n+ const newValue = event.target.value;\\n+ setInputValue(newValue);\\n+\\n+ if (activeChatId) {\\n+ debouncedUpdateUserState((prevState) => ({\\n+ chatInputs: {\\n+ ...(prevState?.chatInputs || {}),\\n+ [activeChatId]: newValue,\\n+ },\\n+ }));\\n+ }\\n+ };\\n+\\n const handleFormSubmit = (event) => {\\n event.preventDefault();\\n if (codeRequestId && inputValue) {\\n@@ -89,6 +119,14 @@ function MessageInput({\\n });\\n \\n setInputValue(\\\"\\\");\\n+ if (activeChatId) {\\n+ debouncedUpdateUserState((prevState) => ({\\n+ chatInputs: {\\n+ ...(prevState?.chatInputs || {}),\\n+ [activeChatId]: \\\"\\\",\\n+ },\\n+ }));\\n+ }\\n return;\\n }\\n if (!loading && inputValue) {\\n@@ -97,6 +135,15 @@ function MessageInput({\\n setInputValue(\\\"\\\");\\n setFiles([]);\\n setUrlsData([]);\\n+\\n+ if (activeChatId) {\\n+ debouncedUpdateUserState((prevState) => ({\\n+ chatInputs: {\\n+ ...(prevState?.chatInputs || {}),\\n+ [activeChatId]: \\\"\\\",\\n+ },\\n+ }));\\n+ }\\n }\\n };\\n \\n@@ -165,7 +212,7 @@ function MessageInput({\\n setIsUploadingMedia={setIsUploadingMedia}\\n />\\n )}\\n-
\\n+
\\n \\n
\\n
\\n- \\n- \\n- \\n+ {isStreaming ? (\\n+ \\n+ \\n+ \\n+ ) : (\\n+ \\n+ \\n+ \\n+ )}\\n
\\n
\\n \\ndiff --git a/src/components/chat/MessageList.js b/src/components/chat/MessageList.js\\nindex 77d060b..9bc6626 100644\\n--- a/src/components/chat/MessageList.js\\n+++ b/src/components/chat/MessageList.js\\n@@ -1,5 +1,10 @@\\n import i18next from \\\"i18next\\\";\\n-import React, { useEffect, useContext, useCallback } from \\\"react\\\";\\n+import React, {\\n+ useEffect,\\n+ useCallback,\\n+ useRef,\\n+ useImperativeHandle,\\n+} from \\\"react\\\";\\n import { useTranslation } from \\\"react-i18next\\\";\\n import { AiFillFilePdf, AiFillFileText, AiOutlineRobot } from \\\"react-icons/ai\\\";\\n import { FaUserCircle } from \\\"react-icons/fa\\\";\\n@@ -13,30 +18,38 @@ import {\\n getFilename,\\n isAudioUrl,\\n isVideoUrl,\\n-} from \\\"./MyFilePond\\\";\\n+} from \\\"../../utils/mediaUtils\\\";\\n import CopyButton from \\\"../CopyButton\\\";\\n-import { AuthContext } from \\\"../../App.js\\\";\\n import { useGetActiveChat, useUpdateChat } from \\\"../../../app/queries/chats\\\";\\n import ProgressUpdate from \\\"../editor/ProgressUpdate\\\";\\n import { useGetAutogenRun } from \\\"../../../app/queries/autogen\\\";\\n+import StreamingMessage from \\\"./StreamingMessage\\\";\\n+import ChatImage from \\\"../images/ChatImage\\\";\\n \\n-const getLoadState = (message) => {\\n- const hasImage =\\n- Array.isArray(message.payload) &&\\n- message.payload.some((p) => {\\n- try {\\n- const obj = JSON.parse(p);\\n- return obj.type === \\\"image_url\\\";\\n- } catch (e) {\\n- return false;\\n- }\\n- });\\n+const hasImages = (message) => {\\n+ if (!Array.isArray(message.payload)) return false;\\n \\n- if (hasImage) {\\n- return false;\\n- } else {\\n- return true;\\n- }\\n+ return message.payload.some((p) => {\\n+ try {\\n+ const obj = JSON.parse(p);\\n+ return obj.type === \\\"image_url\\\";\\n+ } catch (e) {\\n+ return false;\\n+ }\\n+ });\\n+};\\n+\\n+const countImages = (message) => {\\n+ if (!Array.isArray(message.payload)) return 0;\\n+\\n+ return message.payload.reduce((count, p) => {\\n+ try {\\n+ const obj = JSON.parse(p);\\n+ return obj.type === \\\"image_url\\\" ? count + 1 : count;\\n+ } catch (e) {\\n+ return count;\\n+ }\\n+ }, 0);\\n };\\n \\n const getToolMetadata = (toolName, t) => {\\n@@ -75,422 +88,649 @@ const parseToolData = (toolString) => {\\n }\\n };\\n \\n-// Displays the list of messages and a message input box.\\n-function MessageList({ messages, bot, loading, chatId }) {\\n- const { user } = useContext(AuthContext);\\n- const { aiName } = user;\\n- const { language } = i18next;\\n- const { getLogo } = config.global;\\n- const { t } = useTranslation();\\n- const [messageLoadState, setMessageLoadState] = React.useState(\\n- messages.map((m) => {\\n- return {\\n- id: m.id,\\n- loaded: getLoadState(m),\\n- };\\n- }),\\n- );\\n- const chat = useGetActiveChat()?.data;\\n- const updateChat = useUpdateChat();\\n- const codeRequestId = chat?.codeRequestId;\\n- const getAutogenRun = useGetAutogenRun(codeRequestId);\\n-\\n- const setCodeRequestFinalData = useCallback(\\n- (data) => {\\n- const message = {\\n- payload: data,\\n- sender: \\\"labeeb\\\",\\n- sentTime: \\\"just now\\\",\\n- direction: \\\"incoming\\\",\\n- position: \\\"single\\\",\\n- tool: '{\\\"toolUsed\\\":\\\"coding\\\"}',\\n- };\\n-\\n- updateChat.mutateAsync({\\n- chatId,\\n- codeRequestId: null,\\n- isChatLoading: false,\\n- messages: [...chat.messages, message],\\n- });\\n- },\\n- [chat?.messages, chatId, updateChat],\\n+const getYoutubeEmbedUrl = (url) => {\\n+ try {\\n+ const urlObj = new URL(url);\\n+ if (urlObj.hostname === \\\"youtu.be\\\") {\\n+ const videoId = urlObj.pathname.slice(1);\\n+ return `https://www.youtube.com/embed/${videoId}`;\\n+ } else if (\\n+ urlObj.hostname === \\\"youtube.com\\\" ||\\n+ urlObj.hostname === \\\"www.youtube.com\\\"\\n+ ) {\\n+ const videoId = urlObj.searchParams.get(\\\"v\\\");\\n+ return `https://www.youtube.com/embed/${videoId}`;\\n+ }\\n+ } catch (err) {\\n+ return null;\\n+ }\\n+ return null;\\n+};\\n+\\n+// Add memoized YouTube component\\n+const MemoizedYouTubeEmbed = React.memo(({ url, onLoad }) => {\\n+ return (\\n+ \\n );\\n+});\\n \\n- useEffect(() => {\\n- const data = getAutogenRun?.data?.data?.data;\\n- if (data) {\\n- setCodeRequestFinalData(data);\\n+// Add this near the top of the file, after imports:\\n+const MemoizedMarkdownMessage = React.memo(\\n+ ({ message }) => {\\n+ return convertMessageToMarkdown(message);\\n+ },\\n+ (prevProps, nextProps) => {\\n+ // If messages are completely identical, no need to re-render\\n+ if (prevProps.message === nextProps.message) {\\n+ return true;\\n }\\n- }, [getAutogenRun?.data?.data, setCodeRequestFinalData]);\\n \\n- const messageLoadStateRef = React.useRef(messageLoadState);\\n+ // If payloads are strings and identical, no need to re-render\\n+ if (\\n+ typeof prevProps.message.payload === \\\"string\\\" &&\\n+ typeof nextProps.message.payload === \\\"string\\\" &&\\n+ prevProps.message.payload === nextProps.message.payload\\n+ ) {\\n+ return true;\\n+ }\\n \\n- useEffect(() => {\\n- // merge load state\\n- const newMessageLoadState = messages.map((m) => {\\n- const existing = messageLoadStateRef.current.find(\\n- (mls) => mls.id === m.id,\\n- );\\n- if (existing) {\\n- return existing;\\n+ // For array payloads, we need to compare each item\\n+ if (\\n+ Array.isArray(prevProps.message.payload) &&\\n+ Array.isArray(nextProps.message.payload)\\n+ ) {\\n+ if (\\n+ prevProps.message.payload.length !==\\n+ nextProps.message.payload.length\\n+ ) {\\n+ return false;\\n }\\n- return {\\n- id: m.id,\\n- loaded: getLoadState(m),\\n- };\\n- });\\n-\\n- setMessageLoadState(newMessageLoadState);\\n- }, [messages]);\\n-\\n- let rowHeight = \\\"h-12 [.docked_&]:h-10\\\";\\n- let basis =\\n- \\\"min-w-[3rem] basis-12 [.docked_&]:basis-10 [.docked_&]:min-w-[2.5rem]\\\";\\n- let buttonWidthClass = \\\"w-12 [.docked_&]:w-10\\\";\\n- const botName =\\n- bot === \\\"code\\\"\\n- ? config?.code?.botName\\n- : aiName || config?.chat?.botName;\\n-\\n- const renderMessage = (message) => {\\n- let avatar;\\n- const toolData = parseToolData(message.tool);\\n-\\n- if (message.sender === \\\"labeeb\\\") {\\n- avatar = toolData?.avatarImage ? (\\n- \\n- ) : bot === \\\"code\\\" ? (\\n- \\n- ) : (\\n- \\n- );\\n \\n- return (\\n- \\n-
\\n- {toolData?.toolUsed && (\\n-
\\n- \\n- {getToolMetadata(toolData.toolUsed, t).icon}\\n- \\n- \\n- {t(\\\"Used {{tool}} tool\\\", {\\n- tool: getToolMetadata(\\n- toolData.toolUsed,\\n- t,\\n- ).translatedName,\\n- })}\\n- \\n-
\\n- )}\\n- \\n-
\\n+ // Compare each item in the array\\n+ return prevProps.message.payload.every((item, index) => {\\n+ const nextItem = nextProps.message.payload[index];\\n+ try {\\n+ const prevObj =\\n+ typeof item === \\\"string\\\" ? JSON.parse(item) : item;\\n+ const nextObj =\\n+ typeof nextItem === \\\"string\\\"\\n+ ? JSON.parse(nextItem)\\n+ : nextItem;\\n \\n-
{avatar}
\\n- \\n-
\\n-
{t(botName)}
\\n- {\\n- if (el) {\\n- const images =\\n- el.getElementsByTagName(\\\"img\\\");\\n- Array.from(images).forEach((img) => {\\n- if (!img.complete) {\\n- img.addEventListener(\\n- \\\"load\\\",\\n- () =>\\n- handleMessageLoad(\\n- message.id,\\n- ),\\n- );\\n- }\\n- });\\n+ // For image URLs, only compare the base URL without query parameters\\n+ if (\\n+ prevObj.type === \\\"image_url\\\" &&\\n+ nextObj.type === \\\"image_url\\\"\\n+ ) {\\n+ const prevUrl = new URL(\\n+ prevObj.url ||\\n+ prevObj.image_url?.url ||\\n+ prevObj.gcs,\\n+ ).pathname;\\n+ const nextUrl = new URL(\\n+ nextObj.url ||\\n+ nextObj.image_url?.url ||\\n+ nextObj.gcs,\\n+ ).pathname;\\n+ return prevUrl === nextUrl;\\n+ }\\n+\\n+ return JSON.stringify(prevObj) === JSON.stringify(nextObj);\\n+ } catch (e) {\\n+ // If JSON parsing fails, compare as strings\\n+ return item === nextItem;\\n+ }\\n+ });\\n+ }\\n+\\n+ // Default to re-rendering if we can't determine equality\\n+ return false;\\n+ },\\n+);\\n+\\n+// Create a memoized component for the static message list content\\n+const MessageListContent = React.memo(function MessageListContent({\\n+ messages,\\n+ renderMessage,\\n+ handleMessageLoad,\\n+ isVideoUrl,\\n+ isAudioUrl,\\n+ getExtension,\\n+ getFilename,\\n+ getYoutubeEmbedUrl,\\n+}) {\\n+ return messages.map((message, index) => {\\n+ const newMessage = { ...message };\\n+ if (!newMessage.id) {\\n+ newMessage.id = newMessage._id || index;\\n+ }\\n+ let display;\\n+ if (Array.isArray(newMessage.payload)) {\\n+ const arr = newMessage.payload.map((t, index2) => {\\n+ try {\\n+ const obj = JSON.parse(t);\\n+ if (obj.type === \\\"text\\\") {\\n+ return obj.text;\\n+ } else if (obj.type === \\\"image_url\\\") {\\n+ const src = obj?.url || obj?.image_url?.url || obj?.gcs;\\n+ if (isVideoUrl(src)) {\\n+ const youtubeEmbedUrl = getYoutubeEmbedUrl(src);\\n+ if (youtubeEmbedUrl) {\\n+ return (\\n+ \\n+ handleMessageLoad(newMessage.id)\\n+ }\\n+ />\\n+ );\\n+ }\\n+ return (\\n+ \\n+ handleMessageLoad(newMessage.id)\\n }\\n- }}\\n- >\\n- {message.payload}\\n+ key={`video-${index}-${index2}`}\\n+ src={src}\\n+ className=\\\"max-h-[20%] max-w-[60%] [.docked_&]:max-w-[90%] rounded border-0 my-2 shadow-lg dark:shadow-black/30\\\"\\n+ style={{\\n+ backgroundColor: \\\"transparent\\\",\\n+ }}\\n+ controls\\n+ preload=\\\"metadata\\\"\\n+ playsInline\\n+ />\\n+ );\\n+ } else if (isAudioUrl(src)) {\\n+ return (\\n+ \\n+ handleMessageLoad(newMessage.id)\\n+ }\\n+ key={`audio-${index}-${index2}`}\\n+ src={src}\\n+ className=\\\"max-h-[20%] max-w-[100%] [.docked_&]:max-w-[80%] rounded-md border bg-white p-1 my-2 dark:border-neutral-700 dark:bg-neutral-800 shadow-lg dark:shadow-black/30\\\"\\n+ controls\\n+ />\\n+ );\\n+ }\\n+\\n+ if (getExtension(src) === \\\".pdf\\\") {\\n+ const filename = decodeURIComponent(\\n+ getFilename(src),\\n+ );\\n+ return (\\n+ \\n+ handleMessageLoad(newMessage.id)\\n+ }\\n+ href={src}\\n+ target=\\\"_blank\\\"\\n+ rel=\\\"noopener noreferrer\\\"\\n+ >\\n+ \\n+ {filename}\\n+ \\n+ );\\n+ }\\n+\\n+ if (getExtension(src) === \\\".txt\\\") {\\n+ const filename = decodeURIComponent(\\n+ getFilename(src),\\n+ );\\n+ return (\\n+ \\n+ handleMessageLoad(newMessage.id)\\n+ }\\n+ href={src}\\n+ target=\\\"_blank\\\"\\n+ rel=\\\"noopener noreferrer\\\"\\n+ >\\n+ \\n+ {filename}\\n+ \\n+ );\\n+ }\\n+\\n+ return (\\n+
\\n+ \\n+ handleMessageLoad(newMessage.id)\\n+ }\\n+ />\\n
\\n-
\\n-
\\n-
\\n- );\\n+ );\\n+ }\\n+ return null;\\n+ } catch (e) {\\n+ console.error(\\\"Invalid JSON:\\\", t);\\n+ return t;\\n+ }\\n+ });\\n+ display = <>{arr};\\n } else {\\n- avatar = (\\n- \\n- );\\n- return (\\n- \\n- \\n-
{avatar}
\\n- \\n-
{t(\\\"You\\\")}
\\n-
\\n-                            {message.payload}\\n-                        
\\n-
\\n-
\\n- );\\n+ display = newMessage.payload;\\n }\\n- };\\n \\n- const handleMessageLoad = (id) => {\\n- setMessageLoadState((prev) => {\\n- return prev.map((m) => {\\n- if (m.id === id) {\\n- return {\\n- id: m.id,\\n- loaded: true,\\n- };\\n- }\\n- return m;\\n+ return (\\n+
\\n+ {renderMessage({ ...newMessage, payload: display })}\\n+
\\n+ );\\n+ });\\n+});\\n+\\n+// Displays the list of messages and a message input box.\\n+const MessageList = React.memo(\\n+ React.forwardRef(function MessageList(\\n+ {\\n+ messages,\\n+ bot,\\n+ loading,\\n+ chatId,\\n+ streamingContent,\\n+ isStreaming,\\n+ aiName,\\n+ onSend,\\n+ },\\n+ ref,\\n+ ) {\\n+ const { language } = i18next;\\n+ const { getLogo } = config.global;\\n+ const { t } = useTranslation();\\n+ const scrollBottomRef = useRef(null);\\n+\\n+ // Forward scrollBottomRef to parent\\n+ useImperativeHandle(\\n+ ref,\\n+ () => ({\\n+ scrollBottomRef,\\n+ }),\\n+ [],\\n+ );\\n+\\n+ const [messageLoadState, setMessageLoadState] = React.useState(\\n+ messages.map((m) => ({\\n+ id: m.id,\\n+ loaded: false,\\n+ imagesCount: 0,\\n+ loadedImagesCount: 0,\\n+ })),\\n+ );\\n+ const messageLoadStateRef = React.useRef(messageLoadState);\\n+ const prevMessageIdsRef = React.useRef(\\n+ messages.map((m) => m?.id).join(\\\",\\\"),\\n+ );\\n+ const prevStreamingContentRef = React.useRef(streamingContent);\\n+ const prevChatIdRef = React.useRef(chatId);\\n+\\n+ const chat = useGetActiveChat()?.data;\\n+ const updateChat = useUpdateChat();\\n+ const codeRequestId = chat?.codeRequestId;\\n+ const getAutogenRun = useGetAutogenRun(codeRequestId);\\n+\\n+ // Reset scroll when switching chats\\n+ useEffect(() => {\\n+ if (chatId !== prevChatIdRef.current) {\\n+ scrollBottomRef.current?.resetScrollState();\\n+ prevChatIdRef.current = chatId;\\n+ }\\n+ }, [chatId]);\\n+\\n+ // Track streaming content updates without forcing scroll\\n+ React.useEffect(() => {\\n+ prevStreamingContentRef.current = streamingContent;\\n+ }, [streamingContent]);\\n+\\n+ const setCodeRequestFinalData = useCallback(\\n+ (data) => {\\n+ const message = {\\n+ payload: data,\\n+ sender: \\\"labeeb\\\",\\n+ sentTime: \\\"just now\\\",\\n+ direction: \\\"incoming\\\",\\n+ position: \\\"single\\\",\\n+ tool: '{\\\"toolUsed\\\":\\\"coding\\\"}',\\n+ };\\n+\\n+ updateChat.mutateAsync({\\n+ chatId,\\n+ codeRequestId: null,\\n+ isChatLoading: false,\\n+ messages: chat?.messages\\n+ ? [...chat.messages, message]\\n+ : [message],\\n+ });\\n+ },\\n+ [chatId, updateChat, chat?.messages],\\n+ );\\n+\\n+ useEffect(() => {\\n+ const data = getAutogenRun?.data?.data?.data;\\n+ if (data) {\\n+ setCodeRequestFinalData(data);\\n+ }\\n+ }, [getAutogenRun?.data?.data, setCodeRequestFinalData]);\\n+\\n+ useEffect(() => {\\n+ const newMessageIds = messages.map((m) => m?.id).join(\\\",\\\");\\n+ if (prevMessageIdsRef.current === newMessageIds) return;\\n+\\n+ prevMessageIdsRef.current = newMessageIds;\\n+ const newMessageLoadState = messages.map((m) => {\\n+ const existing = messageLoadStateRef.current.find(\\n+ (mls) => mls.id === m.id,\\n+ );\\n+ if (existing) return existing;\\n+\\n+ const messageHasImages = hasImages(m);\\n+ const imageCount = messageHasImages ? countImages(m) : 0;\\n+ return {\\n+ id: m.id,\\n+ loaded: !messageHasImages, // If no images, mark as loaded immediately\\n+ imagesCount: imageCount,\\n+ loadedImagesCount: 0,\\n+ };\\n });\\n- });\\n- };\\n \\n- const loadComplete = messageLoadState.every((m) => m.loaded);\\n+ messageLoadStateRef.current = newMessageLoadState;\\n+ setMessageLoadState(newMessageLoadState);\\n+ }, [messages]);\\n \\n- return (\\n- <>\\n- \\n- {messages.length === 0 && (\\n-
\\n- {t(\\\"Send a message to start a conversation\\\")}\\n-
\\n- )}\\n- {messages.map((message, index) => {\\n- const newMessage = { ...message };\\n- if (!newMessage.id) {\\n- newMessage.id = newMessage._id || index;\\n+ const rowHeight = \\\"h-12 [.docked_&]:h-10\\\";\\n+ const basis =\\n+ \\\"min-w-[3rem] basis-12 [.docked_&]:basis-10 [.docked_&]:min-w-[2.5rem]\\\";\\n+ const buttonWidthClass = \\\"w-12 [.docked_&]:w-10\\\";\\n+ const botName =\\n+ bot === \\\"code\\\"\\n+ ? config?.code?.botName\\n+ : aiName || config?.chat?.botName;\\n+\\n+ const handleMessageLoad = useCallback((messageId) => {\\n+ setMessageLoadState((prev) =>\\n+ prev.map((m) => {\\n+ if (m.id === messageId) {\\n+ const newLoadedCount = m.loadedImagesCount + 1;\\n+ return {\\n+ ...m,\\n+ loadedImagesCount: newLoadedCount,\\n+ loaded: newLoadedCount >= m.imagesCount,\\n+ };\\n }\\n- let display;\\n- if (Array.isArray(newMessage.payload)) {\\n- const arr = newMessage.payload.map((t, index2) => {\\n- try {\\n- const obj = JSON.parse(t);\\n- if (obj.type === \\\"text\\\") {\\n- return obj.text;\\n- } else if (obj.type === \\\"image_url\\\") {\\n- const src =\\n- obj?.url ||\\n- obj?.image_url?.url ||\\n- obj?.gcs;\\n- if (isVideoUrl(src)) {\\n- // Display the video\\n- return (\\n- {\\n- handleMessageLoad(\\n- newMessage.id,\\n- );\\n- }}\\n- key={`video-${index}-${index2}`}\\n- src={src}\\n- className=\\\"max-h-[20%] max-w-[60%] [.docked_&]:max-w-[90%] rounded border bg-white p-1 my-2 dark:border-neutral-700 dark:bg-neutral-800 shadow-lg dark:shadow-black/30\\\"\\n- controls\\n- preload=\\\"metadata\\\"\\n- playsInline\\n- />\\n- );\\n- } else if (isAudioUrl(src)) {\\n- // Display the audio\\n- return (\\n- {\\n- handleMessageLoad(\\n- newMessage.id,\\n- );\\n- }}\\n- key={`audio-${index}-${index2}`}\\n- src={src}\\n- className=\\\"max-h-[20%] max-w-[100%] [.docked_&]:max-w-[80%] rounded-md border bg-white p-1 my-2 dark:border-neutral-700 dark:bg-neutral-800 shadow-lg dark:shadow-black/30\\\"\\n- controls\\n- />\\n- );\\n- }\\n+ return m;\\n+ }),\\n+ );\\n+ }, []);\\n \\n- if (getExtension(src) === \\\".pdf\\\") {\\n- const filename = decodeURIComponent(\\n- getFilename(src),\\n- );\\n-\\n- return (\\n- {\\n- handleMessageLoad(\\n- newMessage.id,\\n- );\\n- }}\\n- href={src}\\n- target=\\\"_blank\\\"\\n- rel=\\\"noopener noreferrer\\\"\\n- >\\n- \\n- {filename}\\n- \\n- );\\n- }\\n+ const handleImageLoad = useCallback(\\n+ (messageId) => {\\n+ handleMessageLoad(messageId);\\n+ },\\n+ [handleMessageLoad],\\n+ );\\n \\n- if (getExtension(src) === \\\".txt\\\") {\\n- const filename = decodeURIComponent(\\n- getFilename(src),\\n- );\\n-\\n- return (\\n- {\\n- handleMessageLoad(\\n- newMessage.id,\\n- );\\n- }}\\n- href={src}\\n- target=\\\"_blank\\\"\\n- rel=\\\"noopener noreferrer\\\"\\n- >\\n- \\n- {filename}\\n- \\n- );\\n- }\\n+ const messageRef = useCallback(\\n+ (element, messageId) => {\\n+ if (!element) return;\\n \\n- // Display the image\\n- return (\\n-
\\n- {\\n- handleMessageLoad(\\n- newMessage.id,\\n- );\\n- }}\\n- src={src}\\n- alt=\\\"uploadedimage\\\"\\n- className=\\\"max-h-[20%] max-w-[60%] [.docked_&]:max-w-[90%] rounded-md border bg-white p-1 my-2 dark:border-neutral-700 dark:bg-neutral-800 shadow-lg dark:shadow-black/30\\\"\\n- />\\n-
\\n- );\\n- }\\n- return null;\\n- } catch (e) {\\n- console.error(\\\"Invalid JSON:\\\", t);\\n- return t;\\n- }\\n- });\\n- display = <>{arr};\\n- } else {\\n- display = newMessage.payload;\\n+ const images = element.getElementsByTagName(\\\"img\\\");\\n+ Array.from(images).forEach((img) => {\\n+ // Remove any existing listeners first\\n+ img.removeEventListener(\\\"load\\\", () =>\\n+ handleImageLoad(messageId),\\n+ );\\n+\\n+ if (!img.complete) {\\n+ img.addEventListener(\\\"load\\\", () =>\\n+ handleImageLoad(messageId),\\n+ );\\n }\\n+ });\\n+ },\\n+ [handleImageLoad],\\n+ );\\n+\\n+ const renderMessage = useCallback(\\n+ (message) => {\\n+ let avatar;\\n+ const toolData = parseToolData(message.tool);\\n \\n- // process the message and create a new\\n- // message object with the updated payload.\\n- const processedMessage = Object.assign({}, newMessage, {\\n- payload: (\\n- \\n- {newMessage.sender === \\\"labeeb\\\" ? (\\n- convertMessageToMarkdown(newMessage)\\n- ) : (\\n-
{display}
\\n+ if (message.sender === \\\"labeeb\\\") {\\n+ avatar = toolData?.avatarImage ? (\\n+ \\n+ ) : bot === \\\"code\\\" ? (\\n+ \\n+ ) : (\\n+ \\n+ );\\n+\\n+ return (\\n+ \\n+
\\n+ {toolData?.toolUsed && (\\n+
\\n+ \\n+ {\\n+ getToolMetadata(\\n+ toolData.toolUsed,\\n+ t,\\n+ ).icon\\n+ }\\n+ \\n+ \\n+ {t(\\\"Used {{tool}} tool\\\", {\\n+ tool: getToolMetadata(\\n+ toolData.toolUsed,\\n+ t,\\n+ ).translatedName,\\n+ })}\\n+ \\n+
\\n )}\\n- \\n- ),\\n- });\\n+ \\n+
\\n \\n+
{avatar}
\\n+ \\n+
\\n+
\\n+ {t(botName)}\\n+
\\n+ messageRef(el, message.id)}\\n+ >\\n+ \\n+ \\n+ \\n+
\\n+
\\n+
\\n+
\\n+ );\\n+ } else {\\n+ avatar = (\\n+ \\n+ );\\n return (\\n-
\\n- {renderMessage(processedMessage)}\\n+ \\n+ \\n+
\\n+ {avatar}\\n+
\\n+ \\n+
{t(\\\"You\\\")}
\\n+
\\n+                                    {message.payload}\\n+                                
\\n+
\\n
\\n );\\n- })}\\n- {loading &&\\n- renderMessage({\\n- id: \\\"loading\\\",\\n- sender: \\\"labeeb\\\",\\n- payload: (\\n-
\\n-
\\n- \\n-
\\n- {codeRequestId && (\\n-
\\n- \\n+ }\\n+ },\\n+ // eslint-disable-next-line react-hooks/exhaustive-deps\\n+ [\\n+ basis,\\n+ bot,\\n+ buttonWidthClass,\\n+ getLogo,\\n+ language,\\n+ messageRef,\\n+ rowHeight,\\n+ t,\\n+ ],\\n+ );\\n+\\n+ const loadComplete = messageLoadState.every((m) => m.loaded);\\n+\\n+ return (\\n+ \\n+
\\n+ {messages.length === 0 && !isStreaming && (\\n+
\\n+ {t(\\\"Send a message to start a conversation\\\")}\\n+
\\n+ )}\\n+
\\n+ \\n+ {isStreaming && (\\n+ \\n+ )}\\n+ {loading &&\\n+ !isStreaming &&\\n+ !codeRequestId &&\\n+ renderMessage({\\n+ id: \\\"loading\\\",\\n+ sender: \\\"labeeb\\\",\\n+ payload: (\\n+
\\n+
\\n+ \\n+
\\n
\\n- )}\\n+ ),\\n+ })}\\n+ {loading && !isStreaming && codeRequestId && (\\n+
\\n+ \\n
\\n- ),\\n- })}\\n+ )}\\n+
\\n+
\\n
\\n- \\n- );\\n-}\\n+ );\\n+ }),\\n+);\\n \\n export default MessageList;\\ndiff --git a/src/components/chat/MyFilePond.js b/src/components/chat/MyFilePond.js\\nindex 66cd148..b68fc9c 100644\\n--- a/src/components/chat/MyFilePond.js\\n+++ b/src/components/chat/MyFilePond.js\\n@@ -17,7 +17,15 @@ import FilePondPluginImagePreview from \\\"filepond-plugin-image-preview\\\";\\n import \\\"filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css\\\";\\n import { useEffect, useRef, useState } from \\\"react\\\";\\n import { useTranslation } from \\\"react-i18next\\\";\\n-import { hashMediaFile } from \\\"../../utils/mediaUtils\\\";\\n+import {\\n+ hashMediaFile,\\n+ DOC_MIME_TYPES,\\n+ ACCEPTED_FILE_TYPES,\\n+ isMediaUrl,\\n+ getFilename,\\n+ getVideoDuration,\\n+} from \\\"../../utils/mediaUtils\\\";\\n+import { isYoutubeUrl } from \\\"../../utils/urlUtils\\\";\\n \\n // Global upload speed tracking\\n let lastBytesPerMs = null; // bytes per millisecond from last successful upload including cloud processing\\n@@ -105,174 +113,9 @@ function RemoteUrlInputUI({\\n );\\n }\\n \\n-const DOC_EXTENSIONS = [\\n- \\\".json\\\",\\n- \\\".csv\\\",\\n- \\\".md\\\",\\n- \\\".xml\\\",\\n- \\\".js\\\",\\n- \\\".html\\\",\\n- \\\".css\\\",\\n- \\\".docx\\\",\\n- \\\".xlsx\\\",\\n- \\\".xls\\\",\\n- \\\".doc\\\",\\n-];\\n-\\n-const IMAGE_EXTENSIONS = [\\n- \\\".jpg\\\",\\n- \\\".jpeg\\\",\\n- \\\".png\\\",\\n- \\\".webp\\\",\\n- \\\".heic\\\",\\n- \\\".heif\\\",\\n- \\\".pdf\\\",\\n- \\\".txt\\\",\\n-];\\n-\\n-const VIDEO_EXTENSIONS = [\\n- \\\".mp4\\\",\\n- \\\".mpeg\\\",\\n- \\\".mov\\\",\\n- \\\".avi\\\",\\n- \\\".flv\\\",\\n- \\\".mpg\\\",\\n- \\\".mov\\\",\\n- \\\".webm\\\",\\n- \\\".wmv\\\",\\n- \\\".3gp\\\",\\n-];\\n-\\n-const AUDIO_EXTENSIONS = [\\\".wav\\\", \\\".mp3\\\", \\\".m4a\\\", \\\".aac\\\", \\\".ogg\\\", \\\".flac\\\"];\\n-\\n-function isDocumentUrl(url) {\\n- const urlExt = getExtension(url);\\n- return DOC_EXTENSIONS.includes(urlExt);\\n-}\\n-\\n-// Extracts the filename from a URL\\n-export function getFilename(url) {\\n- try {\\n- // Create a URL object to handle parsing\\n- const urlObject = new URL(url);\\n-\\n- // Get the pathname and remove leading/trailing slashes\\n- const path = urlObject.pathname.replace(/^\\\\/|\\\\/$/g, \\\"\\\");\\n-\\n- // Get the last part of the path (filename)\\n- const fullFilename = path.split(\\\"/\\\").pop() || \\\"\\\";\\n-\\n- // Decode the filename to handle URL encoding\\n- const decodedFilename = decodeURIComponent(fullFilename);\\n-\\n- // Split by underscore and remove the first part if it exists\\n- const parts = decodedFilename.split(\\\"_\\\");\\n- const relevantParts = parts.length > 1 ? parts.slice(1) : parts;\\n-\\n- // Join the parts back together\\n- return relevantParts.join(\\\"_\\\");\\n- } catch (error) {\\n- console.error(\\\"Error parsing URL:\\\", error);\\n- return \\\"\\\";\\n- }\\n-}\\n-\\n-export function getExtension(url) {\\n- try {\\n- const parsedUrl = new URL(url);\\n- const pathname = parsedUrl.pathname;\\n- return \\\".\\\" + pathname.split(\\\".\\\").pop().toLowerCase();\\n- } catch (error) {\\n- return \\\".\\\" + url.split(\\\".\\\").pop().split(/[?#]/)[0].toLowerCase();\\n- }\\n-}\\n-\\n-function isImageUrl(url) {\\n- const urlExt = getExtension(url);\\n- const mimeType = mime.contentType(urlExt);\\n- return (\\n- IMAGE_EXTENSIONS.includes(urlExt) &&\\n- (mimeType.startsWith(\\\"image/\\\") ||\\n- mimeType === \\\"application/pdf\\\" ||\\n- mimeType.startsWith(\\\"text/plain\\\"))\\n- );\\n-}\\n-\\n-function isVideoUrl(url) {\\n- const urlExt = getExtension(url);\\n- const mimeType = mime.contentType(urlExt);\\n- return VIDEO_EXTENSIONS.includes(urlExt) && mimeType.startsWith(\\\"video/\\\");\\n-}\\n-\\n-function isAudioUrl(url) {\\n- const urlExt = getExtension(url);\\n- const mimeType = mime.contentType(urlExt);\\n- return AUDIO_EXTENSIONS.includes(urlExt) && mimeType.startsWith(\\\"audio/\\\");\\n-}\\n-\\n-function isMediaUrl(url) {\\n- return isImageUrl(url) || isVideoUrl(url) || isAudioUrl(url);\\n-}\\n-\\n-const DOC_MIME_TYPES = DOC_EXTENSIONS.map((ext) => mime.lookup(ext));\\n-const MEDIA_MIME_TYPES = [\\n- // Images\\n- \\\"image/png\\\",\\n- \\\"image/jpeg\\\",\\n- \\\"image/webp\\\",\\n- \\\"image/heic\\\",\\n- \\\"image/heif\\\",\\n- // Videos\\n- \\\"video/mp4\\\",\\n- \\\"video/mpeg\\\",\\n- \\\"video/mov\\\",\\n- \\\"video/quicktime\\\",\\n- \\\"video/avi\\\",\\n- \\\"video/x-flv\\\",\\n- \\\"video/mpg\\\",\\n- \\\"video/webm\\\",\\n- \\\"video/wmv\\\",\\n- \\\"video/3gpp\\\",\\n- \\\"video/m4v\\\",\\n- // Audio\\n- \\\"audio/wav\\\",\\n- \\\"audio/mpeg\\\",\\n- \\\"audio/aac\\\",\\n- \\\"audio/ogg\\\",\\n- \\\"audio/flac\\\",\\n- \\\"audio/m4a\\\",\\n- \\\"audio/mp3\\\",\\n- \\\"audio/mp4\\\",\\n- \\\"audio/x-m4a\\\", // Common browser MIME type for .m4a files\\n- // PDF\\n- \\\"application/pdf\\\",\\n- // Text\\n- \\\"text/plain\\\",\\n-];\\n-\\n-const ACCEPTED_FILE_TYPES = [...DOC_MIME_TYPES, ...MEDIA_MIME_TYPES];\\n-\\n-// Add this helper function to check video duration\\n-function getVideoDuration(file) {\\n- return new Promise((resolve, reject) => {\\n- const video = document.createElement(\\\"video\\\");\\n- video.preload = \\\"metadata\\\";\\n-\\n- video.onloadedmetadata = function () {\\n- window.URL.revokeObjectURL(video.src);\\n- resolve(video.duration);\\n- };\\n-\\n- video.onerror = function () {\\n- reject(\\\"Error loading video file\\\");\\n- };\\n-\\n- video.src = URL.createObjectURL(file);\\n- });\\n-}\\n-\\n // Our app\\n function MyFilePond({ addUrl, files, setFiles, setIsUploadingMedia }) {\\n+ const pondRef = useRef(null);\\n const serverUrl = \\\"/media-helper?useGoogle=true\\\";\\n const [inputUrl, setInputUrl] = useState(\\\"\\\");\\n const [showInputUI, setShowInputUI] = useState(false);\\n@@ -288,11 +131,51 @@ function MyFilePond({ addUrl, files, setFiles, setIsUploadingMedia }) {\\n try {\\n new URL(inputUrl);\\n } catch (err) {\\n- // Invalid URL format\\n alert(t(\\\"Please enter a valid URL\\\"));\\n return;\\n }\\n \\n+ // If it's a YouTube URL, simulate an instant upload through FilePond's API\\n+ if (isYoutubeUrl(inputUrl)) {\\n+ const youtubeResponse = {\\n+ url: inputUrl,\\n+ gcs: inputUrl,\\n+ type: \\\"video/youtube\\\", // custom type used internally\\n+ filename: getFilename(inputUrl),\\n+ payload: JSON.stringify([\\n+ JSON.stringify({\\n+ type: \\\"image_url\\\",\\n+ url: inputUrl,\\n+ gcs: inputUrl,\\n+ }),\\n+ ]),\\n+ };\\n+\\n+ // Pass the response to your existing chat logic\\n+ addUrl(youtubeResponse);\\n+\\n+ // Create a pre-loaded file object\\n+ setFiles((prevFiles) => [\\n+ ...prevFiles,\\n+ {\\n+ source: youtubeResponse,\\n+ options: {\\n+ type: \\\"limbo\\\",\\n+ file: {\\n+ name: getFilename(inputUrl),\\n+ type: \\\"video/youtube\\\",\\n+ size: 0,\\n+ },\\n+ },\\n+ },\\n+ ]);\\n+\\n+ // Clear the URL input\\n+ setInputUrl(\\\"\\\");\\n+ return;\\n+ }\\n+\\n+ // For non-YouTube URLs, continue with the existing logic\\n setIsUploadingMedia(true);\\n setFiles([...files, { source: inputUrl }]);\\n setInputUrl(\\\"\\\");\\n@@ -339,6 +222,7 @@ function MyFilePond({ addUrl, files, setFiles, setIsUploadingMedia }) {\\n
\\n
\\n {\\n+ // For YouTube URLs, immediately load without fetching\\n+ if (isYoutubeUrl(fileUrl)) {\\n+ const response = {\\n+ url: fileUrl,\\n+ gcs: fileUrl,\\n+ type: \\\"video/youtube\\\",\\n+ filename: getFilename(fileUrl),\\n+ };\\n+ load(response);\\n+ return;\\n+ }\\n try {\\n const response = await axios.get(\\n- `${serverUrl}&fetch=${url}`,\\n+ `${serverUrl}&fetch=${fileUrl}`,\\n );\\n if (response.data && response.data.url) {\\n const { url } = response.data;\\n@@ -377,8 +272,6 @@ function MyFilePond({ addUrl, files, setFiles, setIsUploadingMedia }) {\\n load(\\n new Blob([url], {\\n type,\\n- filename,\\n- url,\\n }),\\n );\\n addUrl(response.data);\\n@@ -387,11 +280,7 @@ function MyFilePond({ addUrl, files, setFiles, setIsUploadingMedia }) {\\n }\\n } catch (err) {\\n console.error(err);\\n- error({\\n- body:\\n- err.response?.data || \\\"Invalid URL\\\",\\n- type: \\\"error\\\",\\n- });\\n+ error(\\\"Could not load file\\\");\\n setIsUploadingMedia(false);\\n }\\n },\\n@@ -404,6 +293,25 @@ function MyFilePond({ addUrl, files, setFiles, setIsUploadingMedia }) {\\n progress,\\n abort,\\n ) => {\\n+ // Handle YouTube URLs differently\\n+ if (\\n+ file.type === \\\"video/youtube\\\" ||\\n+ (metadata &&\\n+ metadata.type === \\\"video/youtube\\\")\\n+ ) {\\n+ const response = {\\n+ url: file.name || file,\\n+ gcs: file.name || file,\\n+ type: \\\"video/youtube\\\",\\n+ filename: getFilename(\\n+ file.name || file,\\n+ ),\\n+ };\\n+ progress(true, 100, 100);\\n+ load(response);\\n+ return;\\n+ }\\n+\\n setProcessingLabel(t(\\\"Checking file...\\\"));\\n setIsUploadingMedia(true);\\n \\n@@ -701,5 +609,3 @@ function MyFilePond({ addUrl, files, setFiles, setIsUploadingMedia }) {\\n }\\n \\n export default MyFilePond;\\n-\\n-export { isAudioUrl, isDocumentUrl, isImageUrl, isMediaUrl, isVideoUrl };\\ndiff --git a/src/components/chat/SavedChats.js b/src/components/chat/SavedChats.js\\nindex ab5d51c..f3829c8 100644\\n--- a/src/components/chat/SavedChats.js\\n+++ b/src/components/chat/SavedChats.js\\n@@ -1,8 +1,14 @@\\n+import {\\n+ DropdownMenu,\\n+ DropdownMenuContent,\\n+ DropdownMenuItem,\\n+ DropdownMenuTrigger,\\n+} from \\\"@/components/ui/dropdown-menu\\\";\\n import { PlusIcon } from \\\"@heroicons/react/24/outline\\\";\\n import dayjs from \\\"dayjs\\\";\\n import relativeTime from \\\"dayjs/plugin/relativeTime\\\";\\n import i18next from \\\"i18next\\\";\\n-import { EditIcon, TrashIcon, XIcon } from \\\"lucide-react\\\";\\n+import { EditIcon, MoreVertical, TrashIcon, XIcon } from \\\"lucide-react\\\";\\n import { useRouter } from \\\"next/navigation\\\";\\n import { useEffect, useMemo, useState } from \\\"react\\\";\\n import { useTranslation } from \\\"react-i18next\\\";\\n@@ -194,7 +200,7 @@ function SavedChats({ displayState }) {\\n className={classNames(\\n editingId === chat._id\\n ? \\\"flex\\\"\\n- : \\\"hidden group-hover:flex\\\",\\n+ : \\\"hidden sm:group-hover:flex\\\",\\n \\\"items-center gap-1 -mt-5 -me-2\\\",\\n )}\\n >\\n@@ -234,6 +240,37 @@ function SavedChats({ displayState }) {\\n \\n \\n
\\n+ {editingId !== chat._id && (\\n+
\\n+ \\n+ \\n+ \\n+ \\n+ \\n+ {/* add Edit and taxonomy options here */}\\n+ {\\n+ setEditingId(chat._id);\\n+ setEditedName(\\n+ chat.title,\\n+ );\\n+ }}\\n+ >\\n+ {t(\\\"Edit title\\\")}\\n+ \\n+ {\\n+ handleDelete(chat._id);\\n+ }}\\n+ >\\n+ {t(\\\"Delete chat\\\")}\\n+ \\n+ \\n+ \\n+
\\n+ )}\\n
\\n
\\n
    \\ndiff --git a/src/components/chat/ScrollToBottom.js b/src/components/chat/ScrollToBottom.js\\nindex 23c805e..cb68cd2 100644\\n--- a/src/components/chat/ScrollToBottom.js\\n+++ b/src/components/chat/ScrollToBottom.js\\n@@ -1,27 +1,126 @@\\n-import React, { useEffect, useRef } from \\\"react\\\";\\n+import React, {\\n+ useEffect,\\n+ useRef,\\n+ useCallback,\\n+ useImperativeHandle,\\n+ forwardRef,\\n+} from \\\"react\\\";\\n \\n-const ScrollToBottom = ({ children, loadComplete }) => {\\n+const ScrollToBottom = forwardRef(({ children, loadComplete }, ref) => {\\n const containerRef = useRef(null);\\n+ const userHasScrolledUp = useRef(false);\\n+ const lastScrollTop = useRef(0);\\n+ const scrollAttempts = useRef(0);\\n+ const maxScrollAttempts = 3; // Maximum number of scroll attempts\\n \\n+ const scrollToBottom = useCallback(() => {\\n+ if (!containerRef.current) return;\\n+\\n+ const { scrollHeight, clientHeight } = containerRef.current;\\n+ containerRef.current.scrollTo({\\n+ top: scrollHeight - clientHeight,\\n+ behavior: \\\"auto\\\",\\n+ });\\n+ }, []);\\n+\\n+ // Check if we're actually at the bottom and retry if not\\n+ const verifyScrollPosition = useCallback(() => {\\n+ if (!containerRef.current || userHasScrolledUp.current) return;\\n+\\n+ const { scrollTop, scrollHeight, clientHeight } = containerRef.current;\\n+ const isAtBottom =\\n+ Math.abs(scrollTop + clientHeight - scrollHeight) < 10;\\n+\\n+ if (!isAtBottom && scrollAttempts.current < maxScrollAttempts) {\\n+ // If not at bottom, try scrolling again with a slight delay\\n+ scrollAttempts.current += 1;\\n+ setTimeout(() => {\\n+ scrollToBottom();\\n+ // Check again after scrolling\\n+ setTimeout(verifyScrollPosition, 100);\\n+ }, 50 * scrollAttempts.current); // Increasing delay with each attempt\\n+ } else {\\n+ // Reset attempts counter after we're done\\n+ scrollAttempts.current = 0;\\n+ }\\n+ }, [scrollToBottom]);\\n+\\n+ // Enhanced scroll to bottom that verifies position\\n+ const enhancedScrollToBottom = useCallback(() => {\\n+ scrollAttempts.current = 0; // Reset attempts counter\\n+ scrollToBottom();\\n+ // Verify scroll position after initial scroll\\n+ setTimeout(verifyScrollPosition, 100);\\n+ }, [scrollToBottom, verifyScrollPosition]);\\n+\\n+ // Reset scroll state and scroll to bottom\\n+ const resetScrollState = useCallback(() => {\\n+ userHasScrolledUp.current = false;\\n+ enhancedScrollToBottom();\\n+ }, [enhancedScrollToBottom]);\\n+\\n+ // Expose reset function to parent\\n+ useImperativeHandle(\\n+ ref,\\n+ () => ({\\n+ resetScrollState,\\n+ }),\\n+ [resetScrollState],\\n+ );\\n+\\n+ // Scroll to bottom on new messages if user hasn't scrolled up\\n useEffect(() => {\\n- // Scrolls to the bottom of the chat container\\n- const scroll = () => {\\n- const scrollHeight = containerRef.current.scrollHeight;\\n- const height = containerRef.current.clientHeight;\\n- const maxScrollTop = scrollHeight - height;\\n- containerRef.current.scrollTop =\\n- maxScrollTop > 0 ? maxScrollTop : 0;\\n- };\\n-\\n- scroll();\\n- // Dependency array ensures effect runs when 'children' changes or when loading is complete\\n- }, [children, loadComplete]);\\n+ if (!userHasScrolledUp.current) {\\n+ enhancedScrollToBottom();\\n+ }\\n+ }, [children, enhancedScrollToBottom]);\\n+\\n+ // Additional effect to ensure we scroll after all content is loaded\\n+ useEffect(() => {\\n+ if (loadComplete && !userHasScrolledUp.current) {\\n+ enhancedScrollToBottom();\\n+ }\\n+ }, [loadComplete, enhancedScrollToBottom]);\\n+\\n+ // Final check after a delay to catch any late-rendering content\\n+ useEffect(() => {\\n+ if (loadComplete && !userHasScrolledUp.current) {\\n+ const timeoutId = setTimeout(() => {\\n+ verifyScrollPosition();\\n+ }, 300); // Longer delay to catch late DOM updates\\n+\\n+ return () => clearTimeout(timeoutId);\\n+ }\\n+ }, [loadComplete, verifyScrollPosition]);\\n \\n return (\\n-
    \\n+ {\\n+ if (!containerRef.current) return;\\n+\\n+ const { scrollTop, scrollHeight, clientHeight } =\\n+ containerRef.current;\\n+ const isScrollingUp = scrollTop < lastScrollTop.current;\\n+ lastScrollTop.current = scrollTop;\\n+\\n+ // If scrolling up and not already marked as scrolled up\\n+ if (isScrollingUp && !userHasScrolledUp.current) {\\n+ userHasScrolledUp.current = true;\\n+ }\\n+\\n+ // If we reach bottom, re-enable auto-scroll\\n+ const isAtBottom =\\n+ Math.abs(scrollTop + clientHeight - scrollHeight) < 10;\\n+ if (isAtBottom) {\\n+ userHasScrolledUp.current = false;\\n+ }\\n+ }}\\n+ >\\n {children}\\n
    \\n );\\n-};\\n+});\\n \\n export default ScrollToBottom;\\ndiff --git a/src/components/chat/StreamingMessage.js b/src/components/chat/StreamingMessage.js\\nnew file mode 100644\\nindex 0000000..cf6c675\\n--- /dev/null\\n+++ b/src/components/chat/StreamingMessage.js\\n@@ -0,0 +1,235 @@\\n+import React, {\\n+ useEffect,\\n+ useRef,\\n+ useState,\\n+ useCallback,\\n+ useMemo,\\n+} from \\\"react\\\";\\n+import { convertMessageToMarkdown } from \\\"./ChatMessage\\\";\\n+import { AiOutlineRobot } from \\\"react-icons/ai\\\";\\n+import classNames from \\\"../../../app/utils/class-names\\\";\\n+import config from \\\"../../../config\\\";\\n+import { useTranslation } from \\\"react-i18next\\\";\\n+import i18next from \\\"i18next\\\";\\n+import Loader from \\\"../../../app/components/loader\\\";\\n+\\n+// Memoize the content component to prevent re-renders when only the loader position changes\\n+const StreamingContent = React.memo(function StreamingContent({\\n+ content,\\n+ onContentUpdate,\\n+}) {\\n+ const contentRef = useRef(null);\\n+ const markdownContent = useMemo(() => {\\n+ return convertMessageToMarkdown({\\n+ payload: content,\\n+ sender: \\\"labeeb\\\",\\n+ });\\n+ }, [content]);\\n+\\n+ useEffect(() => {\\n+ if (contentRef.current) {\\n+ // Ensure we call onContentUpdate after the content has been rendered\\n+ requestAnimationFrame(() => {\\n+ onContentUpdate(contentRef.current);\\n+ });\\n+ }\\n+ }, [content, onContentUpdate]);\\n+\\n+ return (\\n+ \\n+ {markdownContent}\\n+
\\n+ );\\n+});\\n+\\n+const StreamingMessage = React.memo(function StreamingMessage({\\n+ content,\\n+ bot,\\n+ aiName,\\n+}) {\\n+ const contentNodeRef = useRef(null);\\n+ const [loaderPosition, setLoaderPosition] = useState({ x: 0, y: 0 });\\n+ const [showLoader, setShowLoader] = useState(false);\\n+ const lastUpdateRef = useRef(Date.now());\\n+ const loaderTimeoutRef = useRef(null);\\n+ const { t } = useTranslation();\\n+ const { language } = i18next;\\n+ const { getLogo } = config.global;\\n+\\n+ const calculateLoaderPosition = useCallback((contentNode) => {\\n+ if (!contentNode) return;\\n+\\n+ // Get all text nodes, including those in nested elements\\n+ const walker = document.createTreeWalker(\\n+ contentNode,\\n+ NodeFilter.SHOW_TEXT,\\n+ {\\n+ acceptNode: (node) => {\\n+ if (!node.textContent.trim()) {\\n+ return NodeFilter.FILTER_SKIP;\\n+ }\\n+ return NodeFilter.FILTER_ACCEPT;\\n+ },\\n+ },\\n+ );\\n+\\n+ let lastTextNode = null;\\n+ let lastNodeRect = null;\\n+\\n+ while (walker.nextNode()) {\\n+ const node = walker.currentNode;\\n+ const range = document.createRange();\\n+ range.selectNodeContents(node);\\n+ const rects = range.getClientRects();\\n+\\n+ if (rects.length > 0) {\\n+ lastTextNode = node;\\n+ lastNodeRect = rects[rects.length - 1];\\n+ }\\n+ }\\n+\\n+ if (lastTextNode && lastNodeRect) {\\n+ const range = document.createRange();\\n+ range.setStart(lastTextNode, lastTextNode.textContent.length);\\n+ range.setEnd(lastTextNode, lastTextNode.textContent.length);\\n+\\n+ const rect = range.getBoundingClientRect();\\n+ const contentRect = contentNode.getBoundingClientRect();\\n+ const textContainer = lastTextNode.parentElement;\\n+ const computedStyle = window.getComputedStyle(textContainer);\\n+ const fontSize = parseFloat(computedStyle.fontSize);\\n+ const textMiddle = rect.top + rect.height / 2;\\n+ const loaderHeight = 16;\\n+\\n+ setLoaderPosition({\\n+ x:\\n+ rect.right -\\n+ contentRect.left +\\n+ Math.min(fontSize * 0.25, 4) +\\n+ 5,\\n+ y: textMiddle - contentRect.top - loaderHeight / 2 - 3,\\n+ });\\n+ }\\n+ }, []);\\n+\\n+ const handleContentUpdate = useCallback(\\n+ (contentNode) => {\\n+ if (!contentNode) return;\\n+\\n+ contentNodeRef.current = contentNode;\\n+ const now = Date.now();\\n+\\n+ // Clear any existing loader timeout\\n+ if (loaderTimeoutRef.current) {\\n+ clearTimeout(loaderTimeoutRef.current);\\n+ loaderTimeoutRef.current = null;\\n+ }\\n+\\n+ // If we're actively streaming, hide the loader and schedule showing it\\n+ if (now - lastUpdateRef.current < 200) {\\n+ setShowLoader(false);\\n+ }\\n+\\n+ // Always schedule the loader to appear after 200ms\\n+ loaderTimeoutRef.current = setTimeout(() => {\\n+ if (contentNodeRef.current) {\\n+ setShowLoader(true);\\n+ calculateLoaderPosition(contentNodeRef.current);\\n+ }\\n+ }, 200);\\n+\\n+ lastUpdateRef.current = now;\\n+ },\\n+ [calculateLoaderPosition],\\n+ );\\n+\\n+ // Update loader position when content changes\\n+ useEffect(() => {\\n+ if (showLoader && contentNodeRef.current) {\\n+ calculateLoaderPosition(contentNodeRef.current);\\n+ }\\n+ }, [content, showLoader, calculateLoaderPosition]);\\n+\\n+ // Cleanup timeout\\n+ useEffect(() => {\\n+ return () => {\\n+ if (loaderTimeoutRef.current) {\\n+ clearTimeout(loaderTimeoutRef.current);\\n+ }\\n+ };\\n+ }, []);\\n+\\n+ let rowHeight = \\\"h-12 [.docked_&]:h-10\\\";\\n+ let basis =\\n+ \\\"min-w-[3rem] basis-12 [.docked_&]:basis-10 [.docked_&]:min-w-[2.5rem]\\\";\\n+ let buttonWidthClass = \\\"w-12 [.docked_&]:w-10\\\";\\n+ const botName =\\n+ bot === \\\"code\\\"\\n+ ? config?.code?.botName\\n+ : aiName || config?.chat?.botName;\\n+\\n+ const avatar = useMemo(() => {\\n+ return bot === \\\"code\\\" ? (\\n+ \\n+ ) : (\\n+ \\n+ );\\n+ }, [bot, getLogo, language, basis, buttonWidthClass, rowHeight]);\\n+\\n+ return (\\n+
\\n+
{avatar}
\\n+ \\n+
\\n+
\\n+ {t(botName)}\\n+
\\n+
\\n+ \\n+ {showLoader && (\\n+
\\n+ \\n+ \\n+
\\n+
\\n+ )}\\n+
\\n+
\\n+
\\n+
\\n+ );\\n+});\\n+\\n+StreamingMessage.displayName = \\\"StreamingMessage\\\";\\n+export default StreamingMessage;\\ndiff --git a/src/components/code/CodeBlock.js b/src/components/code/CodeBlock.js\\nindex 767bd9e..549e891 100644\\n--- a/src/components/code/CodeBlock.js\\n+++ b/src/components/code/CodeBlock.js\\n@@ -4,7 +4,7 @@ import CopyButton from \\\"../CopyButton\\\";\\n \\n const CodeBlock = ({ code, language }) => {\\n let highlightedCode = \\\"\\\";\\n- const trimmedCode = code.trim();\\n+ const trimmedCode = code?.trim() || \\\"\\\";\\n \\n if (language && HighlightJS.getLanguage(language)) {\\n highlightedCode = HighlightJS.highlight(trimmedCode, {\\ndiff --git a/src/components/editor/ProgressUpdate.js b/src/components/editor/ProgressUpdate.js\\nindex 983ee84..86a104b 100644\\n--- a/src/components/editor/ProgressUpdate.js\\n+++ b/src/components/editor/ProgressUpdate.js\\n@@ -43,6 +43,13 @@ const ProgressUpdate = ({\\n \\n const curInfo = data?.requestProgress?.info;\\n \\n+ // Check for error in the requestProgress data\\n+ if (data?.requestProgress?.error) {\\n+ // Handle the error by setting an error message in the UI\\n+ setInfo(`Error: ${data.requestProgress.error}`);\\n+ return;\\n+ }\\n+\\n if (result) {\\n let finalData = result;\\n try {\\ndiff --git a/src/components/images/ChatImage.js b/src/components/images/ChatImage.js\\nnew file mode 100644\\nindex 0000000..0111646\\n--- /dev/null\\n+++ b/src/components/images/ChatImage.js\\n@@ -0,0 +1,111 @@\\n+\\\"use client\\\";\\n+\\n+import React, { useEffect, useRef } from \\\"react\\\";\\n+import {\\n+ getStableImageId,\\n+ tempToPermanentUrlMap,\\n+} from \\\"../../utils/imageUtils.mjs\\\";\\n+\\n+const ChatImage = React.memo(\\n+ function ChatImage({\\n+ node,\\n+ src,\\n+ alt = \\\"\\\",\\n+ className = \\\"max-h-[20%] max-w-[60%] [.docked_&]:max-w-[90%] rounded my-2 shadow-lg dark:shadow-black/30\\\",\\n+ style = {},\\n+ onLoad,\\n+ ...props\\n+ }) {\\n+ // Check if we have a permanent URL for this temporary URL\\n+ const permanentUrl = tempToPermanentUrlMap.get(src);\\n+ const bestSrc = permanentUrl || src;\\n+\\n+ // Get a stable ID that persists even when the URL changes from temp to permanent\\n+ const stableId = getStableImageId(src, node);\\n+\\n+ // Track current src and use a loading ref to prevent flashing\\n+ const [currentSrc, setCurrentSrc] = React.useState(bestSrc);\\n+ const isLoadingNewSrc = useRef(false);\\n+ const previousSrcRef = useRef(bestSrc);\\n+\\n+ // Handle URL changes by preloading the new image\\n+ useEffect(() => {\\n+ // If the best source URL has changed\\n+ if (bestSrc !== previousSrcRef.current) {\\n+ // If we already have the image preloaded (from processImageUrls)\\n+ // we can switch immediately\\n+ if (tempToPermanentUrlMap.has(previousSrcRef.current)) {\\n+ setCurrentSrc(bestSrc);\\n+ } else {\\n+ // Otherwise, preload the new image before switching\\n+ isLoadingNewSrc.current = true; // Track that we're loading a new image\\n+\\n+ const img = new Image();\\n+ img.onload = () => {\\n+ // Only switch once the new image is loaded\\n+ setCurrentSrc(bestSrc);\\n+ isLoadingNewSrc.current = false;\\n+ };\\n+ img.onerror = () => {\\n+ // If there's an error, still swap to avoid getting stuck\\n+ setCurrentSrc(bestSrc);\\n+ isLoadingNewSrc.current = false;\\n+ };\\n+ img.src = bestSrc;\\n+ }\\n+ }\\n+\\n+ // Update the ref for the next comparison\\n+ previousSrcRef.current = bestSrc;\\n+ }, [bestSrc]);\\n+\\n+ // Also handle direct src prop changes (fallback)\\n+ useEffect(() => {\\n+ if (src !== previousSrcRef.current && !permanentUrl) {\\n+ // For direct src changes, also preload\\n+ isLoadingNewSrc.current = true;\\n+\\n+ const img = new Image();\\n+ img.onload = () => {\\n+ setCurrentSrc(src);\\n+ isLoadingNewSrc.current = false;\\n+ };\\n+ img.onerror = () => {\\n+ setCurrentSrc(src);\\n+ isLoadingNewSrc.current = false;\\n+ };\\n+ img.src = src;\\n+\\n+ previousSrcRef.current = src;\\n+ }\\n+ }, [src, permanentUrl]);\\n+\\n+ return (\\n+ \\n+ );\\n+ },\\n+ (prevProps, nextProps) => {\\n+ // Only re-render if src, alt, or node changes\\n+ // Note: The component will still handle src changes internally via useEffect\\n+ return (\\n+ prevProps.src === nextProps.src &&\\n+ prevProps.alt === nextProps.alt &&\\n+ prevProps.node === nextProps.node\\n+ );\\n+ },\\n+);\\n+\\n+export default ChatImage;\\ndiff --git a/src/components/images/ImagesPage.js b/src/components/images/ImagesPage.js\\nindex 90572b7..9c3871b 100644\\n--- a/src/components/images/ImagesPage.js\\n+++ b/src/components/images/ImagesPage.js\\n@@ -14,6 +14,7 @@ import {\\n TooltipContent,\\n TooltipProvider,\\n } from \\\"../../../@/components/ui/tooltip\\\";\\n+import ChatImage from \\\"./ChatImage\\\";\\n \\n function ImagesPage() {\\n const [prompt, setPrompt] = useState(\\\"\\\");\\n@@ -267,7 +268,7 @@ function ImagesPage() {\\n \\n \\n handleBulkAction(\\\"download\\\")}\\n >\\n@@ -282,7 +283,7 @@ function ImagesPage() {\\n \\n \\n handleBulkAction(\\\"delete\\\")}\\n >\\n@@ -299,7 +300,7 @@ function ImagesPage() {\\n \\n \\n {\\n if (\\n window.confirm(\\n@@ -417,11 +418,12 @@ function ImageTile({\\n \\n
\\n {!expired && url && !loadError ? (\\n- setLoadError(true)}\\n onLoad={() => setLoadError(false)}\\n+ className=\\\"w-full h-full object-cover object-center\\\"\\n />\\n ) : (\\n
\\n@@ -443,7 +445,7 @@ function ImageTile({\\n \\n
\\n {\\n e.stopPropagation();\\n@@ -453,7 +455,7 @@ function ImageTile({\\n \\n \\n {\\n if (\\n@@ -566,8 +568,8 @@ function ImageModal({ show, image, onHide }) {\\n \\n
\\n
\\n- \\ndiff --git a/src/components/notifications/NotificationButton.js b/src/components/notifications/NotificationButton.js\\nnew file mode 100644\\nindex 0000000..da62573\\n--- /dev/null\\n+++ b/src/components/notifications/NotificationButton.js\\n@@ -0,0 +1,330 @@\\n+import {\\n+ AlertDialog,\\n+ AlertDialogAction,\\n+ AlertDialogCancel,\\n+ AlertDialogContent,\\n+ AlertDialogDescription,\\n+ AlertDialogFooter,\\n+ AlertDialogHeader,\\n+ AlertDialogTitle,\\n+} from \\\"@/components/ui/alert-dialog\\\";\\n+import {\\n+ Popover,\\n+ PopoverContent,\\n+ PopoverTrigger,\\n+} from \\\"@/components/ui/popover\\\";\\n+import { BellIcon } from \\\"@heroicons/react/24/outline\\\";\\n+import { BanIcon, Check, EyeOff, XIcon } from \\\"lucide-react\\\";\\n+import { useRouter } from \\\"next/navigation\\\";\\n+import { useCallback, useContext, useState } from \\\"react\\\";\\n+import { useTranslation } from \\\"react-i18next\\\";\\n+import TimeAgo from \\\"react-time-ago\\\";\\n+import stringcase from \\\"stringcase\\\";\\n+import Loader from \\\"../../../app/components/loader\\\";\\n+import {\\n+ useCancelRequest,\\n+ useDismissNotification,\\n+ useNotifications,\\n+} from \\\"../../../app/queries/notifications\\\";\\n+import { LanguageContext } from \\\"../../contexts/LanguageProvider\\\";\\n+import { useNotificationsContext } from \\\"../../contexts/NotificationContext\\\";\\n+\\n+export const NotificationDisplayType = {\\n+ \\\"video-translate\\\": \\\"Video translation\\\",\\n+};\\n+\\n+const getLocaleShortName = (locale, usersLanguage) => {\\n+ try {\\n+ return new Intl.DisplayNames([usersLanguage], { type: \\\"language\\\" }).of(\\n+ locale?.split(\\\"-\\\")[0],\\n+ );\\n+ } catch (e) {\\n+ return locale; // fallback to code if translation fails\\n+ }\\n+};\\n+\\n+// Add status icons/colors mapping\\n+export const StatusIndicator = ({ status }) => {\\n+ if (status === \\\"failed\\\") {\\n+ return ;\\n+ } else if (status === \\\"completed\\\") {\\n+ return ;\\n+ } else if (status === \\\"in_progress\\\") {\\n+ return ;\\n+ } else if (status === \\\"cancelled\\\") {\\n+ return ;\\n+ } else {\\n+ return \\\"Unknown\\\";\\n+ }\\n+};\\n+\\n+export const getStatusColorClass = (status) => {\\n+ switch (status) {\\n+ case \\\"completed\\\":\\n+ return \\\"text-green-500\\\";\\n+ case \\\"failed\\\":\\n+ case \\\"cancelled\\\":\\n+ return \\\"text-red-500\\\";\\n+ case \\\"in_progress\\\":\\n+ return \\\"text-sky-500\\\";\\n+ default:\\n+ return \\\"text-gray-500\\\";\\n+ }\\n+};\\n+\\n+export default function NotificationButton() {\\n+ const { t } = useTranslation();\\n+ const { isNotificationOpen, setIsNotificationOpen } =\\n+ useNotificationsContext();\\n+ const { data: notificationsData } = useNotifications();\\n+ const notifications = notificationsData?.requests || []; // Extract requests from the response\\n+ const dismissNotification = useDismissNotification();\\n+ const [dismissingIds, setDismissingIds] = useState(new Set());\\n+ const [cancelRequestId, setCancelRequestId] = useState(null);\\n+ const { language } = useContext(LanguageContext);\\n+ const router = useRouter();\\n+ const cancelRequest = useCancelRequest();\\n+\\n+ const handleDismiss = (requestId) => {\\n+ setDismissingIds((prev) => new Set([...prev, requestId]));\\n+ setTimeout(() => {\\n+ dismissNotification.mutate(requestId);\\n+ setDismissingIds((prev) => {\\n+ const next = new Set(prev);\\n+ next.delete(requestId);\\n+ return next;\\n+ });\\n+ }, 300);\\n+ };\\n+\\n+ const handleCancelRequest = (requestId) => {\\n+ setCancelRequestId(requestId);\\n+ };\\n+\\n+ const confirmCancel = useCallback(async () => {\\n+ if (cancelRequestId) {\\n+ await cancelRequest.mutate(cancelRequestId);\\n+ setCancelRequestId(null);\\n+ }\\n+ }, [cancelRequestId, cancelRequest]);\\n+\\n+ return (\\n+ <>\\n+ \\n+ \\n+ \\n+ {notifications.filter((n) => n.status === \\\"in_progress\\\")\\n+ .length > 0 && (\\n+ <>\\n+ \\n+ \\n+ {\\n+ notifications.filter(\\n+ (n) => n.status === \\\"in_progress\\\",\\n+ ).length\\n+ }\\n+ \\n+ \\n+ )}\\n+ \\n+ \\n+
\\n+

{t(\\\"Notifications\\\")}

\\n+
\\n+ {notifications.length === 0 ? (\\n+

\\n+ {t(\\\"No recent or active notifications\\\")}\\n+

\\n+ ) : (\\n+
\\n+ {notifications.map((notification) => (\\n+ \\n+
\\n+
\\n+ \\n+
\\n+
\\n+ \\n+ {t(\\n+ NotificationDisplayType[\\n+ notification\\n+ .type\\n+ ],\\n+ )}\\n+ \\n+ {notification.metadata && (\\n+ \\n+ {t(\\n+ \\\"{{from}} to {{to}}\\\",\\n+ {\\n+ from: getLocaleShortName(\\n+ notification\\n+ .metadata\\n+ .sourceLocale,\\n+ language,\\n+ ),\\n+ to: getLocaleShortName(\\n+ notification\\n+ .metadata\\n+ .targetLocale,\\n+ language,\\n+ ),\\n+ },\\n+ )}\\n+
\\n+ )}\\n+ {notification.status ===\\n+ \\\"in_progress\\\" && (\\n+ \\n+ {\\n+ notification.statusText\\n+ }\\n+ \\n+ )}\\n+ {notification.status ===\\n+ \\\"failed\\\" && (\\n+ \\n+ {notification.statusText ||\\n+ t(\\n+ \\\"Request failed\\\",\\n+ )}\\n+ \\n+ )}\\n+ \\n+ {t(\\n+ stringcase.sentencecase(\\n+ notification.status,\\n+ ),\\n+ )}\\n+ \\n+ {notification.status ===\\n+ \\\"in_progress\\\" && (\\n+
\\n+ \\n+
\\n+ )}\\n+ {notification.createdAt && (\\n+ \\n+ {t(\\\"Created \\\")}{\\\" \\\"}\\n+ \\n+ \\n+ )}\\n+
\\n+
\\n+ {notification.status ===\\n+ \\\"in_progress\\\" && (\\n+ \\n+ handleCancelRequest(\\n+ notification.requestId,\\n+ )\\n+ }\\n+ className=\\\"p-1 hover:bg-gray-100 rounded flex items-start\\\"\\n+ title={t(\\\"Cancel\\\")}\\n+ >\\n+ \\n+ \\n+ )}\\n+ {(notification.status ===\\n+ \\\"completed\\\" ||\\n+ notification.status ===\\n+ \\\"failed\\\" ||\\n+ notification.status ===\\n+ \\\"cancelled\\\") && (\\n+ \\n+ handleDismiss(\\n+ notification.requestId,\\n+ )\\n+ }\\n+ className=\\\"p-1 hover:bg-gray-100 rounded flex items-start\\\"\\n+ title={t(\\\"Hide\\\")}\\n+ >\\n+ \\n+ \\n+ )}\\n+
\\n+
\\n+
\\n+ ))}\\n+
\\n+ )}\\n+
\\n+
\\n+ {\\n+ router.push(\\\"/notifications\\\");\\n+ setIsNotificationOpen(false);\\n+ }}\\n+ >\\n+ {t(\\\"View history\\\")}\\n+ \\n+
\\n+
\\n+ \\n+ \\n+\\n+ setCancelRequestId(null)}\\n+ >\\n+ \\n+ \\n+ \\n+ {t(\\\"Confirm Cancellation\\\")}\\n+ \\n+ \\n+ {t(\\n+ \\\"Are you sure you want to cancel this request? This action cannot be undone.\\\",\\n+ )}\\n+ \\n+ \\n+ \\n+ {t(\\\"No\\\")}\\n+ \\n+ {t(\\\"Yes, Cancel Request\\\")}\\n+ \\n+ \\n+ \\n+ \\n+ \\n+ );\\n+}\\ndiff --git a/src/components/sandbox/OutputSandbox.js b/src/components/sandbox/OutputSandbox.js\\nnew file mode 100644\\nindex 0000000..e89e30f\\n--- /dev/null\\n+++ b/src/components/sandbox/OutputSandbox.js\\n@@ -0,0 +1,129 @@\\n+import React, { useEffect, useRef, useState } from \\\"react\\\";\\n+\\n+export default function OutputSandbox({ content, height = \\\"300px\\\" }) {\\n+ const iframeRef = useRef(null);\\n+ const [isLoading, setIsLoading] = useState(true);\\n+ const resizeObserverRef = useRef(null);\\n+\\n+ useEffect(() => {\\n+ if (!iframeRef.current) return;\\n+\\n+ const iframe = iframeRef.current;\\n+ const setupFrame = async () => {\\n+ try {\\n+ setIsLoading(true);\\n+\\n+ // Create a base tag to handle relative URLs\\n+ const base = document.createElement(\\\"base\\\");\\n+ base.href = window.location.origin;\\n+\\n+ // Create proper HTML structure\\n+ const html = `\\n+ \\n+ \\n+ \\n+ \\n+ \\n+ \\n+ \\n+ ${content}\\n+ \\n+ `;\\n+\\n+ // Use srcdoc for better security and performance\\n+ iframe.srcdoc = html;\\n+\\n+ // Handle iframe load\\n+ iframe.onload = () => {\\n+ const frameDoc =\\n+ iframe.contentDocument || iframe.contentWindow.document;\\n+\\n+ // Clean up any existing observer\\n+ if (resizeObserverRef.current) {\\n+ resizeObserverRef.current.disconnect();\\n+ }\\n+\\n+ // Setup new resize observer\\n+ const resizeObserver = new ResizeObserver((entries) => {\\n+ for (const entry of entries) {\\n+ const height = Math.max(\\n+ entry.contentRect.height,\\n+ entry.target.scrollHeight,\\n+ );\\n+ iframe.style.height = `${height}px`;\\n+ }\\n+ });\\n+\\n+ // Ensure body exists before observing\\n+ if (frameDoc.body) {\\n+ resizeObserver.observe(frameDoc.body);\\n+ resizeObserverRef.current = resizeObserver;\\n+ }\\n+\\n+ // Setup message handling for iframe->parent communication\\n+ iframe.contentWindow.addEventListener(\\n+ \\\"message\\\",\\n+ (event) => {\\n+ if (event.origin !== window.location.origin) return;\\n+ // Handle messages from the iframe\\n+ console.log(\\\"Message from sandbox:\\\", event.data);\\n+ },\\n+ );\\n+\\n+ setIsLoading(false);\\n+ };\\n+\\n+ // Handle errors\\n+ iframe.onerror = (error) => {\\n+ console.error(\\\"Sandbox iframe error:\\\", error);\\n+ setIsLoading(false);\\n+ };\\n+ } catch (error) {\\n+ console.error(\\\"Error setting up sandbox:\\\", error);\\n+ setIsLoading(false);\\n+ }\\n+ };\\n+\\n+ setupFrame();\\n+\\n+ // Cleanup\\n+ return () => {\\n+ if (resizeObserverRef.current) {\\n+ resizeObserverRef.current.disconnect();\\n+ }\\n+ if (iframe.contentWindow) {\\n+ iframe.contentWindow.removeEventListener(\\\"message\\\", () => {});\\n+ }\\n+ };\\n+ }, [content]);\\n+\\n+ return (\\n+
\\n+ {isLoading && (\\n+
\\n+
Loading...
\\n+
\\n+ )}\\n+ \\n+
\\n+ );\\n+}\\ndiff --git a/src/components/transcribe/AddTrackOptions.js b/src/components/transcribe/AddTrackOptions.js\\nindex 588ebc6..c981601 100644\\n--- a/src/components/transcribe/AddTrackOptions.js\\n+++ b/src/components/transcribe/AddTrackOptions.js\\n@@ -7,20 +7,17 @@ import {\\n UploadIcon,\\n VideoIcon,\\n } from \\\"lucide-react\\\";\\n-import { useCallback, useContext, useRef, useState } from \\\"react\\\";\\n+import { useCallback, useContext, useEffect, useRef, useState } from \\\"react\\\";\\n import { useTranslation } from \\\"react-i18next\\\";\\n import { FaVideo } from \\\"react-icons/fa\\\";\\n-import { AuthContext, ServerContext } from \\\"../../App\\\";\\n+import { AuthContext } from \\\"../../App\\\";\\n+import { LanguageContext } from \\\"../../contexts/LanguageProvider\\\";\\n import { useProgress } from \\\"../../contexts/ProgressContext\\\";\\n import { QUERIES } from \\\"../../graphql\\\";\\n+import { isYoutubeUrl } from \\\"../../utils/urlUtils\\\";\\n import LoadingButton from \\\"../editor/LoadingButton\\\";\\n import TranslationOptions from \\\"./TranslationOptions\\\";\\n-import {\\n- convertSrtToVtt,\\n- detectSubtitleFormat,\\n- normalizeVtt,\\n-} from \\\"./transcribe.utils\\\";\\n-import { LanguageContext } from \\\"../../contexts/LanguageProvider\\\";\\n+import { parse, build } from \\\"@aj-archipelago/subvibe\\\";\\n \\n export function AddTrackOptions({\\n url,\\n@@ -52,7 +49,9 @@ export function AddTrackOptions({\\n \\n return (\\n \\n- \\n+ \\n {options.includes(\\\"transcribe\\\") && (\\n \\n \\n@@ -179,13 +178,11 @@ function SubtitleUpload({ onAdd }) {\\n reader.onload = async (e) => {\\n let text = e.target.result;\\n \\n+ const parsed = parse(text);\\n+\\n if (fileExtension === \\\"srt\\\") {\\n- console.log(\\n- \\\"fileExtension\\\",\\n- fileExtension,\\n- convertSrtToVtt(text),\\n- );\\n- text = convertSrtToVtt(text);\\n+ // Convert SRT to VTT format\\n+ text = build(parsed.cues, \\\"vtt\\\");\\n }\\n \\n onAdd({\\n@@ -238,17 +235,20 @@ function ClipboardPaste({ onAdd }) {\\n if (!text.trim()) return;\\n \\n // Detect if the pasted text is in a subtitle format\\n- const format = detectSubtitleFormat(text);\\n+ const parsed = parse(text);\\n+ const format = parsed?.type;\\n+ const cues = parsed?.cues;\\n+\\n let processedText = text;\\n let outputFormat = \\\"\\\";\\n let name = t(\\\"Pasted Transcript\\\");\\n \\n if (format === \\\"srt\\\") {\\n- processedText = convertSrtToVtt(text);\\n+ processedText = build(cues, \\\"vtt\\\");\\n outputFormat = \\\"vtt\\\";\\n name = t(\\\"Pasted Subtitles\\\");\\n } else if (format === \\\"vtt\\\") {\\n- processedText = normalizeVtt(text);\\n+ processedText = build(cues, \\\"vtt\\\");\\n outputFormat = \\\"vtt\\\";\\n name = t(\\\"Pasted Subtitles\\\");\\n }\\n@@ -283,6 +283,19 @@ function ClipboardPaste({ onAdd }) {\\n );\\n }\\n \\n+export const getTranscribeQuery = (modelOption) => {\\n+ switch (modelOption?.toLowerCase()) {\\n+ case \\\"neuralSpace\\\":\\n+ return QUERIES.TRANSCRIBE_NEURALSPACE;\\n+ case \\\"gemini\\\":\\n+ return QUERIES.TRANSCRIBE_GEMINI;\\n+ case \\\"whisper\\\":\\n+ return QUERIES.TRANSCRIBE;\\n+ default:\\n+ return QUERIES.TRANSCRIBE;\\n+ }\\n+};\\n+\\n export default function TranscribeVideo({\\n url,\\n onAdd,\\n@@ -291,12 +304,14 @@ export default function TranscribeVideo({\\n onClose,\\n }) {\\n const { t } = useTranslation();\\n- const { neuralspaceEnabled } = useContext(ServerContext);\\n+ const isYouTubeVideo = url ? isYoutubeUrl(url) : false;\\n \\n- // Move state variables from Video.js\\n const [language, setLanguage] = useState(\\\"\\\");\\n- const [selectedModelOption, setSelectedModelOption] = useState(\\\"Whisper\\\");\\n+ const [selectedModelOption, setSelectedModelOption] = useState(\\n+ isYouTubeVideo ? \\\"Gemini\\\" : \\\"Whisper\\\",\\n+ );\\n const [transcriptionOption, setTranscriptionOption] = useState(null);\\n+ // eslint-disable-next-line no-unused-vars\\n const [requestId, setRequestId] = useState(null);\\n const [loading, setLoading] = useState(false);\\n const [currentOperation, setCurrentOperation] = useState(\\\"\\\");\\n@@ -313,99 +328,99 @@ export default function TranscribeVideo({\\n highlightWords,\\n } = transcriptionOption ?? {};\\n \\n- // Move handleSubmit from Video.js\\n- const handleSubmit = useCallback(\\n- async () => {\\n- if (!url || loading) return;\\n-\\n- setCurrentOperation(t(\\\"Transcribing\\\"));\\n- try {\\n- setLoading(true);\\n-\\n- const _query =\\n- selectedModelOption === \\\"NeuralSpace\\\"\\n- ? QUERIES.TRANSCRIBE_NEURALSPACE\\n- : QUERIES.TRANSCRIBE;\\n-\\n- const { data } = await apolloClient.query({\\n- query: _query,\\n- variables: {\\n- file: url,\\n- language,\\n- wordTimestamped,\\n- responseFormat:\\n- responseFormat !== \\\"formatted\\\"\\n- ? responseFormat\\n- : null,\\n- maxLineCount,\\n- maxLineWidth,\\n- maxWordsPerLine,\\n- highlightWords,\\n- async: true,\\n- },\\n- fetchPolicy: \\\"network-only\\\",\\n- });\\n-\\n- const dataResult =\\n- data?.transcribe?.result ||\\n- data?.transcribe_neuralspace?.result;\\n-\\n- if (dataResult) {\\n- setRequestId(dataResult);\\n- addProgressToast(\\n- dataResult,\\n- t(\\\"Transcribing\\\") + \\\"...\\\",\\n- async (finalData) => {\\n- if (responseFormat === \\\"formatted\\\") {\\n- const response = await apolloClient.query({\\n- query: QUERIES.FORMAT_PARAGRAPH_TURBO,\\n- variables: {\\n- text: finalData,\\n- async: false,\\n- },\\n- });\\n-\\n- finalData =\\n- response.data?.format_paragraph_turbo\\n- ?.result;\\n- }\\n- setLoading(false);\\n- onAdd({\\n- text: finalData,\\n- format: responseFormat,\\n- name:\\n- responseFormat === \\\"vtt\\\"\\n- ? t(\\\"Subtitles\\\")\\n- : t(\\\"Transcript\\\"),\\n+ // Update model if URL changes and it's a YouTube video\\n+ useEffect(() => {\\n+ if (isYouTubeVideo) {\\n+ setSelectedModelOption(\\\"Gemini\\\");\\n+ }\\n+ }, [url, isYouTubeVideo]);\\n+\\n+ const handleSubmit = useCallback(async () => {\\n+ if (!url || loading) return;\\n+\\n+ setCurrentOperation(t(\\\"Transcribing\\\"));\\n+ try {\\n+ setLoading(true);\\n+\\n+ const _query = getTranscribeQuery(selectedModelOption);\\n+\\n+ const { data } = await apolloClient.query({\\n+ query: _query,\\n+ variables: {\\n+ file: url,\\n+ language,\\n+ wordTimestamped,\\n+ responseFormat:\\n+ responseFormat !== \\\"formatted\\\" ? responseFormat : null,\\n+ maxLineCount,\\n+ maxLineWidth,\\n+ maxWordsPerLine,\\n+ highlightWords,\\n+ async: true,\\n+ },\\n+ fetchPolicy: \\\"network-only\\\",\\n+ });\\n+\\n+ const dataResult =\\n+ data?.transcribe?.result ||\\n+ data?.transcribe_neuralspace?.result ||\\n+ data?.transcribe_gemini?.result;\\n+\\n+ if (dataResult) {\\n+ setRequestId(dataResult);\\n+ addProgressToast(\\n+ dataResult,\\n+ t(\\\"Transcribing\\\") + \\\"...\\\",\\n+ async (finalData) => {\\n+ if (responseFormat === \\\"formatted\\\") {\\n+ const response = await apolloClient.query({\\n+ query: QUERIES.FORMAT_PARAGRAPH_TURBO,\\n+ variables: {\\n+ text: finalData,\\n+ async: false,\\n+ },\\n });\\n- setRequestId(null);\\n- },\\n- );\\n- onClose?.();\\n- }\\n- } catch (e) {\\n- console.error(\\\"Transcription error:\\\", e);\\n- setError(e);\\n- setLoading(false);\\n+\\n+ finalData =\\n+ response.data?.format_paragraph_turbo?.result;\\n+ }\\n+ setLoading(false);\\n+ onAdd({\\n+ text: finalData,\\n+ format: responseFormat,\\n+ name:\\n+ responseFormat === \\\"vtt\\\"\\n+ ? t(\\\"Subtitles\\\")\\n+ : t(\\\"Transcript\\\"),\\n+ });\\n+ setRequestId(null);\\n+ },\\n+ );\\n+ onClose?.();\\n }\\n- },\\n+ } catch (e) {\\n+ console.error(\\\"Transcription error:\\\", e);\\n+ setError(e);\\n+ setLoading(false);\\n+ }\\n // eslint-disable-next-line react-hooks/exhaustive-deps\\n- [\\n- url,\\n- language,\\n- wordTimestamped,\\n- responseFormat,\\n- maxLineCount,\\n- maxLineWidth,\\n- maxWordsPerLine,\\n- highlightWords,\\n- loading,\\n- async,\\n- addProgressToast,\\n- t,\\n- onClose,\\n- ],\\n- );\\n+ }, [\\n+ url,\\n+ language,\\n+ wordTimestamped,\\n+ responseFormat,\\n+ maxLineCount,\\n+ maxLineWidth,\\n+ maxWordsPerLine,\\n+ highlightWords,\\n+ loading,\\n+ async,\\n+ addProgressToast,\\n+ t,\\n+ onClose,\\n+ apolloClient,\\n+ selectedModelOption,\\n+ ]);\\n \\n // Add logging for select changes\\n const handleFormatChange = (e) => {\\n@@ -456,16 +471,20 @@ export default function TranscribeVideo({\\n \\n return (\\n <>\\n- {neuralspaceEnabled && (\\n+ {/*
\\n \\n- \\n+ \\n \\n \\n- )}\\n+
*/}\\n \\n
\\n
\\n@@ -482,7 +501,7 @@ export default function TranscribeVideo({\\n {responseFormat === \\\"vtt\\\" && (\\n
\\n
\\n- Transcription type\\n+ {t(\\\"Transcription type\\\")}\\n
\\n \\n
\\n )}\\n@@ -508,6 +528,12 @@ export default function TranscribeVideo({\\n
\\n
\\n \\n+ {error && (\\n+
\\n+ {t(\\\"Error\\\")}: {error.message}\\n+
\\n+ )}\\n+\\n
\\n setSelectedModelOption(e.target.value)}\\n- >\\n- \\n- \\n- \\n- );\\n-}\\n-\\n function TranscriptionTypeSelector({\\n loading,\\n wordTimestamped,\\n maxLineWidth,\\n handleTranscriptionTypeChange,\\n+ selectedModelOption,\\n }) {\\n const { t } = useTranslation();\\n+ const isGemini = selectedModelOption?.toLowerCase() === \\\"gemini\\\";\\n \\n return (\\n \\n \\n- \\n+ {!isGemini && }\\n \\n \\n \\ndiff --git a/src/components/transcribe/AzureVideoTranslate.js b/src/components/transcribe/AzureVideoTranslate.js\\nindex a16b175..591d0f3 100644\\n--- a/src/components/transcribe/AzureVideoTranslate.js\\n+++ b/src/components/transcribe/AzureVideoTranslate.js\\n@@ -1,65 +1,53 @@\\n-import { useApolloClient } from \\\"@apollo/client\\\";\\n+import axios from \\\"axios\\\";\\n import { LanguagesIcon } from \\\"lucide-react\\\";\\n import { useContext, useState } from \\\"react\\\";\\n-import { useProgress } from \\\"../../contexts/ProgressContext\\\";\\n-import { AZURE_VIDEO_TRANSLATE } from \\\"../../graphql\\\";\\n-import { LOCALES } from \\\"../../utils/constants\\\";\\n import { useTranslation } from \\\"react-i18next\\\";\\n+import { toast } from \\\"react-toastify\\\";\\n import { LanguageContext } from \\\"../../contexts/LanguageProvider\\\";\\n+import { useNotificationsContext } from \\\"../../contexts/NotificationContext\\\";\\n+import { LOCALES } from \\\"../../utils/constants\\\";\\n+import { useNotifications } from \\\"../../../app/queries/notifications\\\";\\n \\n-export default function AzureVideoTranslate({ url, onQueued, onComplete }) {\\n- const apolloClient = useApolloClient();\\n+export default function AzureVideoTranslate({ url, onQueued }) {\\n const [sourceLocale, setSourceLocale] = useState(\\\"en-US\\\");\\n const [targetLocale, setTargetLocale] = useState(\\\"ar-QA\\\");\\n- const { addProgressToast } = useProgress();\\n const { t } = useTranslation();\\n const { language } = useContext(LanguageContext);\\n+ const { openNotifications } = useNotificationsContext();\\n+ const { invalidateNotifications } = useNotifications();\\n \\n- async function setFinalDataPre(data) {\\n- if (data === \\\"[DONE]\\\") {\\n- console.log(\\\"[DONE] received\\\");\\n- throw new Error(\\n- \\\"There was an unknown error returned by the translation service. Please try again.\\\",\\n- );\\n- }\\n-\\n- // Parse the data - handle both single and double JSON stringified cases\\n+ const handleSubmit = async () => {\\n try {\\n- data = JSON.parse(data);\\n- // Check if it's still a string and potentially another JSON\\n- if (typeof data === \\\"string\\\") {\\n- data = JSON.parse(data);\\n- }\\n- } catch (e) {\\n- console.error(\\\"Error parsing JSON response:\\\", e);\\n- throw new Error(\\\"Failed to parse translation service response\\\");\\n- }\\n+ const { data } = await axios.post(\\\"/api/azure-video-translate\\\", {\\n+ sourceLocale,\\n+ targetLocale,\\n+ targetLocaleLabel: new Intl.DisplayNames([language], {\\n+ type: \\\"language\\\",\\n+ }).of(targetLocale),\\n+ url,\\n+ });\\n \\n- try {\\n- const defaultSubtitlesUrl = data.outputVideoSubtitleWebVttFileUrl;\\n- const targetVideoUrl =\\n- data.targetLocales[targetLocale].outputVideoFileUrl;\\n- const targetSubtitlesUrl =\\n- data.targetLocales[targetLocale]\\n- .outputVideoSubtitleWebVttFileUrl;\\n+ const requestId = data;\\n \\n- onComplete?.(targetLocale, targetVideoUrl, {\\n- original: defaultSubtitlesUrl,\\n- translated: targetSubtitlesUrl,\\n- });\\n- } catch (e) {\\n- console.error(e);\\n- throw e;\\n+ // Invalidate notifications to trigger a refetch\\n+ invalidateNotifications();\\n+ // Open notifications panel\\n+ openNotifications();\\n+\\n+ onQueued?.(requestId);\\n+ } catch (error) {\\n+ console.error(\\\"Error translating video:\\\", error);\\n+ toast.error(\\\"Error queuing video translation\\\");\\n }\\n- }\\n+ };\\n \\n return (\\n <>\\n
\\n
\\n-
\\n-