Compare commits

...

58 Commits

Author SHA1 Message Date
ItzCrazyKns
60dd7a8108 feat(ui): fix theming issues 2025-12-24 17:24:07 +05:30
ItzCrazyKns
f5e054f6ea feat(chat): fix hidden input 2025-12-24 15:48:16 +05:30
ItzCrazyKns
452180356d feat(library): enhance ui & ux 2025-12-24 15:47:56 +05:30
ItzCrazyKns
0a9641a110 feat(providers): add anthropic 2025-12-24 15:24:06 +05:30
ItzCrazyKns
a2f2e17bbb feat(providers): add lemonade 2025-12-24 14:12:22 +05:30
ItzCrazyKns
e1afcbb787 feat(package): add google genai & bump transformers 2025-12-24 13:56:43 +05:30
ItzCrazyKns
fe2c1b8210 feat(providers): update index map 2025-12-24 13:56:24 +05:30
ItzCrazyKns
d40fcd57d9 feat(ollama): add nemotron to thinking list 2025-12-24 13:56:11 +05:30
ItzCrazyKns
86a43086cc feat(providers): add transformers 2025-12-24 13:55:56 +05:30
ItzCrazyKns
9ce17edd4a feat(providers): add groq 2025-12-24 13:55:42 +05:30
ItzCrazyKns
c4349f3d5c feat(providers): add gemini 2025-12-24 13:55:32 +05:30
ItzCrazyKns
d4c276ab93 Update types.ts 2025-12-24 13:55:12 +05:30
ItzCrazyKns
6ae885e0ed feat(steps): display after loading animation 2025-12-24 13:55:07 +05:30
ItzCrazyKns
dc74e7174f feat(researcher): rename 0_reasoning to __reasoning_preamble to comply with provider guidelines 2025-12-24 13:54:49 +05:30
ItzCrazyKns
53697bb42e feat(classifier-prompt): add calculation widget 2025-12-24 13:53:35 +05:30
ItzCrazyKns
eca66f0b5f feat(writer): add system instructions, send response block on response 2025-12-24 13:53:09 +05:30
ItzCrazyKns
cf95ea0af7 feat(app): lint & beautify 2025-12-23 18:54:01 +05:30
ItzCrazyKns
24c32ed881 feat(app): enhance attach transition 2025-12-23 18:53:40 +05:30
ItzCrazyKns
b47f522bf2 feat(app): update guide for run command 2025-12-23 18:40:30 +05:30
ItzCrazyKns
ea18c13326 feat(app): remove uploads 2025-12-23 18:38:25 +05:30
ItzCrazyKns
b706434bac feat(chat-window): display only when ready 2025-12-23 17:56:15 +05:30
ItzCrazyKns
2c65bd916b feat(chat-hook): set ready before reconnecting 2025-12-23 17:29:14 +05:30
ItzCrazyKns
c3b74a3fd0 feat(assistant-steps): only open last comp 2025-12-23 17:17:56 +05:30
ItzCrazyKns
5f04034650 feat(chat-hook): handle reconnect 2025-12-23 17:17:19 +05:30
ItzCrazyKns
5847379db0 Update types.ts 2025-12-23 17:15:46 +05:30
ItzCrazyKns
8520ea6fe5 feat(researcher): emit sources as block 2025-12-23 17:15:42 +05:30
ItzCrazyKns
a6d4f47130 feat(search-agent): save history 2025-12-23 17:15:32 +05:30
ItzCrazyKns
f278eb8bf1 feat(routes): add reconnect route 2025-12-23 17:15:02 +05:30
ItzCrazyKns
0e176e0b78 feat(chat-route): add history saving, disconnect on abort, use subscribe method 2025-12-23 17:14:02 +05:30
ItzCrazyKns
8ba64be446 feat(session): fix sessions getting disregarded due to reload 2025-12-23 17:12:56 +05:30
ItzCrazyKns
216332fb20 feat(session): add subscribe method, getAllBlocks 2025-12-23 17:12:15 +05:30
ItzCrazyKns
68a9e048ac feat(schema): change focusMode to sources 2025-12-23 17:11:38 +05:30
ItzCrazyKns
13d6bcf113 Update Optimization.tsx 2025-12-22 17:58:30 +05:30
ItzCrazyKns
94a24d4058 feat(message-input): add overflow to prevent blocked popovers 2025-12-21 19:56:43 +05:30
ItzCrazyKns
300cfa35c7 Update Optimization.tsx 2025-12-19 16:45:46 +05:30
ItzCrazyKns
85273493a0 feat(copy): fix type mismatch 2025-12-19 16:35:13 +05:30
ItzCrazyKns
6e2345bd2d feat(message-box): update markdown2jsx overrides to render codeblock 2025-12-19 16:27:55 +05:30
ItzCrazyKns
fdee29c93e feat(renderers): add code block 2025-12-19 16:26:51 +05:30
ItzCrazyKns
21cb0f5fd9 feat(app): add syntax highlighter 2025-12-19 16:26:38 +05:30
ItzCrazyKns
a82b605c70 feat(citation): move to message renderer 2025-12-19 16:26:13 +05:30
ItzCrazyKns
64683e3dec feat(assistant-steps): improve style 2025-12-19 16:25:56 +05:30
ItzCrazyKns
604774ef6e feat(social-search): add social search 2025-12-18 13:56:39 +05:30
ItzCrazyKns
ac183a90e8 feat(academic-search): add academic search 2025-12-18 13:56:26 +05:30
ItzCrazyKns
5511a276d4 Update Sources.tsx 2025-12-18 13:56:08 +05:30
ItzCrazyKns
473a04b6a5 feat(suggestions): prevent icon from shrinking 2025-12-18 13:56:04 +05:30
ItzCrazyKns
491136822f feat(app): lint & beautify 2025-12-17 21:17:21 +05:30
ItzCrazyKns
6e086953b1 feat(agents): add academic and social search 2025-12-17 21:17:08 +05:30
ItzCrazyKns
1961e4e707 feat(empty-chat-message-input): use sources 2025-12-15 23:49:26 +05:30
ItzCrazyKns
249889f55a feat(actions-registry): add sources, update web search to become active on web 2025-12-15 23:49:11 +05:30
ItzCrazyKns
9b2c229e9c feat(message-input): remove copilot toggle 2025-12-15 23:48:32 +05:30
ItzCrazyKns
4bdb90e150 feat(message-input-actions): update to use motion, improve animations 2025-12-15 23:48:14 +05:30
ItzCrazyKns
f9cc97ffb5 feat(message-input-actions): add sources 2025-12-15 23:47:48 +05:30
ItzCrazyKns
9dd670f46a feat(chat-hook): handle sources 2025-12-15 23:47:38 +05:30
ItzCrazyKns
bd3c5f895a feat(message-input-actions): remove copilot, focus selector 2025-12-15 23:47:21 +05:30
ItzCrazyKns
e6c8a0aa6f Add antialiased class to body element 2025-12-15 23:47:01 +05:30
ItzCrazyKns
b90b92079b feat(chat-route): accept sources 2025-12-15 23:46:46 +05:30
ItzCrazyKns
a3065d58ef feat(package): add motion, react tooltip, phosphor icons 2025-12-15 23:46:11 +05:30
ItzCrazyKns
ca4809f0f2 Update manager.ts 2025-12-14 19:32:09 +05:30
63 changed files with 2978 additions and 900 deletions

View File

@@ -81,7 +81,7 @@ There are mainly 2 ways of installing Perplexica - With Docker, Without Docker.
Perplexica can be easily run using Docker. Simply run the following command: Perplexica can be easily run using Docker. Simply run the following command:
```bash ```bash
docker run -d -p 3000:3000 -v perplexica-data:/home/perplexica/data -v perplexica-uploads:/home/perplexica/uploads --name perplexica itzcrazykns1337/perplexica:latest docker run -d -p 3000:3000 -v perplexica-data:/home/perplexica/data --name perplexica itzcrazykns1337/perplexica:latest
``` ```
This will pull and start the Perplexica container with the bundled SearxNG search engine. Once running, open your browser and navigate to http://localhost:3000. You can then configure your settings (API keys, models, etc.) directly in the setup screen. This will pull and start the Perplexica container with the bundled SearxNG search engine. Once running, open your browser and navigate to http://localhost:3000. You can then configure your settings (API keys, models, etc.) directly in the setup screen.
@@ -93,7 +93,7 @@ This will pull and start the Perplexica container with the bundled SearxNG searc
If you already have SearxNG running, you can use the slim version of Perplexica: If you already have SearxNG running, you can use the slim version of Perplexica:
```bash ```bash
docker run -d -p 3000:3000 -e SEARXNG_API_URL=http://your-searxng-url:8080 -v perplexica-data:/home/perplexica/data -v perplexica-uploads:/home/perplexica/uploads --name perplexica itzcrazykns1337/perplexica:slim-latest docker run -d -p 3000:3000 -e SEARXNG_API_URL=http://your-searxng-url:8080 -v perplexica-data:/home/perplexica/data --name perplexica itzcrazykns1337/perplexica:slim-latest
``` ```
**Important**: Make sure your SearxNG instance has: **Important**: Make sure your SearxNG instance has:
@@ -120,7 +120,7 @@ If you prefer to build from source or need more control:
```bash ```bash
docker build -t perplexica . docker build -t perplexica .
docker run -d -p 3000:3000 -v perplexica-data:/home/perplexica/data -v perplexica-uploads:/home/perplexica/uploads --name perplexica perplexica docker run -d -p 3000:3000 -v perplexica-data:/home/perplexica/data --name perplexica perplexica
``` ```
5. Access Perplexica at http://localhost:3000 and configure your settings in the setup screen. 5. Access Perplexica at http://localhost:3000 and configure your settings in the setup screen.

View File

@@ -1,6 +1,8 @@
services: services:
perplexica: perplexica:
image: itzcrazykns1337/perplexica:latest image: itzcrazykns1337/perplexica:latest
build:
context: .
ports: ports:
- '3000:3000' - '3000:3000'
volumes: volumes:

View File

@@ -10,7 +10,7 @@ Simply pull the latest image and restart your container:
docker pull itzcrazykns1337/perplexica:latest docker pull itzcrazykns1337/perplexica:latest
docker stop perplexica docker stop perplexica
docker rm perplexica docker rm perplexica
docker run -d -p 3000:3000 -v perplexica-data:/home/perplexica/data -v perplexica-uploads:/home/perplexica/uploads --name perplexica itzcrazykns1337/perplexica:latest docker run -d -p 3000:3000 -v perplexica-data:/home/perplexica/data --name perplexica itzcrazykns1337/perplexica:latest
``` ```
For slim version: For slim version:
@@ -19,7 +19,7 @@ For slim version:
docker pull itzcrazykns1337/perplexica:slim-latest docker pull itzcrazykns1337/perplexica:slim-latest
docker stop perplexica docker stop perplexica
docker rm perplexica docker rm perplexica
docker run -d -p 3000:3000 -e SEARXNG_API_URL=http://your-searxng-url:8080 -v perplexica-data:/home/perplexica/data -v perplexica-uploads:/home/perplexica/uploads --name perplexica itzcrazykns1337/perplexica:slim-latest docker run -d -p 3000:3000 -e SEARXNG_API_URL=http://your-searxng-url:8080 -v perplexica-data:/home/perplexica/data --name perplexica itzcrazykns1337/perplexica:slim-latest
``` ```
Once updated, go to http://localhost:3000 and verify the latest changes. Your settings are preserved automatically. Once updated, go to http://localhost:3000 and verify the latest changes. Your settings are preserved automatically.

View File

@@ -11,17 +11,19 @@
"format:write": "prettier . --write" "format:write": "prettier . --write"
}, },
"dependencies": { "dependencies": {
"@google/genai": "^1.34.0",
"@headlessui/react": "^2.2.0", "@headlessui/react": "^2.2.0",
"@headlessui/tailwindcss": "^0.2.2", "@headlessui/tailwindcss": "^0.2.2",
"@huggingface/transformers": "^3.7.5", "@huggingface/transformers": "^3.8.1",
"@icons-pack/react-simple-icons": "^12.3.0", "@icons-pack/react-simple-icons": "^12.3.0",
"@phosphor-icons/react": "^2.1.10",
"@radix-ui/react-tooltip": "^1.2.8",
"@tailwindcss/typography": "^0.5.12", "@tailwindcss/typography": "^0.5.12",
"@types/jspdf": "^2.0.0", "@types/jspdf": "^2.0.0",
"axios": "^1.8.3", "axios": "^1.8.3",
"better-sqlite3": "^11.9.1", "better-sqlite3": "^11.9.1",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"drizzle-orm": "^0.40.1", "drizzle-orm": "^0.40.1",
"framer-motion": "^12.23.25",
"js-tiktoken": "^1.0.21", "js-tiktoken": "^1.0.21",
"jspdf": "^3.0.4", "jspdf": "^3.0.4",
"lightweight-charts": "^5.0.9", "lightweight-charts": "^5.0.9",
@@ -29,6 +31,7 @@
"mammoth": "^1.9.1", "mammoth": "^1.9.1",
"markdown-to-jsx": "^7.7.2", "markdown-to-jsx": "^7.7.2",
"mathjs": "^15.1.0", "mathjs": "^15.1.0",
"motion": "^12.23.26",
"next": "^16.0.7", "next": "^16.0.7",
"next-themes": "^0.3.0", "next-themes": "^0.3.0",
"officeparser": "^5.2.2", "officeparser": "^5.2.2",
@@ -38,6 +41,7 @@
"pdf-parse": "^2.4.5", "pdf-parse": "^2.4.5",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"react-syntax-highlighter": "^16.1.0",
"react-text-to-speech": "^0.14.5", "react-text-to-speech": "^0.14.5",
"react-textarea-autosize": "^8.5.3", "react-textarea-autosize": "^8.5.3",
"rfc6902": "^5.1.2", "rfc6902": "^5.1.2",
@@ -54,6 +58,7 @@
"@types/pdf-parse": "^1.1.4", "@types/pdf-parse": "^1.1.4",
"@types/react": "^18", "@types/react": "^18",
"@types/react-dom": "^18", "@types/react-dom": "^18",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/turndown": "^5.0.6", "@types/turndown": "^5.0.6",
"autoprefixer": "^10.0.1", "autoprefixer": "^10.0.1",
"drizzle-kit": "^0.30.5", "drizzle-kit": "^0.30.5",

View File

@@ -4,6 +4,11 @@ import { ModelWithProvider } from '@/lib/models/types';
import SearchAgent from '@/lib/agents/search'; import SearchAgent from '@/lib/agents/search';
import SessionManager from '@/lib/session'; import SessionManager from '@/lib/session';
import { ChatTurnMessage } from '@/lib/types'; import { ChatTurnMessage } from '@/lib/types';
import { SearchSources } from '@/lib/agents/search/types';
import db from '@/lib/db';
import { eq } from 'drizzle-orm';
import { chats } from '@/lib/db/schema';
import UploadManager from '@/lib/uploads/manager';
export const runtime = 'nodejs'; export const runtime = 'nodejs';
export const dynamic = 'force-dynamic'; export const dynamic = 'force-dynamic';
@@ -31,7 +36,7 @@ const bodySchema = z.object({
optimizationMode: z.enum(['speed', 'balanced', 'quality'], { optimizationMode: z.enum(['speed', 'balanced', 'quality'], {
message: 'Optimization mode must be one of: speed, balanced, quality', message: 'Optimization mode must be one of: speed, balanced, quality',
}), }),
focusMode: z.string().min(1, 'Focus mode is required'), sources: z.array(z.string()).optional().default([]),
history: z history: z
.array(z.tuple([z.string(), z.string()])) .array(z.tuple([z.string(), z.string()]))
.optional() .optional()
@@ -42,7 +47,6 @@ const bodySchema = z.object({
systemInstructions: z.string().nullable().optional().default(''), systemInstructions: z.string().nullable().optional().default(''),
}); });
type Message = z.infer<typeof messageSchema>;
type Body = z.infer<typeof bodySchema>; type Body = z.infer<typeof bodySchema>;
const safeValidateBody = (data: unknown) => { const safeValidateBody = (data: unknown) => {
@@ -64,6 +68,38 @@ const safeValidateBody = (data: unknown) => {
}; };
}; };
const ensureChatExists = async (input: {
id: string;
sources: SearchSources[];
query: string;
fileIds: string[];
}) => {
try {
const exists = await db.query.chats
.findFirst({
where: eq(chats.id, input.id),
})
.execute();
if (!exists) {
await db.insert(chats).values({
id: input.id,
createdAt: new Date().toISOString(),
sources: input.sources,
title: input.query,
files: input.fileIds.map((id) => {
return {
fileId: id,
name: UploadManager.getFile(id)?.name || 'Uploaded File',
};
}),
});
}
} catch (err) {
console.error('Failed to check/save chat:', err);
}
};
export const POST = async (req: Request) => { export const POST = async (req: Request) => {
try { try {
const reqBody = (await req.json()) as Body; const reqBody = (await req.json()) as Body;
@@ -120,96 +156,86 @@ export const POST = async (req: Request) => {
const writer = responseStream.writable.getWriter(); const writer = responseStream.writable.getWriter();
const encoder = new TextEncoder(); const encoder = new TextEncoder();
let receivedMessage = ''; const disconnect = session.subscribe((event: string, data: any) => {
if (event === 'data') {
session.addListener('data', (data: any) => { if (data.type === 'block') {
if (data.type === 'response') { writer.write(
encoder.encode(
JSON.stringify({
type: 'block',
block: data.block,
}) + '\n',
),
);
} else if (data.type === 'updateBlock') {
writer.write(
encoder.encode(
JSON.stringify({
type: 'updateBlock',
blockId: data.blockId,
patch: data.patch,
}) + '\n',
),
);
} else if (data.type === 'researchComplete') {
writer.write(
encoder.encode(
JSON.stringify({
type: 'researchComplete',
}) + '\n',
),
);
}
} else if (event === 'end') {
writer.write( writer.write(
encoder.encode( encoder.encode(
JSON.stringify({ JSON.stringify({
type: 'message', type: 'messageEnd',
}) + '\n',
),
);
writer.close();
session.removeAllListeners();
} else if (event === 'error') {
writer.write(
encoder.encode(
JSON.stringify({
type: 'error',
data: data.data, data: data.data,
}) + '\n', }) + '\n',
), ),
); );
receivedMessage += data.data; writer.close();
} else if (data.type === 'sources') { session.removeAllListeners();
writer.write(
encoder.encode(
JSON.stringify({
type: 'sources',
data: data.data,
}) + '\n',
),
);
} else if (data.type === 'block') {
writer.write(
encoder.encode(
JSON.stringify({
type: 'block',
block: data.block,
}) + '\n',
),
);
} else if (data.type === 'updateBlock') {
writer.write(
encoder.encode(
JSON.stringify({
type: 'updateBlock',
blockId: data.blockId,
patch: data.patch,
}) + '\n',
),
);
} else if (data.type === 'researchComplete') {
writer.write(
encoder.encode(
JSON.stringify({
type: 'researchComplete',
}) + '\n',
),
);
} }
}); });
session.addListener('end', () => {
writer.write(
encoder.encode(
JSON.stringify({
type: 'messageEnd',
}) + '\n',
),
);
writer.close();
session.removeAllListeners();
});
session.addListener('error', (data: any) => {
writer.write(
encoder.encode(
JSON.stringify({
type: 'error',
data: data.data,
}) + '\n',
),
);
writer.close();
session.removeAllListeners();
});
agent.searchAsync(session, { agent.searchAsync(session, {
chatHistory: history, chatHistory: history,
followUp: message.content, followUp: message.content,
chatId: body.message.chatId,
messageId: body.message.messageId,
config: { config: {
llm, llm,
embedding: embedding, embedding: embedding,
sources: ['web'], sources: body.sources as SearchSources[],
mode: body.optimizationMode, mode: body.optimizationMode,
fileIds: body.files, fileIds: body.files,
systemInstructions: body.systemInstructions || 'None',
}, },
}); });
/* handleHistorySave(message, humanMessageId, body.focusMode, body.files); */ ensureChatExists({
id: body.message.chatId,
sources: body.sources as SearchSources[],
fileIds: body.files,
query: body.message.content,
});
req.signal.addEventListener('abort', () => {
disconnect();
writer.close();
});
return new Response(responseStream.readable, { return new Response(responseStream.readable, {
headers: { headers: {

View File

@@ -0,0 +1,93 @@
import SessionManager from '@/lib/session';
export const POST = async (
req: Request,
{ params }: { params: Promise<{ id: string }> },
) => {
try {
const { id } = await params;
const session = SessionManager.getSession(id);
if (!session) {
return Response.json({ message: 'Session not found' }, { status: 404 });
}
const responseStream = new TransformStream();
const writer = responseStream.writable.getWriter();
const encoder = new TextEncoder();
const disconnect = session.subscribe((event, data) => {
if (event === 'data') {
if (data.type === 'block') {
writer.write(
encoder.encode(
JSON.stringify({
type: 'block',
block: data.block,
}) + '\n',
),
);
} else if (data.type === 'updateBlock') {
writer.write(
encoder.encode(
JSON.stringify({
type: 'updateBlock',
blockId: data.blockId,
patch: data.patch,
}) + '\n',
),
);
} else if (data.type === 'researchComplete') {
writer.write(
encoder.encode(
JSON.stringify({
type: 'researchComplete',
}) + '\n',
),
);
}
} else if (event === 'end') {
writer.write(
encoder.encode(
JSON.stringify({
type: 'messageEnd',
}) + '\n',
),
);
writer.close();
disconnect();
} else if (event === 'error') {
writer.write(
encoder.encode(
JSON.stringify({
type: 'error',
data: data.data,
}) + '\n',
),
);
writer.close();
disconnect();
}
});
req.signal.addEventListener('abort', () => {
disconnect();
writer.close();
});
return new Response(responseStream.readable, {
headers: {
'Content-Type': 'text/event-stream',
Connection: 'keep-alive',
'Cache-Control': 'no-cache, no-transform',
},
});
} catch (err) {
console.error('Error in reconnecting to session stream: ', err);
return Response.json(
{ message: 'An error has occurred.' },
{ status: 500 },
);
}
};

View File

@@ -57,7 +57,7 @@ export const POST = async (req: Request) => {
llm: llm, llm: llm,
sources: ['web', 'discussions', 'academic'], sources: ['web', 'discussions', 'academic'],
mode: 'balanced', mode: 'balanced',
fileIds: [] fileIds: [],
}, },
followUp: body.query, followUp: body.query,
}); });

View File

@@ -1,10 +1,5 @@
'use client'; 'use client';
import ChatWindow from '@/components/ChatWindow'; import ChatWindow from '@/components/ChatWindow';
import React from 'react';
const Page = () => { export default ChatWindow;
return <ChatWindow />;
};
export default Page;

View File

@@ -34,7 +34,7 @@ export default function RootLayout({
return ( return (
<html className="h-full" lang="en" suppressHydrationWarning> <html className="h-full" lang="en" suppressHydrationWarning>
<body className={cn('h-full', montserrat.className)}> <body className={cn('h-full antialiased', montserrat.className)}>
<ThemeProvider> <ThemeProvider>
{setupComplete ? ( {setupComplete ? (
<ChatProvider> <ChatProvider>

View File

@@ -1,8 +1,8 @@
'use client'; 'use client';
import DeleteChat from '@/components/DeleteChat'; import DeleteChat from '@/components/DeleteChat';
import { cn, formatTimeDifference } from '@/lib/utils'; import { formatTimeDifference } from '@/lib/utils';
import { BookOpenText, ClockIcon, Delete, ScanEye } from 'lucide-react'; import { BookOpenText, ClockIcon, FileText, Globe2Icon } from 'lucide-react';
import Link from 'next/link'; import Link from 'next/link';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
@@ -10,7 +10,8 @@ export interface Chat {
id: string; id: string;
title: string; title: string;
createdAt: string; createdAt: string;
focusMode: string; sources: string[];
files: { fileId: string; name: string }[];
} }
const Page = () => { const Page = () => {
@@ -37,74 +38,137 @@ const Page = () => {
fetchChats(); fetchChats();
}, []); }, []);
return loading ? ( return (
<div className="flex flex-row items-center justify-center min-h-screen">
<svg
aria-hidden="true"
className="w-8 h-8 text-light-200 fill-light-secondary dark:text-[#202020] animate-spin dark:fill-[#ffffff3b]"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100.003 78.2051 78.1951 100.003 50.5908 100C22.9765 99.9972 0.997224 78.018 1 50.4037C1.00281 22.7993 22.8108 0.997224 50.4251 1C78.0395 1.00281 100.018 22.8108 100 50.4251ZM9.08164 50.594C9.06312 73.3997 27.7909 92.1272 50.5966 92.1457C73.4023 92.1642 92.1298 73.4365 92.1483 50.6308C92.1669 27.8251 73.4392 9.0973 50.6335 9.07878C27.8278 9.06026 9.10003 27.787 9.08164 50.594Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4037 97.8624 35.9116 96.9801 33.5533C95.1945 28.8227 92.871 24.3692 90.0681 20.348C85.6237 14.1775 79.4473 9.36872 72.0454 6.45794C64.6435 3.54717 56.3134 2.65431 48.3133 3.89319C45.869 4.27179 44.3768 6.77534 45.014 9.20079C45.6512 11.6262 48.1343 13.0956 50.5786 12.717C56.5073 11.8281 62.5542 12.5399 68.0406 14.7911C73.527 17.0422 78.2187 20.7487 81.5841 25.4923C83.7976 28.5886 85.4467 32.059 86.4416 35.7474C87.1273 38.1189 89.5423 39.6781 91.9676 39.0409Z"
fill="currentFill"
/>
</svg>
</div>
) : (
<div> <div>
<div className="flex flex-col pt-4"> <div className="flex flex-col pt-10 border-b border-light-200/20 dark:border-dark-200/20 pb-6 px-2">
<div className="flex items-center"> <div className="flex flex-col lg:flex-row lg:items-end lg:justify-between gap-3">
<BookOpenText /> <div className="flex items-center justify-center">
<h1 className="text-3xl font-medium p-2">Library</h1> <BookOpenText size={45} className="mb-2.5" />
</div> <div className="flex flex-col">
<hr className="border-t border-[#2B2C2C] my-4 w-full" /> <h1
</div> className="text-5xl font-normal p-2 pb-0"
{chats.length === 0 && ( style={{ fontFamily: 'PP Editorial, serif' }}
<div className="flex flex-row items-center justify-center min-h-screen">
<p className="text-black/70 dark:text-white/70 text-sm">
No chats found.
</p>
</div>
)}
{chats.length > 0 && (
<div className="flex flex-col pb-20 lg:pb-2">
{chats.map((chat, i) => (
<div
className={cn(
'flex flex-col space-y-4 py-6',
i !== chats.length - 1
? 'border-b border-white-200 dark:border-dark-200'
: '',
)}
key={i}
>
<Link
href={`/c/${chat.id}`}
className="text-black dark:text-white lg:text-xl font-medium truncate transition duration-200 hover:text-[#24A0ED] dark:hover:text-[#24A0ED] cursor-pointer"
> >
{chat.title} Library
</Link> </h1>
<div className="flex flex-row items-center justify-between w-full"> <div className="px-2 text-sm text-black/60 dark:text-white/60 text-center lg:text-left">
<div className="flex flex-row items-center space-x-1 lg:space-x-1.5 text-black/70 dark:text-white/70"> Past chats, sources, and uploads.
<ClockIcon size={15} />
<p className="text-xs">
{formatTimeDifference(new Date(), chat.createdAt)} Ago
</p>
</div>
<DeleteChat
chatId={chat.id}
chats={chats}
setChats={setChats}
/>
</div> </div>
</div> </div>
))} </div>
<div className="flex items-center justify-center lg:justify-end gap-2 text-xs text-black/60 dark:text-white/60">
<span className="inline-flex items-center gap-1 rounded-full border border-black/20 dark:border-white/20 px-2 py-0.5">
<BookOpenText size={14} />
{loading
? 'Loading…'
: `${chats.length} ${chats.length === 1 ? 'chat' : 'chats'}`}
</span>
</div>
</div>
</div>
{loading ? (
<div className="flex flex-row items-center justify-center min-h-[60vh]">
<svg
aria-hidden="true"
className="w-8 h-8 text-light-200 fill-light-secondary dark:text-[#202020] animate-spin dark:fill-[#ffffff3b]"
viewBox="0 0 100 101"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M100 50.5908C100.003 78.2051 78.1951 100.003 50.5908 100C22.9765 99.9972 0.997224 78.018 1 50.4037C1.00281 22.7993 22.8108 0.997224 50.4251 1C78.0395 1.00281 100.018 22.8108 100 50.4251ZM9.08164 50.594C9.06312 73.3997 27.7909 92.1272 50.5966 92.1457C73.4023 92.1642 92.1298 73.4365 92.1483 50.6308C92.1669 27.8251 73.4392 9.0973 50.6335 9.07878C27.8278 9.06026 9.10003 27.787 9.08164 50.594Z"
fill="currentColor"
/>
<path
d="M93.9676 39.0409C96.393 38.4037 97.8624 35.9116 96.9801 33.5533C95.1945 28.8227 92.871 24.3692 90.0681 20.348C85.6237 14.1775 79.4473 9.36872 72.0454 6.45794C64.6435 3.54717 56.3134 2.65431 48.3133 3.89319C45.869 4.27179 44.3768 6.77534 45.014 9.20079C45.6512 11.6262 48.1343 13.0956 50.5786 12.717C56.5073 11.8281 62.5542 12.5399 68.0406 14.7911C73.527 17.0422 78.2187 20.7487 81.5841 25.4923C83.7976 28.5886 85.4467 32.059 86.4416 35.7474C87.1273 38.1189 89.5423 39.6781 91.9676 39.0409Z"
fill="currentFill"
/>
</svg>
</div>
) : chats.length === 0 ? (
<div className="flex flex-col items-center justify-center min-h-[70vh] px-2 text-center">
<div className="flex items-center justify-center w-12 h-12 rounded-2xl border border-light-200 dark:border-dark-200 bg-light-secondary dark:bg-dark-secondary">
<BookOpenText className="text-black/70 dark:text-white/70" />
</div>
<p className="mt-2 text-black/70 dark:text-white/70 text-sm">
No chats found.
</p>
<p className="mt-1 text-black/70 dark:text-white/70 text-sm">
<Link href="/" className="text-sky-400">
Start a new chat
</Link>{' '}
to see it listed here.
</p>
</div>
) : (
<div className="pt-6 pb-28 px-2">
<div className="rounded-2xl border border-light-200 dark:border-dark-200 overflow-hidden bg-light-primary dark:bg-dark-primary">
{chats.map((chat, index) => {
const sourcesLabel =
chat.sources.length === 0
? null
: chat.sources.length <= 2
? chat.sources
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
.join(', ')
: `${chat.sources
.slice(0, 2)
.map((s) => s.charAt(0).toUpperCase() + s.slice(1))
.join(', ')} + ${chat.sources.length - 2}`;
return (
<div
key={chat.id}
className={
'group flex flex-col gap-2 p-4 hover:bg-light-secondary dark:hover:bg-dark-secondary transition-colors duration-200 ' +
(index !== chats.length - 1
? 'border-b border-light-200 dark:border-dark-200'
: '')
}
>
<div className="flex items-start justify-between gap-3">
<Link
href={`/c/${chat.id}`}
className="flex-1 text-black dark:text-white text-base lg:text-lg font-medium leading-snug line-clamp-2 group-hover:text-[#24A0ED] transition duration-200"
title={chat.title}
>
{chat.title}
</Link>
<div className="pt-0.5 shrink-0">
<DeleteChat
chatId={chat.id}
chats={chats}
setChats={setChats}
/>
</div>
</div>
<div className="flex flex-wrap items-center gap-2 text-black/70 dark:text-white/70">
<span className="inline-flex items-center gap-1 text-xs">
<ClockIcon size={14} />
{formatTimeDifference(new Date(), chat.createdAt)} Ago
</span>
{sourcesLabel && (
<span className="inline-flex items-center gap-1 text-xs border border-black/20 dark:border-white/20 rounded-full px-2 py-0.5">
<Globe2Icon size={14} />
{sourcesLabel}
</span>
)}
{chat.files.length > 0 && (
<span className="inline-flex items-center gap-1 text-xs border border-black/20 dark:border-white/20 rounded-full px-2 py-0.5">
<FileText size={14} />
{chat.files.length}{' '}
{chat.files.length === 1 ? 'file' : 'files'}
</span>
)}
</div>
</div>
);
})}
</div>
</div> </div>
)} )}
</div> </div>

View File

@@ -54,17 +54,21 @@ const getStepTitle = (
const AssistantSteps = ({ const AssistantSteps = ({
block, block,
status, status,
isLast,
}: { }: {
block: ResearchBlock; block: ResearchBlock;
status: 'answering' | 'completed' | 'error'; status: 'answering' | 'completed' | 'error';
isLast: boolean;
}) => { }) => {
const [isExpanded, setIsExpanded] = useState(true); const [isExpanded, setIsExpanded] = useState(
isLast && status === 'answering' ? true : false,
);
const { researchEnded, loading } = useChat(); const { researchEnded, loading } = useChat();
useEffect(() => { useEffect(() => {
if (researchEnded) { if (researchEnded && isLast) {
setIsExpanded(false); setIsExpanded(false);
} else if (status === 'answering') { } else if (status === 'answering' && isLast) {
setIsExpanded(true); setIsExpanded(true);
} }
}, [researchEnded, status]); }, [researchEnded, status]);
@@ -182,8 +186,10 @@ const AssistantSteps = ({
: ''; : '';
return ( return (
<span <a
key={idx} key={idx}
href={url}
target="_blank"
className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md text-xs font-medium bg-light-100 dark:bg-dark-100 text-black/70 dark:text-white/70 border border-light-200 dark:border-dark-200" className="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md text-xs font-medium bg-light-100 dark:bg-dark-100 text-black/70 dark:text-white/70 border border-light-200 dark:border-dark-200"
> >
{faviconUrl && ( {faviconUrl && (
@@ -197,7 +203,7 @@ const AssistantSteps = ({
/> />
)} )}
<span className="line-clamp-1">{title}</span> <span className="line-clamp-1">{title}</span>
</span> </a>
); );
})} })}
</div> </div>
@@ -219,33 +225,26 @@ const AssistantSteps = ({
{step.type === 'upload_search_results' && {step.type === 'upload_search_results' &&
step.results.length > 0 && ( step.results.length > 0 && (
<div className="mt-1.5 space-y-2"> <div className="mt-1.5 grid gap-3 lg:grid-cols-3">
{step.results.slice(0, 4).map((result, idx) => { {step.results.slice(0, 4).map((result, idx) => {
const title = const title =
(result.metadata && (result.metadata &&
(result.metadata.title || (result.metadata.title ||
result.metadata.fileName)) || result.metadata.fileName)) ||
'Untitled document'; 'Untitled document';
const snippet = (result.content || '')
.replace(/\s+/g, ' ')
.trim()
.slice(0, 220);
return ( return (
<div <div
key={idx} key={idx}
className="flex gap-3 items-start rounded-lg border border-light-200 dark:border-dark-200 bg-light-100 dark:bg-dark-100 p-2" className="flex flex-row space-x-3 rounded-lg border border-light-200 dark:border-dark-200 bg-light-100 dark:bg-dark-100 p-2 cursor-pointer"
> >
<div className="mt-0.5 h-10 w-10 rounded-md bg-cyan-100 text-cyan-800 dark:bg-sky-500 dark:text-cyan-50 flex items-center justify-center"> <div className="mt-0.5 h-10 w-10 rounded-md bg-cyan-100 text-cyan-800 dark:bg-sky-500 dark:text-cyan-50 flex items-center justify-center">
<FileText className="w-5 h-5" /> <FileText className="w-5 h-5" />
</div> </div>
<div className="flex-1 min-w-0"> <div className="flex flex-col justify-center">
<div className="text-sm font-semibold text-black dark:text-white line-clamp-1"> <p className="text-[13px] text-black dark:text-white line-clamp-1">
{title} {title}
</div> </p>
<div className="text-xs text-black/70 dark:text-white/70 mt-0.5 leading-relaxed line-clamp-3">
{snippet || 'No preview available.'}
</div>
</div> </div>
</div> </div>
); );

View File

@@ -59,7 +59,7 @@ const Chat = () => {
}, [messages]); }, [messages]);
return ( return (
<div className="flex flex-col space-y-6 pt-8 pb-28 sm:mx-4 md:mx-8"> <div className="flex flex-col space-y-6 pt-8 pb-44 lg:pb-28 sm:mx-4 md:mx-8">
{sections.map((section, i) => { {sections.map((section, i) => {
const isLast = i === sections.length - 1; const isLast = i === sections.length - 1;
@@ -80,7 +80,7 @@ const Chat = () => {
{loading && !messageAppeared && <MessageBoxLoading />} {loading && !messageAppeared && <MessageBoxLoading />}
<div ref={messageEnd} className="h-0" /> <div ref={messageEnd} className="h-0" />
{dividerWidth > 0 && ( {dividerWidth > 0 && (
<div className="bottom-6 fixed z-40" style={{ width: dividerWidth }}> <div className="fixed z-40 bottom-24 lg:bottom-6" style={{ width: dividerWidth }}>
<div <div
className="pointer-events-none absolute -bottom-6 left-0 right-0 h-[calc(100%+24px+24px)] dark:hidden" className="pointer-events-none absolute -bottom-6 left-0 right-0 h-[calc(100%+24px+24px)] dark:hidden"
style={{ style={{

View File

@@ -6,7 +6,8 @@ import EmptyChat from './EmptyChat';
import NextError from 'next/error'; import NextError from 'next/error';
import { useChat } from '@/lib/hooks/useChat'; import { useChat } from '@/lib/hooks/useChat';
import SettingsButtonMobile from './Settings/SettingsButtonMobile'; import SettingsButtonMobile from './Settings/SettingsButtonMobile';
import { Block, Chunk } from '@/lib/types'; import { Block } from '@/lib/types';
import Loader from './ui/Loader';
export interface BaseMessage { export interface BaseMessage {
chatId: string; chatId: string;
@@ -21,35 +22,6 @@ export interface Message extends BaseMessage {
status: 'answering' | 'completed' | 'error'; status: 'answering' | 'completed' | 'error';
} }
export interface UserMessage extends BaseMessage {
role: 'user';
content: string;
}
export interface AssistantMessage extends BaseMessage {
role: 'assistant';
content: string;
suggestions?: string[];
}
export interface SourceMessage extends BaseMessage {
role: 'source';
sources: Chunk[];
}
export interface SuggestionMessage extends BaseMessage {
role: 'suggestion';
suggestions: string[];
}
export type LegacyMessage =
| AssistantMessage
| UserMessage
| SourceMessage
| SuggestionMessage;
export type ChatTurn = UserMessage | AssistantMessage;
export interface File { export interface File {
fileName: string; fileName: string;
fileExtension: string; fileExtension: string;
@@ -62,7 +34,8 @@ export interface Widget {
} }
const ChatWindow = () => { const ChatWindow = () => {
const { hasError, notFound, messages } = useChat(); const { hasError, notFound, messages, isReady } = useChat();
if (hasError) { if (hasError) {
return ( return (
<div className="relative"> <div className="relative">
@@ -78,18 +51,24 @@ const ChatWindow = () => {
); );
} }
return notFound ? ( return isReady ? (
<NextError statusCode={404} /> notFound ? (
<NextError statusCode={404} />
) : (
<div>
{messages.length > 0 ? (
<>
<Navbar />
<Chat />
</>
) : (
<EmptyChat />
)}
</div>
)
) : ( ) : (
<div> <div className="flex items-center justify-center min-h-screen w-full">
{messages.length > 0 ? ( <Loader />
<>
<Navbar />
<Chat />
</>
) : (
<EmptyChat />
)}
</div> </div>
); );
}; };

View File

@@ -1,7 +1,7 @@
import { ArrowRight } from 'lucide-react'; import { ArrowRight } from 'lucide-react';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
import Focus from './MessageInputActions/Focus'; import Sources from './MessageInputActions/Sources';
import Optimization from './MessageInputActions/Optimization'; import Optimization from './MessageInputActions/Optimization';
import Attach from './MessageInputActions/Attach'; import Attach from './MessageInputActions/Attach';
import { useChat } from '@/lib/hooks/useChat'; import { useChat } from '@/lib/hooks/useChat';
@@ -68,8 +68,8 @@ const EmptyChatMessageInput = () => {
<Optimization /> <Optimization />
<div className="flex flex-row items-center space-x-2"> <div className="flex flex-row items-center space-x-2">
<div className="flex flex-row items-center space-x-1"> <div className="flex flex-row items-center space-x-1">
<Sources />
<ModelSelector /> <ModelSelector />
<Focus />
<Attach /> <Attach />
</div> </div>
<button <button

View File

@@ -2,6 +2,7 @@ import { Check, ClipboardList } from 'lucide-react';
import { Message } from '../ChatWindow'; import { Message } from '../ChatWindow';
import { useState } from 'react'; import { useState } from 'react';
import { Section } from '@/lib/hooks/useChat'; import { Section } from '@/lib/hooks/useChat';
import { SourceBlock } from '@/lib/types';
const Copy = ({ const Copy = ({
section, section,
@@ -15,15 +16,24 @@ const Copy = ({
return ( return (
<button <button
onClick={() => { onClick={() => {
const sources = section.message.responseBlocks.filter(
(b) => b.type === 'source' && b.data.length > 0,
) as SourceBlock[];
const contentToCopy = `${initialMessage}${ const contentToCopy = `${initialMessage}${
section?.message.responseBlocks.filter((b) => b.type === 'source') sources.length > 0 &&
?.length > 0 && `\n\nCitations:\n${sources
`\n\nCitations:\n${section.message.responseBlocks .map((source) => source.data)
.filter((b) => b.type === 'source') .flat()
?.map((source: any, i: any) => `[${i + 1}] ${source.metadata.url}`) .map(
(s, i) =>
`[${i + 1}] ${s.metadata.url.startsWith('file_id://') ? s.metadata.fileName || 'Uploaded File' : s.metadata.url}`,
)
.join(`\n`)}` .join(`\n`)}`
}`; }`;
navigator.clipboard.writeText(contentToCopy); navigator.clipboard.writeText(contentToCopy);
setCopied(true); setCopied(true);
setTimeout(() => setCopied(false), 1000); setTimeout(() => setCopied(false), 1000);
}} }}

View File

@@ -12,7 +12,7 @@ import {
Plus, Plus,
CornerDownRight, CornerDownRight,
} from 'lucide-react'; } from 'lucide-react';
import Markdown, { MarkdownToJSX } from 'markdown-to-jsx'; import Markdown, { MarkdownToJSX, RuleType } from 'markdown-to-jsx';
import Copy from './MessageActions/Copy'; import Copy from './MessageActions/Copy';
import Rewrite from './MessageActions/Rewrite'; import Rewrite from './MessageActions/Rewrite';
import MessageSources from './MessageSources'; import MessageSources from './MessageSources';
@@ -21,10 +21,11 @@ import SearchVideos from './SearchVideos';
import { useSpeech } from 'react-text-to-speech'; import { useSpeech } from 'react-text-to-speech';
import ThinkBox from './ThinkBox'; import ThinkBox from './ThinkBox';
import { useChat, Section } from '@/lib/hooks/useChat'; import { useChat, Section } from '@/lib/hooks/useChat';
import Citation from './Citation'; import Citation from './MessageRenderer/Citation';
import AssistantSteps from './AssistantSteps'; import AssistantSteps from './AssistantSteps';
import { ResearchBlock } from '@/lib/types'; import { ResearchBlock } from '@/lib/types';
import Renderer from './Widgets/Renderer'; import Renderer from './Widgets/Renderer';
import CodeBlock from './MessageRenderer/CodeBlock';
const ThinkTagProcessor = ({ const ThinkTagProcessor = ({
children, children,
@@ -67,6 +68,21 @@ const MessageBox = ({
const { speechStatus, start, stop } = useSpeech({ text: speechMessage }); const { speechStatus, start, stop } = useSpeech({ text: speechMessage });
const markdownOverrides: MarkdownToJSX.Options = { const markdownOverrides: MarkdownToJSX.Options = {
renderRule(next, node, renderChildren, state) {
if (node.type === RuleType.codeInline) {
return `\`${node.text}\``;
}
if (node.type === RuleType.codeBlock) {
return (
<CodeBlock key={state.key} language={node.lang || ''}>
{node.text}
</CodeBlock>
);
}
return next();
},
overrides: { overrides: {
think: { think: {
component: ThinkTagProcessor, component: ThinkTagProcessor,
@@ -115,12 +131,11 @@ const MessageBox = ({
<AssistantSteps <AssistantSteps
block={researchBlock} block={researchBlock}
status={section.message.status} status={section.message.status}
isLast={isLast}
/> />
</div> </div>
))} ))}
{section.widgets.length > 0 && <Renderer widgets={section.widgets} />}
{isLast && {isLast &&
loading && loading &&
!researchEnded && !researchEnded &&
@@ -135,6 +150,8 @@ const MessageBox = ({
</div> </div>
)} )}
{section.widgets.length > 0 && <Renderer widgets={section.widgets} />}
<div className="flex flex-col space-y-2"> <div className="flex flex-col space-y-2">
{sources.length > 0 && ( {sources.length > 0 && (
<div className="flex flex-row items-center space-x-2"> <div className="flex flex-row items-center space-x-2">
@@ -218,10 +235,10 @@ const MessageBox = ({
className="group w-full py-4 text-left transition-colors duration-200" className="group w-full py-4 text-left transition-colors duration-200"
> >
<div className="flex items-center justify-between gap-3"> <div className="flex items-center justify-between gap-3">
<div className="flex flex-row space-x-3 items-center "> <div className="flex flex-row space-x-3 items-center">
<CornerDownRight <CornerDownRight
size={17} size={15}
className="group-hover:text-sky-400 transition-colors duration-200" className="group-hover:text-sky-400 transition-colors duration-200 flex-shrink-0"
/> />
<p className="text-sm text-black/70 dark:text-white/70 group-hover:text-sky-400 transition-colors duration-200 leading-relaxed"> <p className="text-sm text-black/70 dark:text-white/70 group-hover:text-sky-400 transition-colors duration-200 leading-relaxed">
{suggestion} {suggestion}

View File

@@ -2,7 +2,6 @@ import { cn } from '@/lib/utils';
import { ArrowUp } from 'lucide-react'; import { ArrowUp } from 'lucide-react';
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import TextareaAutosize from 'react-textarea-autosize'; import TextareaAutosize from 'react-textarea-autosize';
import CopilotToggle from './MessageInputActions/Copilot';
import AttachSmall from './MessageInputActions/AttachSmall'; import AttachSmall from './MessageInputActions/AttachSmall';
import { useChat } from '@/lib/hooks/useChat'; import { useChat } from '@/lib/hooks/useChat';
@@ -62,7 +61,7 @@ const MessageInput = () => {
} }
}} }}
className={cn( className={cn(
'relative bg-light-secondary dark:bg-dark-secondary p-4 flex items-center overflow-hidden border border-light-200 dark:border-dark-200 shadow-sm shadow-light-200/10 dark:shadow-black/20 transition-all duration-200 focus-within:border-light-300 dark:focus-within:border-dark-300', 'relative bg-light-secondary dark:bg-dark-secondary p-4 flex items-center overflow-visible border border-light-200 dark:border-dark-200 shadow-sm shadow-light-200/10 dark:shadow-black/20 transition-all duration-200 focus-within:border-light-300 dark:focus-within:border-dark-300',
mode === 'multi' ? 'flex-col rounded-2xl' : 'flex-row rounded-full', mode === 'multi' ? 'flex-col rounded-2xl' : 'flex-row rounded-full',
)} )}
> >
@@ -78,11 +77,16 @@ const MessageInput = () => {
placeholder="Ask a follow-up" placeholder="Ask a follow-up"
/> />
{mode === 'single' && ( {mode === 'single' && (
<div className="flex flex-row items-center space-x-4"> <button
<CopilotToggle disabled={message.trim().length === 0 || loading}
copilotEnabled={copilotEnabled} className="bg-[#24A0ED] text-white disabled:text-black/50 dark:disabled:text-white/50 hover:bg-opacity-85 transition duration-100 disabled:bg-[#e0e0dc79] dark:disabled:bg-[#ececec21] rounded-full p-2"
setCopilotEnabled={setCopilotEnabled} >
/> <ArrowUp className="bg-background" size={17} />
</button>
)}
{mode === 'multi' && (
<div className="flex flex-row items-center justify-between w-full pt-2">
<AttachSmall />
<button <button
disabled={message.trim().length === 0 || loading} disabled={message.trim().length === 0 || loading}
className="bg-[#24A0ED] text-white disabled:text-black/50 dark:disabled:text-white/50 hover:bg-opacity-85 transition duration-100 disabled:bg-[#e0e0dc79] dark:disabled:bg-[#ececec21] rounded-full p-2" className="bg-[#24A0ED] text-white disabled:text-black/50 dark:disabled:text-white/50 hover:bg-opacity-85 transition duration-100 disabled:bg-[#e0e0dc79] dark:disabled:bg-[#ececec21] rounded-full p-2"
@@ -91,23 +95,6 @@ const MessageInput = () => {
</button> </button>
</div> </div>
)} )}
{mode === 'multi' && (
<div className="flex flex-row items-center justify-between w-full pt-2">
<AttachSmall />
<div className="flex flex-row items-center space-x-4">
<CopilotToggle
copilotEnabled={copilotEnabled}
setCopilotEnabled={setCopilotEnabled}
/>
<button
disabled={message.trim().length === 0 || loading}
className="bg-[#24A0ED] text-white disabled:text-black/50 dark:disabled:text-white/50 hover:bg-opacity-85 transition duration-100 disabled:bg-[#e0e0dc79] dark:disabled:bg-[#ececec21] rounded-full p-2"
>
<ArrowUp className="bg-background" size={17} />
</button>
</div>
</div>
)}
</form> </form>
); );
}; };

View File

@@ -16,6 +16,8 @@ import {
} from 'lucide-react'; } from 'lucide-react';
import { Fragment, useRef, useState } from 'react'; import { Fragment, useRef, useState } from 'react';
import { useChat } from '@/lib/hooks/useChat'; import { useChat } from '@/lib/hooks/useChat';
import { AnimatePresence } from 'motion/react';
import { motion } from 'framer-motion';
const Attach = () => { const Attach = () => {
const { files, setFiles, setFileIds, fileIds } = useChat(); const { files, setFiles, setFileIds, fileIds } = useChat();
@@ -53,86 +55,95 @@ const Attach = () => {
return loading ? ( return loading ? (
<div className="active:border-none hover:bg-light-200 hover:dark:bg-dark-200 p-2 rounded-lg focus:outline-none text-black/50 dark:text-white/50 transition duration-200"> <div className="active:border-none hover:bg-light-200 hover:dark:bg-dark-200 p-2 rounded-lg focus:outline-none text-black/50 dark:text-white/50 transition duration-200">
<LoaderCircle size={16} className="text-sky-400 animate-spin" /> <LoaderCircle size={16} className="text-sky-500 animate-spin" />
</div> </div>
) : files.length > 0 ? ( ) : files.length > 0 ? (
<Popover className="relative w-full max-w-[15rem] md:max-w-md lg:max-w-lg"> <Popover className="relative w-full max-w-[15rem] md:max-w-md lg:max-w-lg">
<PopoverButton {({ open }) => (
type="button" <>
className="active:border-none hover:bg-light-200 hover:dark:bg-dark-200 p-2 rounded-lg focus:outline-none headless-open:text-black dark:headless-open:text-white text-black/50 dark:text-white/50 active:scale-95 transition duration-200 hover:text-black dark:hover:text-white" <PopoverButton
> type="button"
<File size={16} className="text-sky-400" /> className="active:border-none hover:bg-light-200 hover:dark:bg-dark-200 p-2 rounded-lg focus:outline-none headless-open:text-black dark:headless-open:text-white text-black/50 dark:text-white/50 active:scale-95 transition duration-200 hover:text-black dark:hover:text-white"
</PopoverButton> >
<Transition <File size={16} className="text-sky-500" />
as={Fragment} </PopoverButton>
enter="transition ease-out duration-150" <AnimatePresence>
enterFrom="opacity-0 translate-y-1" {open && (
enterTo="opacity-100 translate-y-0" <PopoverPanel
leave="transition ease-in duration-150" className="absolute z-10 w-64 md:w-[350px] right-0"
leaveFrom="opacity-100 translate-y-0" static
leaveTo="opacity-0 translate-y-1" >
> <motion.div
<PopoverPanel className="absolute z-10 w-64 md:w-[350px] right-0"> initial={{ opacity: 0, scale: 0.9 }}
<div className="bg-light-primary dark:bg-dark-primary border rounded-md border-light-200 dark:border-dark-200 w-full max-h-[200px] md:max-h-none overflow-y-auto flex flex-col"> animate={{ opacity: 1, scale: 1 }}
<div className="flex flex-row items-center justify-between px-3 py-2"> exit={{ opacity: 0, scale: 0.9 }}
<h4 className="text-black dark:text-white font-medium text-sm"> transition={{ duration: 0.1, ease: 'easeOut' }}
Attached files className="origin-top-right bg-light-primary dark:bg-dark-primary border rounded-md border-light-200 dark:border-dark-200 w-full max-h-[200px] md:max-h-none overflow-y-auto flex flex-col"
</h4>
<div className="flex flex-row items-center space-x-4">
<button
type="button"
onClick={() => fileInputRef.current.click()}
className="flex flex-row items-center space-x-1 text-black/70 dark:text-white/70 hover:text-black hover:dark:text-white transition duration-200 focus:outline-none"
> >
<input <div className="flex flex-row items-center justify-between px-3 py-2">
type="file" <h4 className="text-black/70 dark:text-white/70 text-sm">
onChange={handleChange} Attached files
ref={fileInputRef} </h4>
accept=".pdf,.docx,.txt" <div className="flex flex-row items-center space-x-4">
multiple <button
hidden type="button"
/> onClick={() => fileInputRef.current.click()}
<Plus size={16} /> className="flex flex-row items-center space-x-1 text-black/70 dark:text-white/70 hover:text-black hover:dark:text-white transition duration-200 focus:outline-none"
<p className="text-xs">Add</p> >
</button> <input
<button type="file"
onClick={() => { onChange={handleChange}
setFiles([]); ref={fileInputRef}
setFileIds([]); accept=".pdf,.docx,.txt"
}} multiple
className="flex flex-row items-center space-x-1 text-black/70 dark:text-white/70 hover:text-black hover:dark:text-white transition duration-200 focus:outline-none" hidden
> />
<Trash size={14} /> <Plus size={16} />
<p className="text-xs">Clear</p> <p className="text-xs">Add</p>
</button> </button>
</div> <button
</div> onClick={() => {
<div className="h-[0.5px] mx-2 bg-white/10" /> setFiles([]);
<div className="flex flex-col items-center"> setFileIds([]);
{files.map((file, i) => ( }}
<div className="flex flex-row items-center space-x-1 text-black/70 dark:text-white/70 hover:text-black hover:dark:text-white transition duration-200 focus:outline-none"
key={i} >
className="flex flex-row items-center justify-start w-full space-x-3 p-3" <Trash size={13} />
> <p className="text-xs">Clear</p>
<div className="bg-light-100 dark:bg-dark-100 flex items-center justify-center w-10 h-10 rounded-md"> </button>
<File </div>
size={16}
className="text-black/70 dark:text-white/70"
/>
</div> </div>
<p className="text-black/70 dark:text-white/70 text-sm"> <div className="h-[0.5px] mx-2 bg-white/10" />
{file.fileName.length > 25 <div className="flex flex-col items-center">
? file.fileName.replace(/\.\w+$/, '').substring(0, 25) + {files.map((file, i) => (
'...' + <div
file.fileExtension key={i}
: file.fileName} className="flex flex-row items-center justify-start w-full space-x-3 p-3"
</p> >
</div> <div className="bg-light-100 dark:bg-dark-100 flex items-center justify-center w-9 h-9 rounded-md">
))} <File
</div> size={16}
</div> className="text-black/70 dark:text-white/70"
</PopoverPanel> />
</Transition> </div>
<p className="text-black/70 dark:text-white/70 text-xs">
{file.fileName.length > 25
? file.fileName
.replace(/\.\w+$/, '')
.substring(0, 25) +
'...' +
file.fileExtension
: file.fileName}
</p>
</div>
))}
</div>
</motion.div>
</PopoverPanel>
)}
</AnimatePresence>
</>
)}
</Popover> </Popover>
) : ( ) : (
<button <button

View File

@@ -1,21 +1,14 @@
import { cn } from '@/lib/utils';
import { import {
Popover, Popover,
PopoverButton, PopoverButton,
PopoverPanel, PopoverPanel,
Transition, Transition,
} from '@headlessui/react'; } from '@headlessui/react';
import { import { File, LoaderCircle, Paperclip, Plus, Trash } from 'lucide-react';
CopyPlus,
File,
LoaderCircle,
Paperclip,
Plus,
Trash,
} from 'lucide-react';
import { Fragment, useRef, useState } from 'react'; import { Fragment, useRef, useState } from 'react';
import { File as FileType } from '../ChatWindow';
import { useChat } from '@/lib/hooks/useChat'; import { useChat } from '@/lib/hooks/useChat';
import { AnimatePresence } from 'motion/react';
import { motion } from 'framer-motion';
const AttachSmall = () => { const AttachSmall = () => {
const { files, setFiles, setFileIds, fileIds } = useChat(); const { files, setFiles, setFileIds, fileIds } = useChat();
@@ -53,86 +46,95 @@ const AttachSmall = () => {
return loading ? ( return loading ? (
<div className="flex flex-row items-center justify-between space-x-1 p-1 "> <div className="flex flex-row items-center justify-between space-x-1 p-1 ">
<LoaderCircle size={20} className="text-sky-400 animate-spin" /> <LoaderCircle size={20} className="text-sky-500 animate-spin" />
</div> </div>
) : files.length > 0 ? ( ) : files.length > 0 ? (
<Popover className="max-w-[15rem] md:max-w-md lg:max-w-lg"> <Popover className="max-w-[15rem] md:max-w-md lg:max-w-lg">
<PopoverButton {({ open }) => (
type="button" <>
className="flex flex-row items-center justify-between space-x-1 p-1 text-black/50 dark:text-white/50 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary active:scale-95 transition duration-200 hover:text-black dark:hover:text-white" <PopoverButton
> type="button"
<File size={20} className="text-sky-400" /> className="flex flex-row items-center justify-between space-x-1 p-1 text-black/50 dark:text-white/50 rounded-xl hover:bg-light-secondary dark:hover:bg-dark-secondary active:scale-95 transition duration-200 hover:text-black dark:hover:text-white"
</PopoverButton> >
<Transition <File size={20} className="text-sky-500" />
as={Fragment} </PopoverButton>
enter="transition ease-out duration-150" <AnimatePresence>
enterFrom="opacity-0 translate-y-1" {open && (
enterTo="opacity-100 translate-y-0" <PopoverPanel
leave="transition ease-in duration-150" className="absolute z-10 w-64 md:w-[350px] bottom-14"
leaveFrom="opacity-100 translate-y-0" static
leaveTo="opacity-0 translate-y-1" >
> <motion.div
<PopoverPanel className="absolute z-10 w-64 md:w-[350px] bottom-14 -ml-3"> initial={{ opacity: 0, scale: 0.9 }}
<div className="bg-light-primary dark:bg-dark-primary border rounded-md border-light-200 dark:border-dark-200 w-full max-h-[200px] md:max-h-none overflow-y-auto flex flex-col"> animate={{ opacity: 1, scale: 1 }}
<div className="flex flex-row items-center justify-between px-3 py-2"> exit={{ opacity: 0, scale: 0.9 }}
<h4 className="text-black dark:text-white font-medium text-sm"> transition={{ duration: 0.1, ease: 'easeOut' }}
Attached files className="origin-bottom-left bg-light-primary dark:bg-dark-primary border rounded-md border-light-200 dark:border-dark-200 w-full max-h-[200px] md:max-h-none overflow-y-auto flex flex-col"
</h4>
<div className="flex flex-row items-center space-x-4">
<button
type="button"
onClick={() => fileInputRef.current.click()}
className="flex flex-row items-center space-x-1 text-black/70 dark:text-white/70 hover:text-black hover:dark:text-white transition duration-200"
> >
<input <div className="flex flex-row items-center justify-between px-3 py-2">
type="file" <h4 className="text-black/70 dark:text-white/70 font-medium text-sm">
onChange={handleChange} Attached files
ref={fileInputRef} </h4>
accept=".pdf,.docx,.txt" <div className="flex flex-row items-center space-x-4">
multiple <button
hidden type="button"
/> onClick={() => fileInputRef.current.click()}
<Plus size={18} /> className="flex flex-row items-center space-x-1 text-black/70 dark:text-white/70 hover:text-black hover:dark:text-white transition duration-200"
<p className="text-xs">Add</p> >
</button> <input
<button type="file"
onClick={() => { onChange={handleChange}
setFiles([]); ref={fileInputRef}
setFileIds([]); accept=".pdf,.docx,.txt"
}} multiple
className="flex flex-row items-center space-x-1 text-black/70 dark:text-white/70 hover:text-black hover:dark:text-white transition duration-200" hidden
> />
<Trash size={14} /> <Plus size={16} />
<p className="text-xs">Clear</p> <p className="text-xs">Add</p>
</button> </button>
</div> <button
</div> onClick={() => {
<div className="h-[0.5px] mx-2 bg-white/10" /> setFiles([]);
<div className="flex flex-col items-center"> setFileIds([]);
{files.map((file, i) => ( }}
<div className="flex flex-row items-center space-x-1 text-black/70 dark:text-white/70 hover:text-black hover:dark:text-white transition duration-200"
key={i} >
className="flex flex-row items-center justify-start w-full space-x-3 p-3" <Trash size={13} />
> <p className="text-xs">Clear</p>
<div className="bg-light-100 dark:bg-dark-100 flex items-center justify-center w-10 h-10 rounded-md"> </button>
<File </div>
size={16}
className="text-black/70 dark:text-white/70"
/>
</div> </div>
<p className="text-black/70 dark:text-white/70 text-sm"> <div className="h-[0.5px] mx-2 bg-white/10" />
{file.fileName.length > 25 <div className="flex flex-col items-center">
? file.fileName.replace(/\.\w+$/, '').substring(0, 25) + {files.map((file, i) => (
'...' + <div
file.fileExtension key={i}
: file.fileName} className="flex flex-row items-center justify-start w-full space-x-3 p-3"
</p> >
</div> <div className="bg-light-100 dark:bg-dark-100 flex items-center justify-center w-9 h-9 rounded-md">
))} <File
</div> size={16}
</div> className="text-black/70 dark:text-white/70"
</PopoverPanel> />
</Transition> </div>
<p className="text-black/70 dark:text-white/70 text-xs">
{file.fileName.length > 25
? file.fileName
.replace(/\.\w+$/, '')
.substring(0, 25) +
'...' +
file.fileExtension
: file.fileName}
</p>
</div>
))}
</div>
</motion.div>
</PopoverPanel>
)}
</AnimatePresence>
</>
)}
</Popover> </Popover>
) : ( ) : (
<button <button

View File

@@ -2,15 +2,11 @@
import { Cpu, Loader2, Search } from 'lucide-react'; import { Cpu, Loader2, Search } from 'lucide-react';
import { cn } from '@/lib/utils'; import { cn } from '@/lib/utils';
import { import { Popover, PopoverButton, PopoverPanel } from '@headlessui/react';
Popover, import { useEffect, useMemo, useState } from 'react';
PopoverButton,
PopoverPanel,
Transition,
} from '@headlessui/react';
import { Fragment, useEffect, useMemo, useState } from 'react';
import { MinimalProvider } from '@/lib/models/types'; import { MinimalProvider } from '@/lib/models/types';
import { useChat } from '@/lib/hooks/useChat'; import { useChat } from '@/lib/hooks/useChat';
import { AnimatePresence, motion } from 'motion/react';
const ModelSelector = () => { const ModelSelector = () => {
const [providers, setProviders] = useState<MinimalProvider[]>([]); const [providers, setProviders] = useState<MinimalProvider[]>([]);
@@ -79,119 +75,127 @@ const ModelSelector = () => {
return ( return (
<Popover className="relative w-full max-w-[15rem] md:max-w-md lg:max-w-lg"> <Popover className="relative w-full max-w-[15rem] md:max-w-md lg:max-w-lg">
<PopoverButton {({ open }) => (
type="button" <>
className="active:border-none hover:bg-light-200 hover:dark:bg-dark-200 p-2 rounded-lg focus:outline-none headless-open:text-black dark:headless-open:text-white text-black/50 dark:text-white/50 active:scale-95 transition duration-200 hover:text-black dark:hover:text-white" <PopoverButton
> type="button"
<Cpu size={16} className="text-sky-500" /> className="active:border-none hover:bg-light-200 hover:dark:bg-dark-200 p-2 rounded-lg focus:outline-none headless-open:text-black dark:headless-open:text-white text-black/50 dark:text-white/50 active:scale-95 transition duration-200 hover:text-black dark:hover:text-white"
</PopoverButton> >
<Transition <Cpu size={16} className="text-sky-500" />
as={Fragment} </PopoverButton>
enter="transition ease-out duration-100" <AnimatePresence>
enterFrom="opacity-0 translate-y-1" {open && (
enterTo="opacity-100 translate-y-0" <PopoverPanel
leave="transition ease-in duration-100" className="absolute z-10 w-[230px] sm:w-[270px] md:w-[300px] right-0"
leaveFrom="opacity-100 translate-y-0" static
leaveTo="opacity-0 translate-y-1" >
> <motion.div
<PopoverPanel className="absolute z-10 w-[230px] sm:w-[270px] md:w-[300px] -right-4"> initial={{ opacity: 0, scale: 0.9 }}
<div className="bg-light-primary dark:bg-dark-primary max-h-[300px] sm:max-w-none border rounded-lg border-light-200 dark:border-dark-200 w-full flex flex-col shadow-lg overflow-hidden"> animate={{ opacity: 1, scale: 1 }}
<div className="p-4 border-b border-light-200 dark:border-dark-200"> exit={{ opacity: 0, scale: 0.9 }}
<div className="relative"> transition={{ duration: 0.1, ease: 'easeOut' }}
<Search className="origin-top-right bg-light-primary dark:bg-dark-primary max-h-[300px] sm:max-w-none border rounded-lg border-light-200 dark:border-dark-200 w-full flex flex-col shadow-lg overflow-hidden"
size={16} >
className="absolute left-3 top-1/2 -translate-y-1/2 text-black/40 dark:text-white/40" <div className="p-2 border-b border-light-200 dark:border-dark-200">
/> <div className="relative">
<input <Search
type="text" size={16}
placeholder="Search models..." className="absolute left-3 top-1/2 -translate-y-1/2 text-black/40 dark:text-white/40"
value={searchQuery} />
onChange={(e) => setSearchQuery(e.target.value)} <input
className="w-full pl-9 pr-3 py-2 bg-light-secondary dark:bg-dark-secondary rounded-lg placeholder:text-sm text-sm text-black dark:text-white placeholder:text-black/40 dark:placeholder:text-white/40 focus:outline-none focus:ring-2 focus:ring-sky-500/20 border border-transparent focus:border-sky-500/30 transition duration-200" type="text"
/> placeholder="Search models..."
</div> value={searchQuery}
</div> onChange={(e) => setSearchQuery(e.target.value)}
className="w-full pl-8 pr-3 py-2 bg-light-secondary dark:bg-dark-secondary rounded-lg placeholder:text-xs placeholder:-translate-y-[1.5px] text-xs text-black dark:text-white placeholder:text-black/40 dark:placeholder:text-white/40 focus:outline-none border border-transparent transition duration-200"
/>
</div>
</div>
<div className="max-h-[320px] overflow-y-auto"> <div className="max-h-[320px] overflow-y-auto">
{isLoading ? ( {isLoading ? (
<div className="flex items-center justify-center py-16"> <div className="flex items-center justify-center py-16">
<Loader2 <Loader2
className="animate-spin text-black/40 dark:text-white/40" className="animate-spin text-black/40 dark:text-white/40"
size={24} size={24}
/> />
</div>
) : filteredProviders.length === 0 ? (
<div className="text-center py-16 px-4 text-black/60 dark:text-white/60 text-sm">
{searchQuery
? 'No models found'
: 'No chat models configured'}
</div>
) : (
<div className="flex flex-col">
{filteredProviders.map((provider, providerIndex) => (
<div key={provider.id}>
<div className="px-4 py-2.5 sticky top-0 bg-light-primary dark:bg-dark-primary border-b border-light-200/50 dark:border-dark-200/50">
<p className="text-xs text-black/50 dark:text-white/50 uppercase tracking-wider">
{provider.name}
</p>
</div> </div>
) : filteredProviders.length === 0 ? (
<div className="flex flex-col px-2 py-2 space-y-0.5"> <div className="text-center py-16 px-4 text-black/60 dark:text-white/60 text-sm">
{provider.chatModels.map((model) => ( {searchQuery
<button ? 'No models found'
key={model.key} : 'No chat models configured'}
onClick={() => </div>
handleModelSelect(provider.id, model.key) ) : (
} <div className="flex flex-col">
type="button" {filteredProviders.map((provider, providerIndex) => (
className={cn( <div key={provider.id}>
'px-3 py-2 flex items-center justify-between text-start duration-200 cursor-pointer transition rounded-lg group', <div className="px-4 py-2.5 sticky top-0 bg-light-primary dark:bg-dark-primary border-b border-light-200/50 dark:border-dark-200/50">
chatModelProvider?.providerId === provider.id && <p className="text-xs text-black/50 dark:text-white/50 uppercase tracking-wider">
chatModelProvider?.key === model.key {provider.name}
? 'bg-light-secondary dark:bg-dark-secondary'
: 'hover:bg-light-secondary dark:hover:bg-dark-secondary',
)}
>
<div className="flex items-center space-x-2.5 min-w-0 flex-1">
<Cpu
size={15}
className={cn(
'shrink-0',
chatModelProvider?.providerId ===
provider.id &&
chatModelProvider?.key === model.key
? 'text-sky-500'
: 'text-black/50 dark:text-white/50 group-hover:text-black/70 group-hover:dark:text-white/70',
)}
/>
<p
className={cn(
'text-sm truncate',
chatModelProvider?.providerId ===
provider.id &&
chatModelProvider?.key === model.key
? 'text-sky-500 font-medium'
: 'text-black/70 dark:text-white/70 group-hover:text-black dark:group-hover:text-white',
)}
>
{model.name}
</p> </p>
</div> </div>
</button>
<div className="flex flex-col px-2 py-2 space-y-0.5">
{provider.chatModels.map((model) => (
<button
key={model.key}
onClick={() =>
handleModelSelect(provider.id, model.key)
}
type="button"
className={cn(
'px-3 py-2 flex items-center justify-between text-start duration-200 cursor-pointer transition rounded-lg group',
chatModelProvider?.providerId ===
provider.id &&
chatModelProvider?.key === model.key
? 'bg-light-secondary dark:bg-dark-secondary'
: 'hover:bg-light-secondary dark:hover:bg-dark-secondary',
)}
>
<div className="flex items-center space-x-2.5 min-w-0 flex-1">
<Cpu
size={15}
className={cn(
'shrink-0',
chatModelProvider?.providerId ===
provider.id &&
chatModelProvider?.key === model.key
? 'text-sky-500'
: 'text-black/50 dark:text-white/50 group-hover:text-black/70 group-hover:dark:text-white/70',
)}
/>
<p
className={cn(
'text-xs truncate',
chatModelProvider?.providerId ===
provider.id &&
chatModelProvider?.key === model.key
? 'text-sky-500 font-medium'
: 'text-black/70 dark:text-white/70 group-hover:text-black dark:group-hover:text-white',
)}
>
{model.name}
</p>
</div>
</button>
))}
</div>
{providerIndex < filteredProviders.length - 1 && (
<div className="h-px bg-light-200 dark:bg-dark-200" />
)}
</div>
))} ))}
</div> </div>
)}
{providerIndex < filteredProviders.length - 1 && ( </div>
<div className="h-px bg-light-200 dark:bg-dark-200" /> </motion.div>
)} </PopoverPanel>
</div> )}
))} </AnimatePresence>
</div> </>
)} )}
</div>
</div>
</PopoverPanel>
</Transition>
</Popover> </Popover>
); );
}; };

View File

@@ -1,43 +0,0 @@
import { cn } from '@/lib/utils';
import { Switch } from '@headlessui/react';
const CopilotToggle = ({
copilotEnabled,
setCopilotEnabled,
}: {
copilotEnabled: boolean;
setCopilotEnabled: (enabled: boolean) => void;
}) => {
return (
<div className="group flex flex-row items-center space-x-1 active:scale-95 duration-200 transition cursor-pointer">
<Switch
checked={copilotEnabled}
onChange={setCopilotEnabled}
className="bg-light-secondary dark:bg-dark-secondary border border-light-200/70 dark:border-dark-200 relative inline-flex h-5 w-10 sm:h-6 sm:w-11 items-center rounded-full"
>
<span className="sr-only">Copilot</span>
<span
className={cn(
copilotEnabled
? 'translate-x-6 bg-[#24A0ED]'
: 'translate-x-1 bg-black/50 dark:bg-white/50',
'inline-block h-3 w-3 sm:h-4 sm:w-4 transform rounded-full transition-all duration-200',
)}
/>
</Switch>
<p
onClick={() => setCopilotEnabled(!copilotEnabled)}
className={cn(
'text-xs font-medium transition-colors duration-150 ease-in-out',
copilotEnabled
? 'text-[#24A0ED]'
: 'text-black/50 dark:text-white/50 group-hover:text-black dark:group-hover:text-white',
)}
>
Copilot
</p>
</div>
);
};
export default CopilotToggle;

View File

@@ -1,123 +0,0 @@
import {
BadgePercent,
ChevronDown,
Globe,
Pencil,
ScanEye,
SwatchBook,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import {
Popover,
PopoverButton,
PopoverPanel,
Transition,
} from '@headlessui/react';
import { SiReddit, SiYoutube } from '@icons-pack/react-simple-icons';
import { Fragment } from 'react';
import { useChat } from '@/lib/hooks/useChat';
const focusModes = [
{
key: 'webSearch',
title: 'All',
description: 'Searches across all of the internet',
icon: <Globe size={16} />,
},
{
key: 'academicSearch',
title: 'Academic',
description: 'Search in published academic papers',
icon: <SwatchBook size={16} />,
},
{
key: 'writingAssistant',
title: 'Writing',
description: 'Chat without searching the web',
icon: <Pencil size={16} />,
},
{
key: 'wolframAlphaSearch',
title: 'Wolfram Alpha',
description: 'Computational knowledge engine',
icon: <BadgePercent size={16} />,
},
{
key: 'youtubeSearch',
title: 'Youtube',
description: 'Search and watch videos',
icon: <SiYoutube className="h-[16px] w-auto mr-0.5" />,
},
{
key: 'redditSearch',
title: 'Reddit',
description: 'Search for discussions and opinions',
icon: <SiReddit className="h-[16px] w-auto mr-0.5" />,
},
];
const Focus = () => {
const { focusMode, setFocusMode } = useChat();
return (
<Popover className="relative w-full max-w-[15rem] md:max-w-md lg:max-w-lg">
<PopoverButton
type="button"
className="active:border-none hover:bg-light-200 hover:dark:bg-dark-200 p-2 rounded-lg focus:outline-none headless-open:text-black dark:headless-open:text-white text-black/50 dark:text-white/50 active:scale-95 transition duration-200 hover:text-black dark:hover:text-white"
>
{focusMode !== 'webSearch' ? (
<div className="flex flex-row items-center space-x-1">
{focusModes.find((mode) => mode.key === focusMode)?.icon}
</div>
) : (
<div className="flex flex-row items-center space-x-1">
<Globe size={16} />
</div>
)}
</PopoverButton>
<Transition
as={Fragment}
enter="transition ease-out duration-150"
enterFrom="opacity-0 translate-y-1"
enterTo="opacity-100 translate-y-0"
leave="transition ease-in duration-150"
leaveFrom="opacity-100 translate-y-0"
leaveTo="opacity-0 translate-y-1"
>
<PopoverPanel className="absolute z-10 w-64 md:w-[500px] -right-4">
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2 bg-light-primary dark:bg-dark-primary border rounded-lg border-light-200 dark:border-dark-200 w-full p-4 max-h-[200px] md:max-h-none overflow-y-auto">
{focusModes.map((mode, i) => (
<PopoverButton
onClick={() => setFocusMode(mode.key)}
key={i}
className={cn(
'p-2 rounded-lg flex flex-col items-start justify-start text-start space-y-2 duration-200 cursor-pointer transition focus:outline-none',
focusMode === mode.key
? 'bg-light-secondary dark:bg-dark-secondary'
: 'hover:bg-light-secondary dark:hover:bg-dark-secondary',
)}
>
<div
className={cn(
'flex flex-row items-center space-x-1',
focusMode === mode.key
? 'text-[#24A0ED]'
: 'text-black dark:text-white',
)}
>
{mode.icon}
<p className="text-sm font-medium">{mode.title}</p>
</div>
<p className="text-black/70 dark:text-white/70 text-xs">
{mode.description}
</p>
</PopoverButton>
))}
</div>
</PopoverPanel>
</Transition>
</Popover>
);
};
export default Focus;

View File

@@ -8,6 +8,7 @@ import {
} from '@headlessui/react'; } from '@headlessui/react';
import { Fragment } from 'react'; import { Fragment } from 'react';
import { useChat } from '@/lib/hooks/useChat'; import { useChat } from '@/lib/hooks/useChat';
import { AnimatePresence, motion } from 'motion/react';
const OptimizationModes = [ const OptimizationModes = [
{ {
@@ -60,40 +61,50 @@ const Optimization = () => {
/> />
</div> </div>
</PopoverButton> </PopoverButton>
<Transition <AnimatePresence>
as={Fragment} {open && (
enter="transition ease-out duration-150" <PopoverPanel
enterFrom="opacity-0 translate-y-1" className="absolute z-10 w-64 md:w-[250px] left-0"
enterTo="opacity-100 translate-y-0" static
leave="transition ease-in duration-150" >
leaveFrom="opacity-100 translate-y-0" <motion.div
leaveTo="opacity-0 translate-y-1" initial={{ opacity: 0, scale: 0.9 }}
> animate={{ opacity: 1, scale: 1 }}
<PopoverPanel className="absolute z-10 w-64 md:w-[250px] left-0"> exit={{ opacity: 0, scale: 0.9 }}
<div className="flex flex-col gap-2 bg-light-primary dark:bg-dark-primary border rounded-lg border-light-200 dark:border-dark-200 w-full p-4 max-h-[200px] md:max-h-none overflow-y-auto"> transition={{ duration: 0.1, ease: 'easeOut' }}
{OptimizationModes.map((mode, i) => ( className="origin-top-left flex flex-col space-y-2 bg-light-primary dark:bg-dark-primary border rounded-lg border-light-200 dark:border-dark-200 w-full p-2 max-h-[200px] md:max-h-none overflow-y-auto"
<PopoverButton >
onClick={() => setOptimizationMode(mode.key)} {OptimizationModes.map((mode, i) => (
key={i} <PopoverButton
className={cn( onClick={() => setOptimizationMode(mode.key)}
'p-2 rounded-lg flex flex-col items-start justify-start text-start space-y-1 duration-200 cursor-pointer transition focus:outline-none', key={i}
optimizationMode === mode.key className={cn(
? 'bg-light-secondary dark:bg-dark-secondary' 'p-2 rounded-lg flex flex-col items-start justify-start text-start space-y-1 duration-200 cursor-pointer transition focus:outline-none',
: 'hover:bg-light-secondary dark:hover:bg-dark-secondary', optimizationMode === mode.key
)} ? 'bg-light-secondary dark:bg-dark-secondary'
> : 'hover:bg-light-secondary dark:hover:bg-dark-secondary',
<div className="flex flex-row items-center space-x-1 text-black dark:text-white"> )}
{mode.icon} >
<p className="text-sm font-medium">{mode.title}</p> <div className="flex flex-row justify-between w-full text-black dark:text-white">
</div> <div className="flex flex-row space-x-1">
<p className="text-black/70 dark:text-white/70 text-xs"> {mode.icon}
{mode.description} <p className="text-xs font-medium">{mode.title}</p>
</p> </div>
</PopoverButton> {mode.key === 'quality' && (
))} <span className="bg-sky-500/70 dark:bg-sky-500/40 border border-sky-600 px-1 rounded-full text-[10px] text-white">
</div> Beta
</PopoverPanel> </span>
</Transition> )}
</div>
<p className="text-black/70 dark:text-white/70 text-xs">
{mode.description}
</p>
</PopoverButton>
))}
</motion.div>
</PopoverPanel>
)}
</AnimatePresence>
</> </>
)} )}
</Popover> </Popover>

View File

@@ -0,0 +1,93 @@
import { useChat } from '@/lib/hooks/useChat';
import {
Popover,
PopoverButton,
PopoverPanel,
Switch,
} from '@headlessui/react';
import {
GlobeIcon,
GraduationCapIcon,
NetworkIcon,
} from '@phosphor-icons/react';
import { AnimatePresence, motion } from 'motion/react';
const sourcesList = [
{
name: 'Web',
key: 'web',
icon: <GlobeIcon className="h-[16px] w-auto" />,
},
{
name: 'Academic',
key: 'academic',
icon: <GraduationCapIcon className="h-[16px] w-auto" />,
},
{
name: 'Social',
key: 'discussions',
icon: <NetworkIcon className="h-[16px] w-auto" />,
},
];
const Sources = () => {
const { sources, setSources } = useChat();
return (
<Popover className="relative">
{({ open }) => (
<>
<PopoverButton className="flex items-center justify-center active:border-none hover:bg-light-200 hover:dark:bg-dark-200 p-2 rounded-lg focus:outline-none text-black/50 dark:text-white/50 active:scale-95 transition duration-200 hover:text-black dark:hover:text-white">
<GlobeIcon className="h-[18px] w-auto" />
</PopoverButton>
<AnimatePresence>
{open && (
<PopoverPanel
static
className="absolute z-10 w-64 md:w-[225px] right-0"
>
<motion.div
initial={{ opacity: 0, scale: 0.9 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.9 }}
transition={{ duration: 0.1, ease: 'easeOut' }}
className="origin-top-right flex flex-col bg-light-primary dark:bg-dark-primary border rounded-lg border-light-200 dark:border-dark-200 w-full p-1 max-h-[200px] md:max-h-none overflow-y-auto shadow-lg"
>
{sourcesList.map((source, i) => (
<div
key={i}
className="flex flex-row justify-between hover:bg-light-100 hover:dark:bg-dark-100 rounded-md py-3 px-2 cursor-pointer"
onClick={() => {
if (!sources.includes(source.key)) {
setSources([...sources, source.key]);
} else {
setSources(sources.filter((s) => s !== source.key));
}
}}
>
<div className="flex flex-row space-x-1.5 text-black/80 dark:text-white/80">
{source.icon}
<p className="text-xs">{source.name}</p>
</div>
<Switch
checked={sources.includes(source.key)}
className="group relative flex h-4 w-7 shrink-0 cursor-pointer rounded-full bg-light-200 dark:bg-white/10 p-0.5 duration-200 ease-in-out focus:outline-none transition-colors disabled:opacity-60 disabled:cursor-not-allowed data-[checked]:bg-sky-500 dark:data-[checked]:bg-sky-500"
>
<span
aria-hidden="true"
className="pointer-events-none inline-block size-3 translate-x-[1px] group-data-[checked]:translate-x-3 rounded-full bg-white shadow-lg ring-0 transition duration-200 ease-in-out"
/>
</Switch>
</div>
))}
</motion.div>
</PopoverPanel>
)}
</AnimatePresence>
</>
)}
</Popover>
);
};
export default Sources;

View File

@@ -0,0 +1,102 @@
import type { CSSProperties } from 'react';
const darkTheme = {
'hljs-comment': {
color: '#8b949e',
},
'hljs-quote': {
color: '#8b949e',
},
'hljs-variable': {
color: '#ff7b72',
},
'hljs-template-variable': {
color: '#ff7b72',
},
'hljs-tag': {
color: '#ff7b72',
},
'hljs-name': {
color: '#ff7b72',
},
'hljs-selector-id': {
color: '#ff7b72',
},
'hljs-selector-class': {
color: '#ff7b72',
},
'hljs-regexp': {
color: '#ff7b72',
},
'hljs-deletion': {
color: '#ff7b72',
},
'hljs-number': {
color: '#f2cc60',
},
'hljs-built_in': {
color: '#f2cc60',
},
'hljs-builtin-name': {
color: '#f2cc60',
},
'hljs-literal': {
color: '#f2cc60',
},
'hljs-type': {
color: '#f2cc60',
},
'hljs-params': {
color: '#f2cc60',
},
'hljs-meta': {
color: '#f2cc60',
},
'hljs-link': {
color: '#f2cc60',
},
'hljs-attribute': {
color: '#58a6ff',
},
'hljs-string': {
color: '#7ee787',
},
'hljs-symbol': {
color: '#7ee787',
},
'hljs-bullet': {
color: '#7ee787',
},
'hljs-addition': {
color: '#7ee787',
},
'hljs-title': {
color: '#79c0ff',
},
'hljs-section': {
color: '#79c0ff',
},
'hljs-keyword': {
color: '#c297ff',
},
'hljs-selector-tag': {
color: '#c297ff',
},
hljs: {
display: 'block',
overflowX: 'auto',
background: '#0d1117',
color: '#c9d1d9',
padding: '0.75em',
border: '1px solid #21262d',
borderRadius: '10px',
},
'hljs-emphasis': {
fontStyle: 'italic',
},
'hljs-strong': {
fontWeight: 'bold',
},
} satisfies Record<string, CSSProperties>;
export default darkTheme;

View File

@@ -0,0 +1,102 @@
import type { CSSProperties } from 'react';
const lightTheme = {
'hljs-comment': {
color: '#6e7781',
},
'hljs-quote': {
color: '#6e7781',
},
'hljs-variable': {
color: '#d73a49',
},
'hljs-template-variable': {
color: '#d73a49',
},
'hljs-tag': {
color: '#d73a49',
},
'hljs-name': {
color: '#d73a49',
},
'hljs-selector-id': {
color: '#d73a49',
},
'hljs-selector-class': {
color: '#d73a49',
},
'hljs-regexp': {
color: '#d73a49',
},
'hljs-deletion': {
color: '#d73a49',
},
'hljs-number': {
color: '#b08800',
},
'hljs-built_in': {
color: '#b08800',
},
'hljs-builtin-name': {
color: '#b08800',
},
'hljs-literal': {
color: '#b08800',
},
'hljs-type': {
color: '#b08800',
},
'hljs-params': {
color: '#b08800',
},
'hljs-meta': {
color: '#b08800',
},
'hljs-link': {
color: '#b08800',
},
'hljs-attribute': {
color: '#0a64ae',
},
'hljs-string': {
color: '#22863a',
},
'hljs-symbol': {
color: '#22863a',
},
'hljs-bullet': {
color: '#22863a',
},
'hljs-addition': {
color: '#22863a',
},
'hljs-title': {
color: '#005cc5',
},
'hljs-section': {
color: '#005cc5',
},
'hljs-keyword': {
color: '#6f42c1',
},
'hljs-selector-tag': {
color: '#6f42c1',
},
hljs: {
display: 'block',
overflowX: 'auto',
background: '#ffffff',
color: '#24292f',
padding: '0.75em',
border: '1px solid #e8edf1',
borderRadius: '10px',
},
'hljs-emphasis': {
fontStyle: 'italic',
},
'hljs-strong': {
fontWeight: 'bold',
},
} satisfies Record<string, CSSProperties>;
export default lightTheme;

View File

@@ -0,0 +1,64 @@
'use client';
import { CheckIcon, CopyIcon } from '@phosphor-icons/react';
import React, { useEffect, useMemo, useState } from 'react';
import { useTheme } from 'next-themes';
import SyntaxHighlighter from 'react-syntax-highlighter';
import darkTheme from './CodeBlockDarkTheme';
import lightTheme from './CodeBlockLightTheme';
const CodeBlock = ({
language,
children,
}: {
language: string;
children: React.ReactNode;
}) => {
const { resolvedTheme } = useTheme();
const [mounted, setMounted] = useState(false);
const [copied, setCopied] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
const syntaxTheme = useMemo(() => {
if (!mounted) return lightTheme;
return resolvedTheme === 'dark' ? darkTheme : lightTheme;
}, [mounted, resolvedTheme]);
return (
<div className="relative">
<button
className="absolute top-2 right-2 p-1"
onClick={() => {
navigator.clipboard.writeText(children as string);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}}
>
{copied ? (
<CheckIcon
size={16}
className="absolute top-2 right-2 text-black/70 dark:text-white/70"
/>
) : (
<CopyIcon
size={16}
className="absolute top-2 right-2 transition duration-200 text-black/70 dark:text-white/70 hover:text-gray-800/70 hover:dark:text-gray-300/70"
/>
)}
</button>
<SyntaxHighlighter
language={language}
style={syntaxTheme}
showInlineLineNumbers
>
{children as string}
</SyntaxHighlighter>
</div>
);
};
export default CodeBlock;

View File

@@ -310,7 +310,7 @@ const SettingsSwitch = ({
checked={isChecked} checked={isChecked}
onChange={handleSave} onChange={handleSave}
disabled={loading} disabled={loading}
className="group relative flex h-6 w-12 shrink-0 cursor-pointer rounded-full bg-white/10 p-1 duration-200 ease-in-out focus:outline-none transition-colors disabled:opacity-60 disabled:cursor-not-allowed data-[checked]:bg-sky-500" className="group relative flex h-6 w-12 shrink-0 cursor-pointer rounded-full bg-light-200 dark:bg-white/10 p-1 duration-200 ease-in-out focus:outline-none transition-colors disabled:opacity-60 disabled:cursor-not-allowed data-[checked]:bg-sky-500 dark:data-[checked]:bg-sky-500"
> >
<span <span
aria-hidden="true" aria-hidden="true"

View File

@@ -1,5 +1,3 @@
import { Message } from '@/components/ChatWindow';
export const getSuggestions = async (chatHistory: [string, string][]) => { export const getSuggestions = async (chatHistory: [string, string][]) => {
const chatTurns = chatHistory.map(([role, content]) => { const chatTurns = chatHistory.map(([role, content]) => {
if (role === 'human') { if (role === 'human') {

View File

@@ -4,9 +4,53 @@ import { classify } from './classifier';
import Researcher from './researcher'; import Researcher from './researcher';
import { getWriterPrompt } from '@/lib/prompts/search/writer'; import { getWriterPrompt } from '@/lib/prompts/search/writer';
import { WidgetExecutor } from './widgets'; import { WidgetExecutor } from './widgets';
import db from '@/lib/db';
import { chats, messages } from '@/lib/db/schema';
import { and, eq, gt } from 'drizzle-orm';
import { TextBlock } from '@/lib/types';
class SearchAgent { class SearchAgent {
async searchAsync(session: SessionManager, input: SearchAgentInput) { async searchAsync(session: SessionManager, input: SearchAgentInput) {
const exists = await db.query.messages.findFirst({
where: and(
eq(messages.chatId, input.chatId),
eq(messages.messageId, input.messageId),
),
});
if (!exists) {
await db.insert(messages).values({
chatId: input.chatId,
messageId: input.messageId,
backendId: session.id,
query: input.followUp,
createdAt: new Date().toISOString(),
status: 'answering',
responseBlocks: [],
});
} else {
await db
.delete(messages)
.where(
and(eq(messages.chatId, input.chatId), gt(messages.id, exists.id)),
)
.execute();
await db
.update(messages)
.set({
status: 'answering',
backendId: session.id,
responseBlocks: [],
})
.where(
and(
eq(messages.chatId, input.chatId),
eq(messages.messageId, input.messageId),
),
)
.execute();
}
const classification = await classify({ const classification = await classify({
chatHistory: input.chatHistory, chatHistory: input.chatHistory,
enabledSources: input.config.sources, enabledSources: input.config.sources,
@@ -70,7 +114,11 @@ class SearchAgent {
const finalContextWithWidgets = `<search_results note="These are the search results and assistant can cite these">\n${finalContext}\n</search_results>\n<widgets_result noteForAssistant="Its output is already showed to the user, assistant can use this information to answer the query but do not CITE this as a souce">\n${widgetContext}\n</widgets_result>`; const finalContextWithWidgets = `<search_results note="These are the search results and assistant can cite these">\n${finalContext}\n</search_results>\n<widgets_result noteForAssistant="Its output is already showed to the user, assistant can use this information to answer the query but do not CITE this as a souce">\n${widgetContext}\n</widgets_result>`;
const writerPrompt = getWriterPrompt(finalContextWithWidgets); const writerPrompt = getWriterPrompt(
finalContextWithWidgets,
input.config.systemInstructions,
input.config.mode,
);
const answerStream = input.config.llm.streamText({ const answerStream = input.config.llm.streamText({
messages: [ messages: [
{ {
@@ -85,18 +133,53 @@ class SearchAgent {
], ],
}); });
let accumulatedText = ''; let responseBlockId = '';
for await (const chunk of answerStream) { for await (const chunk of answerStream) {
accumulatedText += chunk.contentChunk; if (!responseBlockId) {
const block: TextBlock = {
id: crypto.randomUUID(),
type: 'text',
data: chunk.contentChunk,
};
session.emit('data', { session.emitBlock(block);
type: 'response',
data: chunk.contentChunk, responseBlockId = block.id;
}); } else {
const block = session.getBlock(responseBlockId) as TextBlock | null;
if (!block) {
continue;
}
block.data += chunk.contentChunk;
session.updateBlock(block.id, [
{
op: 'replace',
path: '/data',
value: block.data,
},
]);
}
} }
session.emit('end', {}); session.emit('end', {});
await db
.update(messages)
.set({
status: 'completed',
responseBlocks: session.getAllBlocks(),
})
.where(
and(
eq(messages.chatId, input.chatId),
eq(messages.messageId, input.messageId),
),
)
.execute();
} }
} }

View File

@@ -0,0 +1,129 @@
import z from 'zod';
import { ResearchAction } from '../../types';
import { Chunk, SearchResultsResearchBlock } from '@/lib/types';
import { searchSearxng } from '@/lib/searxng';
const schema = z.object({
queries: z.array(z.string()).describe('List of academic search queries'),
});
const academicSearchDescription = `
Use this tool to perform academic searches for scholarly articles, papers, and research studies relevant to the user's query. Provide a list of concise search queries that will help gather comprehensive academic information on the topic at hand.
You can provide up to 3 queries at a time. Make sure the queries are specific and relevant to the user's needs.
For example, if the user is interested in recent advancements in renewable energy, your queries could be:
1. "Recent advancements in renewable energy 2024"
2. "Cutting-edge research on solar power technologies"
3. "Innovations in wind energy systems"
If this tool is present and no other tools are more relevant, you MUST use this tool to get the needed academic information.
`;
const academicSearchAction: ResearchAction<typeof schema> = {
name: 'academic_search',
schema: schema,
getDescription: () => academicSearchDescription,
getToolDescription: () =>
"Use this tool to perform academic searches for scholarly articles, papers, and research studies relevant to the user's query. Provide a list of concise search queries that will help gather comprehensive academic information on the topic at hand.",
enabled: (config) =>
config.sources.includes('academic') &&
config.classification.classification.skipSearch === false &&
config.classification.classification.academicSearch === true,
execute: async (input, additionalConfig) => {
input.queries = input.queries.slice(0, 3);
const researchBlock = additionalConfig.session.getBlock(
additionalConfig.researchBlockId,
);
if (researchBlock && researchBlock.type === 'research') {
researchBlock.data.subSteps.push({
type: 'searching',
id: crypto.randomUUID(),
searching: input.queries,
});
additionalConfig.session.updateBlock(additionalConfig.researchBlockId, [
{
op: 'replace',
path: '/data/subSteps',
value: researchBlock.data.subSteps,
},
]);
}
const searchResultsBlockId = crypto.randomUUID();
let searchResultsEmitted = false;
let results: Chunk[] = [];
const search = async (q: string) => {
const res = await searchSearxng(q, {
engines: ['arxiv', 'google scholar', 'pubmed'],
});
const resultChunks: Chunk[] = res.results.map((r) => ({
content: r.content || r.title,
metadata: {
title: r.title,
url: r.url,
},
}));
results.push(...resultChunks);
if (
!searchResultsEmitted &&
researchBlock &&
researchBlock.type === 'research'
) {
searchResultsEmitted = true;
researchBlock.data.subSteps.push({
id: searchResultsBlockId,
type: 'search_results',
reading: resultChunks,
});
additionalConfig.session.updateBlock(additionalConfig.researchBlockId, [
{
op: 'replace',
path: '/data/subSteps',
value: researchBlock.data.subSteps,
},
]);
} else if (
searchResultsEmitted &&
researchBlock &&
researchBlock.type === 'research'
) {
const subStepIndex = researchBlock.data.subSteps.findIndex(
(step) => step.id === searchResultsBlockId,
);
const subStep = researchBlock.data.subSteps[
subStepIndex
] as SearchResultsResearchBlock;
subStep.reading.push(...resultChunks);
additionalConfig.session.updateBlock(additionalConfig.researchBlockId, [
{
op: 'replace',
path: '/data/subSteps',
value: researchBlock.data.subSteps,
},
]);
}
};
await Promise.all(input.queries.map(search));
return {
type: 'search_results',
results,
};
},
};
export default academicSearchAction;

View File

@@ -11,7 +11,7 @@ const doneAction: ResearchAction<any> = {
name: 'done', name: 'done',
schema: z.object({}), schema: z.object({}),
getToolDescription: () => getToolDescription: () =>
'Only call this after 0_reasoning AND after any other needed tool calls when you truly have enough to answer. Do not call if information is still missing.', 'Only call this after __reasoning_preamble AND after any other needed tool calls when you truly have enough to answer. Do not call if information is still missing.',
getDescription: () => actionDescription, getDescription: () => actionDescription,
enabled: (_) => true, enabled: (_) => true,
execute: async (params, additionalConfig) => { execute: async (params, additionalConfig) => {

View File

@@ -1,7 +1,9 @@
import academicSearchAction from './academicSearch';
import doneAction from './done'; import doneAction from './done';
import planAction from './plan'; import planAction from './plan';
import ActionRegistry from './registry'; import ActionRegistry from './registry';
import scrapeURLAction from './scrapeURL'; import scrapeURLAction from './scrapeURL';
import socialSearchAction from './socialSearch';
import uploadsSearchAction from './uploadsSearch'; import uploadsSearchAction from './uploadsSearch';
import webSearchAction from './webSearch'; import webSearchAction from './webSearch';
@@ -10,5 +12,7 @@ ActionRegistry.register(doneAction);
ActionRegistry.register(planAction); ActionRegistry.register(planAction);
ActionRegistry.register(scrapeURLAction); ActionRegistry.register(scrapeURLAction);
ActionRegistry.register(uploadsSearchAction); ActionRegistry.register(uploadsSearchAction);
ActionRegistry.register(academicSearchAction);
ActionRegistry.register(socialSearchAction);
export { ActionRegistry }; export { ActionRegistry };

View File

@@ -23,7 +23,7 @@ YOU CAN NEVER CALL ANY OTHER TOOL BEFORE CALLING THIS ONE FIRST, IF YOU DO, THAT
`; `;
const planAction: ResearchAction<typeof schema> = { const planAction: ResearchAction<typeof schema> = {
name: '0_reasoning', name: '__reasoning_preamble',
schema: schema, schema: schema,
getToolDescription: () => getToolDescription: () =>
'Use this FIRST on every turn to state your plan in natural language before any other action. Keep it short, action-focused, and tailored to the current query.', 'Use this FIRST on every turn to state your plan in natural language before any other action. Keep it short, action-focused, and tailored to the current query.',

View File

@@ -5,6 +5,7 @@ import {
ClassifierOutput, ClassifierOutput,
ResearchAction, ResearchAction,
SearchAgentConfig, SearchAgentConfig,
SearchSources,
} from '../../types'; } from '../../types';
class ActionRegistry { class ActionRegistry {
@@ -22,6 +23,7 @@ class ActionRegistry {
classification: ClassifierOutput; classification: ClassifierOutput;
fileIds: string[]; fileIds: string[];
mode: SearchAgentConfig['mode']; mode: SearchAgentConfig['mode'];
sources: SearchSources[];
}): ResearchAction[] { }): ResearchAction[] {
return Array.from( return Array.from(
this.actions.values().filter((action) => action.enabled(config)), this.actions.values().filter((action) => action.enabled(config)),
@@ -32,6 +34,7 @@ class ActionRegistry {
classification: ClassifierOutput; classification: ClassifierOutput;
fileIds: string[]; fileIds: string[];
mode: SearchAgentConfig['mode']; mode: SearchAgentConfig['mode'];
sources: SearchSources[];
}): Tool[] { }): Tool[] {
const availableActions = this.getAvailableActions(config); const availableActions = this.getAvailableActions(config);
@@ -46,6 +49,7 @@ class ActionRegistry {
classification: ClassifierOutput; classification: ClassifierOutput;
fileIds: string[]; fileIds: string[];
mode: SearchAgentConfig['mode']; mode: SearchAgentConfig['mode'];
sources: SearchSources[];
}): string { }): string {
const availableActions = this.getAvailableActions(config); const availableActions = this.getAvailableActions(config);

View File

@@ -0,0 +1,129 @@
import z from 'zod';
import { ResearchAction } from '../../types';
import { Chunk, SearchResultsResearchBlock } from '@/lib/types';
import { searchSearxng } from '@/lib/searxng';
const schema = z.object({
queries: z.array(z.string()).describe('List of social search queries'),
});
const socialSearchDescription = `
Use this tool to perform social media searches for relevant posts, discussions, and trends related to the user's query. Provide a list of concise search queries that will help gather comprehensive social media information on the topic at hand.
You can provide up to 3 queries at a time. Make sure the queries are specific and relevant to the user's needs.
For example, if the user is interested in public opinion on electric vehicles, your queries could be:
1. "Electric vehicles public opinion 2024"
2. "Social media discussions on EV adoption"
3. "Trends in electric vehicle usage"
If this tool is present and no other tools are more relevant, you MUST use this tool to get the needed social media information.
`;
const socialSearchAction: ResearchAction<typeof schema> = {
name: 'social_search',
schema: schema,
getDescription: () => socialSearchDescription,
getToolDescription: () =>
"Use this tool to perform social media searches for relevant posts, discussions, and trends related to the user's query. Provide a list of concise search queries that will help gather comprehensive social media information on the topic at hand.",
enabled: (config) =>
config.sources.includes('discussions') &&
config.classification.classification.skipSearch === false &&
config.classification.classification.discussionSearch === true,
execute: async (input, additionalConfig) => {
input.queries = input.queries.slice(0, 3);
const researchBlock = additionalConfig.session.getBlock(
additionalConfig.researchBlockId,
);
if (researchBlock && researchBlock.type === 'research') {
researchBlock.data.subSteps.push({
type: 'searching',
id: crypto.randomUUID(),
searching: input.queries,
});
additionalConfig.session.updateBlock(additionalConfig.researchBlockId, [
{
op: 'replace',
path: '/data/subSteps',
value: researchBlock.data.subSteps,
},
]);
}
const searchResultsBlockId = crypto.randomUUID();
let searchResultsEmitted = false;
let results: Chunk[] = [];
const search = async (q: string) => {
const res = await searchSearxng(q, {
engines: ['reddit'],
});
const resultChunks: Chunk[] = res.results.map((r) => ({
content: r.content || r.title,
metadata: {
title: r.title,
url: r.url,
},
}));
results.push(...resultChunks);
if (
!searchResultsEmitted &&
researchBlock &&
researchBlock.type === 'research'
) {
searchResultsEmitted = true;
researchBlock.data.subSteps.push({
id: searchResultsBlockId,
type: 'search_results',
reading: resultChunks,
});
additionalConfig.session.updateBlock(additionalConfig.researchBlockId, [
{
op: 'replace',
path: '/data/subSteps',
value: researchBlock.data.subSteps,
},
]);
} else if (
searchResultsEmitted &&
researchBlock &&
researchBlock.type === 'research'
) {
const subStepIndex = researchBlock.data.subSteps.findIndex(
(step) => step.id === searchResultsBlockId,
);
const subStep = researchBlock.data.subSteps[
subStepIndex
] as SearchResultsResearchBlock;
subStep.reading.push(...resultChunks);
additionalConfig.session.updateBlock(additionalConfig.researchBlockId, [
{
op: 'replace',
path: '/data/subSteps',
value: researchBlock.data.subSteps,
},
]);
}
};
await Promise.all(input.queries.map(search));
return {
type: 'search_results',
results,
};
},
};
export default socialSearchAction;

View File

@@ -32,11 +32,11 @@ Start initially with broader queries to get an overview, then narrow down with m
Your queries shouldn't be sentences but rather keywords that are SEO friendly and can be used to search the web for information. Your queries shouldn't be sentences but rather keywords that are SEO friendly and can be used to search the web for information.
For example if the user is asking about Tesla, your actions should be like: For example if the user is asking about Tesla, your actions should be like:
1. 0_reasoning "The user is asking about Tesla. I will start with broader queries to get an overview of Tesla, then narrow down with more specific queries based on the results I receive." then 1. __reasoning_preamble "The user is asking about Tesla. I will start with broader queries to get an overview of Tesla, then narrow down with more specific queries based on the results I receive." then
2. web_search ["Tesla", "Tesla latest news", "Tesla stock price"] then 2. web_search ["Tesla", "Tesla latest news", "Tesla stock price"] then
3. 0_reasoning "Based on the previous search results, I will now narrow down my queries to focus on Tesla's recent developments and stock performance." then 3. __reasoning_preamble "Based on the previous search results, I will now narrow down my queries to focus on Tesla's recent developments and stock performance." then
4. web_search ["Tesla Q2 2025 earnings", "Tesla new model 2025", "Tesla stock analysis"] then done. 4. web_search ["Tesla Q2 2025 earnings", "Tesla new model 2025", "Tesla stock analysis"] then done.
5. 0_reasoning "I have gathered enough information to provide a comprehensive answer." 5. __reasoning_preamble "I have gathered enough information to provide a comprehensive answer."
6. done. 6. done.
You can search for 3 queries in one go, make sure to utilize all 3 queries to maximize the information you can gather. If a question is simple, then split your queries to cover different aspects or related topics to get a comprehensive understanding. You can search for 3 queries in one go, make sure to utilize all 3 queries to maximize the information you can gather. If a question is simple, then split your queries to cover different aspects or related topics to get a comprehensive understanding.
@@ -82,6 +82,7 @@ const webSearchAction: ResearchAction<typeof actionSchema> = {
return prompt; return prompt;
}, },
enabled: (config) => enabled: (config) =>
config.sources.includes('web') &&
config.classification.classification.skipSearch === false, config.classification.classification.skipSearch === false,
execute: async (input, additionalConfig) => { execute: async (input, additionalConfig) => {
input.queries = input.queries.slice(0, 3); input.queries = input.queries.slice(0, 3);

View File

@@ -23,6 +23,7 @@ class Researcher {
classification: input.classification, classification: input.classification,
fileIds: input.config.fileIds, fileIds: input.config.fileIds,
mode: input.config.mode, mode: input.config.mode,
sources: input.config.sources,
}); });
const availableActionsDescription = const availableActionsDescription =
@@ -30,6 +31,7 @@ class Researcher {
classification: input.classification, classification: input.classification,
fileIds: input.config.fileIds, fileIds: input.config.fileIds,
mode: input.config.mode, mode: input.config.mode,
sources: input.config.sources,
}); });
const researchBlockId = crypto.randomUUID(); const researchBlockId = crypto.randomUUID();
@@ -85,7 +87,7 @@ class Researcher {
if (partialRes.toolCallChunk.length > 0) { if (partialRes.toolCallChunk.length > 0) {
partialRes.toolCallChunk.forEach((tc) => { partialRes.toolCallChunk.forEach((tc) => {
if ( if (
tc.name === '0_reasoning' && tc.name === '__reasoning_preamble' &&
tc.arguments['plan'] && tc.arguments['plan'] &&
!reasoningEmitted && !reasoningEmitted &&
block && block &&
@@ -107,7 +109,7 @@ class Researcher {
}, },
]); ]);
} else if ( } else if (
tc.name === '0_reasoning' && tc.name === '__reasoning_preamble' &&
tc.arguments['plan'] && tc.arguments['plan'] &&
reasoningEmitted && reasoningEmitted &&
block && block &&
@@ -204,8 +206,9 @@ class Researcher {
}) })
.filter((r) => r !== undefined); .filter((r) => r !== undefined);
session.emit('data', { session.emitBlock({
type: 'sources', id: crypto.randomUUID(),
type: 'source',
data: filteredSearchResults, data: filteredSearchResults,
}); });

View File

@@ -12,12 +12,15 @@ export type SearchAgentConfig = {
llm: BaseLLM<any>; llm: BaseLLM<any>;
embedding: BaseEmbedding<any>; embedding: BaseEmbedding<any>;
mode: 'speed' | 'balanced' | 'quality'; mode: 'speed' | 'balanced' | 'quality';
systemInstructions: string;
}; };
export type SearchAgentInput = { export type SearchAgentInput = {
chatHistory: ChatTurnMessage[]; chatHistory: ChatTurnMessage[];
followUp: string; followUp: string;
config: SearchAgentConfig; config: SearchAgentConfig;
chatId: string;
messageId: string;
}; };
export type WidgetInput = { export type WidgetInput = {
@@ -107,6 +110,7 @@ export interface ResearchAction<
classification: ClassifierOutput; classification: ClassifierOutput;
fileIds: string[]; fileIds: string[];
mode: SearchAgentConfig['mode']; mode: SearchAgentConfig['mode'];
sources: SearchSources[];
}) => boolean; }) => boolean;
execute: ( execute: (
params: z.infer<TSchema>, params: z.infer<TSchema>,

View File

@@ -1,6 +1,7 @@
import { sql } from 'drizzle-orm'; import { sql } from 'drizzle-orm';
import { text, integer, sqliteTable } from 'drizzle-orm/sqlite-core'; import { text, integer, sqliteTable } from 'drizzle-orm/sqlite-core';
import { Block } from '../types'; import { Block } from '../types';
import { SearchSources } from '../agents/search/types';
export const messages = sqliteTable('messages', { export const messages = sqliteTable('messages', {
id: integer('id').primaryKey(), id: integer('id').primaryKey(),
@@ -26,7 +27,11 @@ export const chats = sqliteTable('chats', {
id: text('id').primaryKey(), id: text('id').primaryKey(),
title: text('title').notNull(), title: text('title').notNull(),
createdAt: text('createdAt').notNull(), createdAt: text('createdAt').notNull(),
focusMode: text('focusMode').notNull(), sources: text('sources', {
mode: 'json',
})
.$type<SearchSources[]>()
.default(sql`'[]'`),
files: text('files', { mode: 'json' }) files: text('files', { mode: 'json' })
.$type<DBFile[]>() .$type<DBFile[]>()
.default(sql`'[]'`), .default(sql`'[]'`),

View File

@@ -34,7 +34,7 @@ type ChatContext = {
chatHistory: [string, string][]; chatHistory: [string, string][];
files: File[]; files: File[];
fileIds: string[]; fileIds: string[];
focusMode: string; sources: string[];
chatId: string | undefined; chatId: string | undefined;
optimizationMode: string; optimizationMode: string;
isMessagesLoaded: boolean; isMessagesLoaded: boolean;
@@ -48,7 +48,7 @@ type ChatContext = {
researchEnded: boolean; researchEnded: boolean;
setResearchEnded: (ended: boolean) => void; setResearchEnded: (ended: boolean) => void;
setOptimizationMode: (mode: string) => void; setOptimizationMode: (mode: string) => void;
setFocusMode: (mode: string) => void; setSources: (sources: string[]) => void;
setFiles: (files: File[]) => void; setFiles: (files: File[]) => void;
setFileIds: (fileIds: string[]) => void; setFileIds: (fileIds: string[]) => void;
sendMessage: ( sendMessage: (
@@ -176,7 +176,7 @@ const loadMessages = async (
setMessages: (messages: Message[]) => void, setMessages: (messages: Message[]) => void,
setIsMessagesLoaded: (loaded: boolean) => void, setIsMessagesLoaded: (loaded: boolean) => void,
setChatHistory: (history: [string, string][]) => void, setChatHistory: (history: [string, string][]) => void,
setFocusMode: (mode: string) => void, setSources: (sources: string[]) => void,
setNotFound: (notFound: boolean) => void, setNotFound: (notFound: boolean) => void,
setFiles: (files: File[]) => void, setFiles: (files: File[]) => void,
setFileIds: (fileIds: string[]) => void, setFileIds: (fileIds: string[]) => void,
@@ -234,7 +234,7 @@ const loadMessages = async (
setFileIds(files.map((file: File) => file.fileId)); setFileIds(files.map((file: File) => file.fileId));
setChatHistory(history); setChatHistory(history);
setFocusMode(data.chat.focusMode); setSources(data.chat.sources);
setIsMessagesLoaded(true); setIsMessagesLoaded(true);
}; };
@@ -243,7 +243,7 @@ export const chatContext = createContext<ChatContext>({
chatId: '', chatId: '',
fileIds: [], fileIds: [],
files: [], files: [],
focusMode: '', sources: [],
hasError: false, hasError: false,
isMessagesLoaded: false, isMessagesLoaded: false,
isReady: false, isReady: false,
@@ -260,7 +260,7 @@ export const chatContext = createContext<ChatContext>({
sendMessage: async () => {}, sendMessage: async () => {},
setFileIds: () => {}, setFileIds: () => {},
setFiles: () => {}, setFiles: () => {},
setFocusMode: () => {}, setSources: () => {},
setOptimizationMode: () => {}, setOptimizationMode: () => {},
setChatModelProvider: () => {}, setChatModelProvider: () => {},
setEmbeddingModelProvider: () => {}, setEmbeddingModelProvider: () => {},
@@ -269,6 +269,7 @@ export const chatContext = createContext<ChatContext>({
export const ChatProvider = ({ children }: { children: React.ReactNode }) => { export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
const params: { chatId: string } = useParams(); const params: { chatId: string } = useParams();
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const initialMessage = searchParams.get('q'); const initialMessage = searchParams.get('q');
@@ -286,7 +287,7 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
const [files, setFiles] = useState<File[]>([]); const [files, setFiles] = useState<File[]>([]);
const [fileIds, setFileIds] = useState<string[]>([]); const [fileIds, setFileIds] = useState<string[]>([]);
const [focusMode, setFocusMode] = useState('webSearch'); const [sources, setSources] = useState<string[]>(['web']);
const [optimizationMode, setOptimizationMode] = useState('speed'); const [optimizationMode, setOptimizationMode] = useState('speed');
const [isMessagesLoaded, setIsMessagesLoaded] = useState(false); const [isMessagesLoaded, setIsMessagesLoaded] = useState(false);
@@ -401,6 +402,53 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
}); });
}, [messages]); }, [messages]);
const checkReconnect = async () => {
setIsReady(true);
console.debug(new Date(), 'app:ready');
if (messages.length > 0) {
const lastMsg = messages[messages.length - 1];
if (lastMsg.status === 'answering') {
setLoading(true);
setResearchEnded(false);
setMessageAppeared(false);
const res = await fetch(`/api/reconnect/${lastMsg.backendId}`, {
method: 'POST',
});
if (!res.body) throw new Error('No response body');
const reader = res.body?.getReader();
const decoder = new TextDecoder('utf-8');
let partialChunk = '';
const messageHandler = getMessageHandler(lastMsg);
while (true) {
const { value, done } = await reader.read();
if (done) break;
partialChunk += decoder.decode(value, { stream: true });
try {
const messages = partialChunk.split('\n');
for (const msg of messages) {
if (!msg.trim()) continue;
const json = JSON.parse(msg);
messageHandler(json);
}
partialChunk = '';
} catch (error) {
console.warn('Incomplete JSON, waiting for next chunk...');
}
}
}
}
};
useEffect(() => { useEffect(() => {
checkConfig( checkConfig(
setChatModelProvider, setChatModelProvider,
@@ -436,7 +484,7 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
setMessages, setMessages,
setIsMessagesLoaded, setIsMessagesLoaded,
setChatHistory, setChatHistory,
setFocusMode, setSources,
setNotFound, setNotFound,
setFiles, setFiles,
setFileIds, setFileIds,
@@ -454,13 +502,15 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
}, [messages]); }, [messages]);
useEffect(() => { useEffect(() => {
if (isMessagesLoaded && isConfigReady) { if (isMessagesLoaded && isConfigReady && newChatCreated) {
setIsReady(true); setIsReady(true);
console.debug(new Date(), 'app:ready'); console.debug(new Date(), 'app:ready');
} else if (isMessagesLoaded && isConfigReady && !newChatCreated) {
checkReconnect();
} else { } else {
setIsReady(false); setIsReady(false);
} }
}, [isMessagesLoaded, isConfigReady]); }, [isMessagesLoaded, isConfigReady, newChatCreated]);
const rewrite = (messageId: string) => { const rewrite = (messageId: string) => {
const index = messages.findIndex((msg) => msg.messageId === messageId); const index = messages.findIndex((msg) => msg.messageId === messageId);
@@ -488,38 +538,10 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [isConfigReady, isReady, initialMessage]); }, [isConfigReady, isReady, initialMessage]);
const sendMessage: ChatContext['sendMessage'] = async ( const getMessageHandler = (message: Message) => {
message, const messageId = message.messageId;
messageId,
rewrite = false,
) => {
if (loading || !message) return;
setLoading(true);
setResearchEnded(false);
setMessageAppeared(false);
if (messages.length <= 1) { return async (data: any) => {
window.history.replaceState(null, '', `/c/${chatId}`);
}
messageId = messageId ?? crypto.randomBytes(7).toString('hex');
const backendId = crypto.randomBytes(20).toString('hex');
const newMessage: Message = {
messageId,
chatId: chatId!,
backendId,
query: message,
responseBlocks: [],
status: 'answering',
createdAt: new Date(),
};
setMessages((prevMessages) => [...prevMessages, newMessage]);
const receivedTextRef = { current: '' };
const messageHandler = async (data: any) => {
if (data.type === 'error') { if (data.type === 'error') {
toast.error(data.data); toast.error(data.data);
setLoading(false); setLoading(false);
@@ -536,7 +558,7 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
if (data.type === 'researchComplete') { if (data.type === 'researchComplete') {
setResearchEnded(true); setResearchEnded(true);
if ( if (
newMessage.responseBlocks.find( message.responseBlocks.find(
(b) => b.type === 'source' && b.data.length > 0, (b) => b.type === 'source' && b.data.length > 0,
) )
) { ) {
@@ -556,6 +578,13 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
return msg; return msg;
}), }),
); );
if (
(data.block.type === 'source' && data.block.data.length > 0) ||
data.block.type === 'text'
) {
setMessageAppeared(true);
}
} }
if (data.type === 'updateBlock') { if (data.type === 'updateBlock') {
@@ -577,72 +606,19 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
); );
} }
if (data.type === 'sources') {
const sourceBlock: Block = {
id: crypto.randomBytes(7).toString('hex'),
type: 'source',
data: data.data,
};
setMessages((prev) =>
prev.map((msg) => {
if (msg.messageId === messageId) {
return {
...msg,
responseBlocks: [...msg.responseBlocks, sourceBlock],
};
}
return msg;
}),
);
if (data.data.length > 0) {
setMessageAppeared(true);
}
}
if (data.type === 'message') {
receivedTextRef.current += data.data;
setMessages((prev) =>
prev.map((msg) => {
if (msg.messageId === messageId) {
const existingTextBlockIndex = msg.responseBlocks.findIndex(
(b) => b.type === 'text',
);
if (existingTextBlockIndex >= 0) {
const updatedBlocks = [...msg.responseBlocks];
const existingBlock = updatedBlocks[
existingTextBlockIndex
] as Block & { type: 'text' };
updatedBlocks[existingTextBlockIndex] = {
...existingBlock,
data: existingBlock.data + data.data,
};
return { ...msg, responseBlocks: updatedBlocks };
} else {
const textBlock: Block = {
id: crypto.randomBytes(7).toString('hex'),
type: 'text',
data: data.data,
};
return {
...msg,
responseBlocks: [...msg.responseBlocks, textBlock],
};
}
}
return msg;
}),
);
setMessageAppeared(true);
}
if (data.type === 'messageEnd') { if (data.type === 'messageEnd') {
const currentMsg = messagesRef.current.find(
(msg) => msg.messageId === messageId,
);
const newHistory: [string, string][] = [ const newHistory: [string, string][] = [
...chatHistory, ...chatHistory,
['human', message], ['human', message.query],
['assistant', receivedTextRef.current], [
'assistant',
currentMsg?.responseBlocks.find((b) => b.type === 'text')?.data ||
'',
],
]; ];
setChatHistory(newHistory); setChatHistory(newHistory);
@@ -672,9 +648,6 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
} }
// Check if there are sources and no suggestions // Check if there are sources and no suggestions
const currentMsg = messagesRef.current.find(
(msg) => msg.messageId === messageId,
);
const hasSourceBlocks = currentMsg?.responseBlocks.some( const hasSourceBlocks = currentMsg?.responseBlocks.some(
(block) => block.type === 'source' && block.data.length > 0, (block) => block.type === 'source' && block.data.length > 0,
@@ -705,6 +678,36 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
} }
} }
}; };
};
const sendMessage: ChatContext['sendMessage'] = async (
message,
messageId,
rewrite = false,
) => {
if (loading || !message) return;
setLoading(true);
setResearchEnded(false);
setMessageAppeared(false);
if (messages.length <= 1) {
window.history.replaceState(null, '', `/c/${chatId}`);
}
messageId = messageId ?? crypto.randomBytes(7).toString('hex');
const backendId = crypto.randomBytes(20).toString('hex');
const newMessage: Message = {
messageId,
chatId: chatId!,
backendId,
query: message,
responseBlocks: [],
status: 'answering',
createdAt: new Date(),
};
setMessages((prevMessages) => [...prevMessages, newMessage]);
const messageIndex = messages.findIndex((m) => m.messageId === messageId); const messageIndex = messages.findIndex((m) => m.messageId === messageId);
@@ -722,7 +725,7 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
}, },
chatId: chatId!, chatId: chatId!,
files: fileIds, files: fileIds,
focusMode: focusMode, sources: sources,
optimizationMode: optimizationMode, optimizationMode: optimizationMode,
history: rewrite history: rewrite
? chatHistory.slice(0, messageIndex === -1 ? undefined : messageIndex) ? chatHistory.slice(0, messageIndex === -1 ? undefined : messageIndex)
@@ -746,6 +749,8 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
let partialChunk = ''; let partialChunk = '';
const messageHandler = getMessageHandler(newMessage);
while (true) { while (true) {
const { value, done } = await reader.read(); const { value, done } = await reader.read();
if (done) break; if (done) break;
@@ -774,7 +779,7 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
chatHistory, chatHistory,
files, files,
fileIds, fileIds,
focusMode, sources,
chatId, chatId,
hasError, hasError,
isMessagesLoaded, isMessagesLoaded,
@@ -785,7 +790,7 @@ export const ChatProvider = ({ children }: { children: React.ReactNode }) => {
optimizationMode, optimizationMode,
setFileIds, setFileIds,
setFiles, setFiles,
setFocusMode, setSources,
setOptimizationMode, setOptimizationMode,
rewrite, rewrite,
sendMessage, sendMessage,

View File

@@ -0,0 +1,5 @@
import OpenAILLM from "../openai/openaiLLM";
class AnthropicLLM extends OpenAILLM {}
export default AnthropicLLM;

View File

@@ -0,0 +1,115 @@
import { UIConfigField } from '@/lib/config/types';
import { getConfiguredModelProviderById } from '@/lib/config/serverRegistry';
import { Model, ModelList, ProviderMetadata } from '../../types';
import BaseEmbedding from '../../base/embedding';
import BaseModelProvider from '../../base/provider';
import BaseLLM from '../../base/llm';
import AnthropicLLM from './anthropicLLM';
interface AnthropicConfig {
apiKey: string;
}
const providerConfigFields: UIConfigField[] = [
{
type: 'password',
name: 'API Key',
key: 'apiKey',
description: 'Your Anthropic API key',
required: true,
placeholder: 'Anthropic API Key',
env: 'ANTHROPIC_API_KEY',
scope: 'server',
},
];
class AnthropicProvider extends BaseModelProvider<AnthropicConfig> {
constructor(id: string, name: string, config: AnthropicConfig) {
super(id, name, config);
}
async getDefaultModels(): Promise<ModelList> {
const res = await fetch('https://api.anthropic.com/v1/models?limit=999', {
method: 'GET',
headers: {
'x-api-key': this.config.apiKey,
'anthropic-version': '2023-06-01',
'Content-type': 'application/json',
},
});
if (!res.ok) {
throw new Error(`Failed to fetch Anthropic models: ${res.statusText}`);
}
const data = (await res.json()).data;
const models: Model[] = data.map((m: any) => {
return {
key: m.id,
name: m.display_name,
};
});
return {
embedding: [],
chat: models,
};
}
async getModelList(): Promise<ModelList> {
const defaultModels = await this.getDefaultModels();
const configProvider = getConfiguredModelProviderById(this.id)!;
return {
embedding: [],
chat: [...defaultModels.chat, ...configProvider.chatModels],
};
}
async loadChatModel(key: string): Promise<BaseLLM<any>> {
const modelList = await this.getModelList();
const exists = modelList.chat.find((m) => m.key === key);
if (!exists) {
throw new Error(
'Error Loading Anthropic Chat Model. Invalid Model Selected',
);
}
return new AnthropicLLM({
apiKey: this.config.apiKey,
model: key,
baseURL: 'https://api.anthropic.com/v1'
});
}
async loadEmbeddingModel(key: string): Promise<BaseEmbedding<any>> {
throw new Error('Anthropic provider does not support embedding models.');
}
static parseAndValidate(raw: any): AnthropicConfig {
if (!raw || typeof raw !== 'object')
throw new Error('Invalid config provided. Expected object');
if (!raw.apiKey)
throw new Error('Invalid config provided. API key must be provided');
return {
apiKey: String(raw.apiKey),
};
}
static getProviderConfigFields(): UIConfigField[] {
return providerConfigFields;
}
static getProviderMetadata(): ProviderMetadata {
return {
key: 'anthropic',
name: 'Anthropic',
};
}
}
export default AnthropicProvider;

View File

@@ -0,0 +1,5 @@
import OpenAIEmbedding from '../openai/openaiEmbedding';
class GeminiEmbedding extends OpenAIEmbedding {}
export default GeminiEmbedding;

View File

@@ -0,0 +1,5 @@
import OpenAILLM from '../openai/openaiLLM';
class GeminiLLM extends OpenAILLM {}
export default GeminiLLM;

View File

@@ -0,0 +1,144 @@
import { UIConfigField } from '@/lib/config/types';
import { getConfiguredModelProviderById } from '@/lib/config/serverRegistry';
import { Model, ModelList, ProviderMetadata } from '../../types';
import GeminiEmbedding from './geminiEmbedding';
import BaseEmbedding from '../../base/embedding';
import BaseModelProvider from '../../base/provider';
import BaseLLM from '../../base/llm';
import GeminiLLM from './geminiLLM';
interface GeminiConfig {
apiKey: string;
}
const providerConfigFields: UIConfigField[] = [
{
type: 'password',
name: 'API Key',
key: 'apiKey',
description: 'Your Gemini API key',
required: true,
placeholder: 'Gemini API Key',
env: 'GEMINI_API_KEY',
scope: 'server',
},
];
class GeminiProvider extends BaseModelProvider<GeminiConfig> {
constructor(id: string, name: string, config: GeminiConfig) {
super(id, name, config);
}
async getDefaultModels(): Promise<ModelList> {
const res = await fetch(
`https://generativelanguage.googleapis.com/v1beta/models?key=${this.config.apiKey}`,
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
},
);
const data = await res.json();
let defaultEmbeddingModels: Model[] = [];
let defaultChatModels: Model[] = [];
data.models.forEach((m: any) => {
if (
m.supportedGenerationMethods.some(
(genMethod: string) =>
genMethod === 'embedText' || genMethod === 'embedContent',
)
) {
defaultEmbeddingModels.push({
key: m.name,
name: m.displayName,
});
} else if (m.supportedGenerationMethods.includes('generateContent')) {
defaultChatModels.push({
key: m.name,
name: m.displayName,
});
}
});
return {
embedding: defaultEmbeddingModels,
chat: defaultChatModels,
};
}
async getModelList(): Promise<ModelList> {
const defaultModels = await this.getDefaultModels();
const configProvider = getConfiguredModelProviderById(this.id)!;
return {
embedding: [
...defaultModels.embedding,
...configProvider.embeddingModels,
],
chat: [...defaultModels.chat, ...configProvider.chatModels],
};
}
async loadChatModel(key: string): Promise<BaseLLM<any>> {
const modelList = await this.getModelList();
const exists = modelList.chat.find((m) => m.key === key);
if (!exists) {
throw new Error(
'Error Loading Gemini Chat Model. Invalid Model Selected',
);
}
return new GeminiLLM({
apiKey: this.config.apiKey,
model: key,
baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai',
});
}
async loadEmbeddingModel(key: string): Promise<BaseEmbedding<any>> {
const modelList = await this.getModelList();
const exists = modelList.embedding.find((m) => m.key === key);
if (!exists) {
throw new Error(
'Error Loading Gemini Embedding Model. Invalid Model Selected.',
);
}
return new GeminiEmbedding({
apiKey: this.config.apiKey,
model: key,
baseURL: 'https://generativelanguage.googleapis.com/v1beta/openai',
});
}
static parseAndValidate(raw: any): GeminiConfig {
if (!raw || typeof raw !== 'object')
throw new Error('Invalid config provided. Expected object');
if (!raw.apiKey)
throw new Error('Invalid config provided. API key must be provided');
return {
apiKey: String(raw.apiKey),
};
}
static getProviderConfigFields(): UIConfigField[] {
return providerConfigFields;
}
static getProviderMetadata(): ProviderMetadata {
return {
key: 'gemini',
name: 'Gemini',
};
}
}
export default GeminiProvider;

View File

@@ -0,0 +1,5 @@
import OpenAILLM from '../openai/openaiLLM';
class GroqLLM extends OpenAILLM {}
export default GroqLLM;

View File

@@ -0,0 +1,113 @@
import { UIConfigField } from '@/lib/config/types';
import { getConfiguredModelProviderById } from '@/lib/config/serverRegistry';
import { Model, ModelList, ProviderMetadata } from '../../types';
import BaseEmbedding from '../../base/embedding';
import BaseModelProvider from '../../base/provider';
import BaseLLM from '../../base/llm';
import GroqLLM from './groqLLM';
interface GroqConfig {
apiKey: string;
}
const providerConfigFields: UIConfigField[] = [
{
type: 'password',
name: 'API Key',
key: 'apiKey',
description: 'Your Groq API key',
required: true,
placeholder: 'Groq API Key',
env: 'GROQ_API_KEY',
scope: 'server',
},
];
class GroqProvider extends BaseModelProvider<GroqConfig> {
constructor(id: string, name: string, config: GroqConfig) {
super(id, name, config);
}
async getDefaultModels(): Promise<ModelList> {
const res = await fetch(`https://api.groq.com/openai/v1/models`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${this.config.apiKey}`,
},
});
const data = await res.json();
const defaultChatModels: Model[] = [];
data.data.forEach((m: any) => {
defaultChatModels.push({
key: m.id,
name: m.id,
});
});
return {
embedding: [],
chat: defaultChatModels,
};
}
async getModelList(): Promise<ModelList> {
const defaultModels = await this.getDefaultModels();
const configProvider = getConfiguredModelProviderById(this.id)!;
return {
embedding: [
...defaultModels.embedding,
...configProvider.embeddingModels,
],
chat: [...defaultModels.chat, ...configProvider.chatModels],
};
}
async loadChatModel(key: string): Promise<BaseLLM<any>> {
const modelList = await this.getModelList();
const exists = modelList.chat.find((m) => m.key === key);
if (!exists) {
throw new Error('Error Loading Groq Chat Model. Invalid Model Selected');
}
return new GroqLLM({
apiKey: this.config.apiKey,
model: key,
baseURL: 'https://api.groq.com/openai/v1',
});
}
async loadEmbeddingModel(key: string): Promise<BaseEmbedding<any>> {
throw new Error('Groq Provider does not support embedding models.');
}
static parseAndValidate(raw: any): GroqConfig {
if (!raw || typeof raw !== 'object')
throw new Error('Invalid config provided. Expected object');
if (!raw.apiKey)
throw new Error('Invalid config provided. API key must be provided');
return {
apiKey: String(raw.apiKey),
};
}
static getProviderConfigFields(): UIConfigField[] {
return providerConfigFields;
}
static getProviderMetadata(): ProviderMetadata {
return {
key: 'groq',
name: 'Groq',
};
}
}
export default GroqProvider;

View File

@@ -2,10 +2,20 @@ import { ModelProviderUISection } from '@/lib/config/types';
import { ProviderConstructor } from '../base/provider'; import { ProviderConstructor } from '../base/provider';
import OpenAIProvider from './openai'; import OpenAIProvider from './openai';
import OllamaProvider from './ollama'; import OllamaProvider from './ollama';
import GeminiProvider from './gemini';
import TransformersProvider from './transformers';
import GroqProvider from './groq';
import LemonadeProvider from './lemonade';
import AnthropicProvider from './anthropic';
export const providers: Record<string, ProviderConstructor<any>> = { export const providers: Record<string, ProviderConstructor<any>> = {
openai: OpenAIProvider, openai: OpenAIProvider,
ollama: OllamaProvider, ollama: OllamaProvider,
gemini: GeminiProvider,
transformers: TransformersProvider,
groq: GroqProvider,
lemonade: LemonadeProvider,
anthropic: AnthropicProvider
}; };
export const getModelProvidersUIConfigSection = export const getModelProvidersUIConfigSection =

View File

@@ -0,0 +1,149 @@
import { UIConfigField } from '@/lib/config/types';
import { getConfiguredModelProviderById } from '@/lib/config/serverRegistry';
import BaseModelProvider from '../../base/provider';
import { Model, ModelList, ProviderMetadata } from '../../types';
import BaseLLM from '../../base/llm';
import LemonadeLLM from './lemonadeLLM';
import BaseEmbedding from '../../base/embedding';
import LemonadeEmbedding from './lemonadeEmbedding';
interface LemonadeConfig {
baseURL: string;
apiKey?: string;
}
const providerConfigFields: UIConfigField[] = [
{
type: 'string',
name: 'Base URL',
key: 'baseURL',
description: 'The base URL for Lemonade API',
required: true,
placeholder: 'https://api.lemonade.ai/v1',
env: 'LEMONADE_BASE_URL',
scope: 'server',
},
{
type: 'password',
name: 'API Key',
key: 'apiKey',
description: 'Your Lemonade API key (optional)',
required: false,
placeholder: 'Lemonade API Key',
env: 'LEMONADE_API_KEY',
scope: 'server',
},
];
class LemonadeProvider extends BaseModelProvider<LemonadeConfig> {
constructor(id: string, name: string, config: LemonadeConfig) {
super(id, name, config);
}
async getDefaultModels(): Promise<ModelList> {
try {
const res = await fetch(`${this.config.baseURL}/models`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
...this.config.apiKey ? {'Authorization': `Bearer ${this.config.apiKey}`} : {}
},
});
const data = await res.json();
const models: Model[] = data.data.filter((m: any) => m.recipe === 'llamacpp').map((m: any) => {
return {
name: m.id,
key: m.id,
};
});
return {
embedding: models,
chat: models,
};
} catch (err) {
if (err instanceof TypeError) {
throw new Error(
'Error connecting to Lemonade API. Please ensure the base URL is correct and the service is available.',
);
}
throw err;
}
}
async getModelList(): Promise<ModelList> {
const defaultModels = await this.getDefaultModels();
const configProvider = getConfiguredModelProviderById(this.id)!;
return {
embedding: [
...defaultModels.embedding,
...configProvider.embeddingModels,
],
chat: [...defaultModels.chat, ...configProvider.chatModels],
};
}
async loadChatModel(key: string): Promise<BaseLLM<any>> {
const modelList = await this.getModelList();
const exists = modelList.chat.find((m) => m.key === key);
if (!exists) {
throw new Error(
'Error Loading Lemonade Chat Model. Invalid Model Selected',
);
}
return new LemonadeLLM({
apiKey: this.config.apiKey || 'not-needed',
model: key,
baseURL: this.config.baseURL,
});
}
async loadEmbeddingModel(key: string): Promise<BaseEmbedding<any>> {
const modelList = await this.getModelList();
const exists = modelList.embedding.find((m) => m.key === key);
if (!exists) {
throw new Error(
'Error Loading Lemonade Embedding Model. Invalid Model Selected.',
);
}
return new LemonadeEmbedding({
apiKey: this.config.apiKey || 'not-needed',
model: key,
baseURL: this.config.baseURL,
});
}
static parseAndValidate(raw: any): LemonadeConfig {
if (!raw || typeof raw !== 'object')
throw new Error('Invalid config provided. Expected object');
if (!raw.baseURL)
throw new Error('Invalid config provided. Base URL must be provided');
return {
baseURL: String(raw.baseURL),
apiKey: raw.apiKey ? String(raw.apiKey) : undefined,
};
}
static getProviderConfigFields(): UIConfigField[] {
return providerConfigFields;
}
static getProviderMetadata(): ProviderMetadata {
return {
key: 'lemonade',
name: 'Lemonade',
};
}
}
export default LemonadeProvider;

View File

@@ -0,0 +1,5 @@
import OpenAIEmbedding from "../openai/openaiEmbedding";
class LemonadeEmbedding extends OpenAIEmbedding {}
export default LemonadeEmbedding;

View File

@@ -0,0 +1,5 @@
import OpenAILLM from "../openai/openaiLLM";
class LemonadeLLM extends OpenAILLM {}
export default LemonadeLLM;

View File

@@ -24,6 +24,7 @@ const reasoningModels = [
'qwen3', 'qwen3',
'deepseek-v3.1', 'deepseek-v3.1',
'magistral', 'magistral',
'nemotron-3-nano',
]; ];
class OllamaLLM extends BaseLLM<OllamaConfig> { class OllamaLLM extends BaseLLM<OllamaConfig> {

View File

@@ -0,0 +1,88 @@
import { UIConfigField } from '@/lib/config/types';
import { getConfiguredModelProviderById } from '@/lib/config/serverRegistry';
import { Model, ModelList, ProviderMetadata } from '../../types';
import BaseModelProvider from '../../base/provider';
import BaseLLM from '../../base/llm';
import BaseEmbedding from '../../base/embedding';
import TransformerEmbedding from './transformerEmbedding';
interface TransformersConfig {}
const defaultEmbeddingModels: Model[] = [
{
name: 'all-MiniLM-L6-v2',
key: 'Xenova/all-MiniLM-L6-v2',
},
{
name: 'mxbai-embed-large-v1',
key: 'mixedbread-ai/mxbai-embed-large-v1',
},
{
name: 'nomic-embed-text-v1',
key: 'Xenova/nomic-embed-text-v1',
},
];
const providerConfigFields: UIConfigField[] = [];
class TransformersProvider extends BaseModelProvider<TransformersConfig> {
constructor(id: string, name: string, config: TransformersConfig) {
super(id, name, config);
}
async getDefaultModels(): Promise<ModelList> {
return {
embedding: [...defaultEmbeddingModels],
chat: [],
};
}
async getModelList(): Promise<ModelList> {
const defaultModels = await this.getDefaultModels();
const configProvider = getConfiguredModelProviderById(this.id)!;
return {
embedding: [
...defaultModels.embedding,
...configProvider.embeddingModels,
],
chat: [],
};
}
async loadChatModel(key: string): Promise<BaseLLM<any>> {
throw new Error('Transformers Provider does not support chat models.');
}
async loadEmbeddingModel(key: string): Promise<BaseEmbedding<any>> {
const modelList = await this.getModelList();
const exists = modelList.embedding.find((m) => m.key === key);
if (!exists) {
throw new Error(
'Error Loading OpenAI Embedding Model. Invalid Model Selected.',
);
}
return new TransformerEmbedding({
model: key,
});
}
static parseAndValidate(raw: any): TransformersConfig {
return {};
}
static getProviderConfigFields(): UIConfigField[] {
return providerConfigFields;
}
static getProviderMetadata(): ProviderMetadata {
return {
key: 'transformers',
name: 'Transformers',
};
}
}
export default TransformersProvider;

View File

@@ -0,0 +1,43 @@
import { Chunk } from '@/lib/types';
import BaseEmbedding from '../../base/embedding';
import { FeatureExtractionPipeline, pipeline } from '@huggingface/transformers';
type TransformerConfig = {
model: string;
};
class TransformerEmbedding extends BaseEmbedding<TransformerConfig> {
private pipelinePromise: Promise<FeatureExtractionPipeline> | null = null;
constructor(protected config: TransformerConfig) {
super(config);
}
async embedText(texts: string[]): Promise<number[][]> {
return this.embed(texts);
}
async embedChunks(chunks: Chunk[]): Promise<number[][]> {
return this.embed(chunks.map((c) => c.content));
}
async embed(texts: string[]): Promise<number[][]> {
if (!this.pipelinePromise) {
this.pipelinePromise = (async () => {
const transformers = await import('@huggingface/transformers');
return (await transformers.pipeline(
'feature-extraction',
this.config.model,
)) as unknown as FeatureExtractionPipeline;
})();
}
const pipeline = await this.pipelinePromise;
const output = await pipeline(texts, { pooling: 'mean', normalize: true });
return output.tolist() as number[][];
}
}
export default TransformerEmbedding;

View File

@@ -55,7 +55,8 @@ You must respond in the following JSON format without any extra text, explanatio
"academicSearch": boolean, "academicSearch": boolean,
"discussionSearch": boolean, "discussionSearch": boolean,
"showWeatherWidget": boolean, "showWeatherWidget": boolean,
"showStockWidget": boolean "showStockWidget": boolean,
"showCalculationWidget": boolean,
}, },
"standaloneFollowUp": string "standaloneFollowUp": string
} }

View File

@@ -109,12 +109,12 @@ const getBalancedPrompt = (
<goal> <goal>
Fulfill the user's request with concise reasoning plus focused actions. Fulfill the user's request with concise reasoning plus focused actions.
You must call the 0_reasoning tool before every tool call in this assistant turn. Alternate: 0_reasoning → tool → 0_reasoning → tool ... and finish with 0_reasoning → done. Open each 0_reasoning with a brief intent phrase (e.g., "Okay, the user wants to...", "Searching for...", "Looking into...") and lay out your reasoning for the next step. Keep it natural language, no tool names. You must call the __reasoning_preamble tool before every tool call in this assistant turn. Alternate: __reasoning_preamble → tool → __reasoning_preamble → tool ... and finish with __reasoning_preamble → done. Open each __reasoning_preamble with a brief intent phrase (e.g., "Okay, the user wants to...", "Searching for...", "Looking into...") and lay out your reasoning for the next step. Keep it natural language, no tool names.
</goal> </goal>
<core_principle> <core_principle>
Your knowledge is outdated; if you have web search, use it to ground answers even for seemingly basic facts. Your knowledge is outdated; if you have web search, use it to ground answers even for seemingly basic facts.
You can call at most 6 tools total per turn: up to 2 reasoning (0_reasoning counts as reasoning), 2-3 information-gathering calls, and 1 done. If you hit the cap, stop after done. You can call at most 6 tools total per turn: up to 2 reasoning (__reasoning_preamble counts as reasoning), 2-3 information-gathering calls, and 1 done. If you hit the cap, stop after done.
Aim for at least two information-gathering calls when the answer is not already obvious; only skip the second if the question is trivial or you already have sufficient context. Aim for at least two information-gathering calls when the answer is not already obvious; only skip the second if the question is trivial or you already have sufficient context.
Do not spam searches—pick the most targeted queries. Do not spam searches—pick the most targeted queries.
</core_principle> </core_principle>
@@ -144,7 +144,7 @@ const getBalancedPrompt = (
</examples> </examples>
<available_tools> <available_tools>
YOU MUST CALL 0_reasoning BEFORE EVERY TOOL CALL IN THIS ASSISTANT TURN. IF YOU DO NOT CALL IT, THE TOOL CALL WILL BE IGNORED. YOU MUST CALL __reasoning_preamble BEFORE EVERY TOOL CALL IN THIS ASSISTANT TURN. IF YOU DO NOT CALL IT, THE TOOL CALL WILL BE IGNORED.
${actionDesc} ${actionDesc}
</available_tools> </available_tools>
@@ -160,16 +160,16 @@ const getBalancedPrompt = (
5. **Overthinking**: Keep reasoning simple and tool calls focused 5. **Overthinking**: Keep reasoning simple and tool calls focused
6. **Skipping the reasoning step**: Always call 0_reasoning first to outline your approach before other actions 6. **Skipping the reasoning step**: Always call __reasoning_preamble first to outline your approach before other actions
</mistakes_to_avoid> </mistakes_to_avoid>
<response_protocol> <response_protocol>
- NEVER output normal text to the user. ONLY call tools. - NEVER output normal text to the user. ONLY call tools.
- Start with 0_reasoning and call 0_reasoning before every tool call (including done): open with intent phrase ("Okay, the user wants to...", "Looking into...", etc.) and lay out your reasoning for the next step. No tool names. - Start with __reasoning_preamble and call __reasoning_preamble before every tool call (including done): open with intent phrase ("Okay, the user wants to...", "Looking into...", etc.) and lay out your reasoning for the next step. No tool names.
- Choose tools based on the action descriptions provided above. - Choose tools based on the action descriptions provided above.
- Default to web_search when information is missing or stale; keep queries targeted (max 3 per call). - Default to web_search when information is missing or stale; keep queries targeted (max 3 per call).
- Use at most 6 tool calls total (0_reasoning + 2-3 info calls + 0_reasoning + done). If done is called early, stop. - Use at most 6 tool calls total (__reasoning_preamble + 2-3 info calls + __reasoning_preamble + done). If done is called early, stop.
- Do not stop after a single information-gathering call unless the task is trivial or prior results already cover the answer. - Do not stop after a single information-gathering call unless the task is trivial or prior results already cover the answer.
- Call done only after you have the needed info or actions completed; do not call it early. - Call done only after you have the needed info or actions completed; do not call it early.
- Do not invent tools. Do not return JSON. - Do not invent tools. Do not return JSON.
@@ -210,15 +210,15 @@ const getQualityPrompt = (
<goal> <goal>
Conduct the deepest, most thorough research possible. Leave no stone unturned. Conduct the deepest, most thorough research possible. Leave no stone unturned.
Follow an iterative reason-act loop: call 0_reasoning before every tool call to outline the next step, then call the tool, then 0_reasoning again to reflect and decide the next step. Repeat until you have exhaustive coverage. Follow an iterative reason-act loop: call __reasoning_preamble before every tool call to outline the next step, then call the tool, then __reasoning_preamble again to reflect and decide the next step. Repeat until you have exhaustive coverage.
Open each 0_reasoning with a brief intent phrase (e.g., "Okay, the user wants to know about...", "From the results, it looks like...", "Now I need to dig into...") and describe what you'll do next. Keep it natural language, no tool names. Open each __reasoning_preamble with a brief intent phrase (e.g., "Okay, the user wants to know about...", "From the results, it looks like...", "Now I need to dig into...") and describe what you'll do next. Keep it natural language, no tool names.
Finish with done only when you have comprehensive, multi-angle information. Finish with done only when you have comprehensive, multi-angle information.
</goal> </goal>
<core_principle> <core_principle>
Your knowledge is outdated; always use the available tools to ground answers. Your knowledge is outdated; always use the available tools to ground answers.
This is DEEP RESEARCH mode—be exhaustive. Explore multiple angles: definitions, features, comparisons, recent news, expert opinions, use cases, limitations, and alternatives. This is DEEP RESEARCH mode—be exhaustive. Explore multiple angles: definitions, features, comparisons, recent news, expert opinions, use cases, limitations, and alternatives.
You can call up to 10 tools total per turn. Use an iterative loop: 0_reasoning → tool call(s) → 0_reasoning → tool call(s) → ... → 0_reasoning → done. You can call up to 10 tools total per turn. Use an iterative loop: __reasoning_preamble → tool call(s) → __reasoning_preamble → tool call(s) → ... → __reasoning_preamble → done.
Never settle for surface-level answers. If results hint at more depth, reason about your next step and follow up. Cross-reference information from multiple queries. Never settle for surface-level answers. If results hint at more depth, reason about your next step and follow up. Cross-reference information from multiple queries.
</core_principle> </core_principle>
@@ -264,7 +264,7 @@ const getQualityPrompt = (
</examples> </examples>
<available_tools> <available_tools>
YOU MUST CALL 0_reasoning BEFORE EVERY TOOL CALL IN THIS ASSISTANT TURN. IF YOU DO NOT CALL IT, THE TOOL CALL WILL BE IGNORED. YOU MUST CALL __reasoning_preamble BEFORE EVERY TOOL CALL IN THIS ASSISTANT TURN. IF YOU DO NOT CALL IT, THE TOOL CALL WILL BE IGNORED.
${actionDesc} ${actionDesc}
</available_tools> </available_tools>
@@ -291,14 +291,14 @@ const getQualityPrompt = (
5. **Premature done**: Don't call done until you've exhausted reasonable research avenues 5. **Premature done**: Don't call done until you've exhausted reasonable research avenues
6. **Skipping the reasoning step**: Always call 0_reasoning first to outline your research strategy 6. **Skipping the reasoning step**: Always call __reasoning_preamble first to outline your research strategy
</mistakes_to_avoid> </mistakes_to_avoid>
<response_protocol> <response_protocol>
- NEVER output normal text to the user. ONLY call tools. - NEVER output normal text to the user. ONLY call tools.
- Follow an iterative loop: 0_reasoning → tool call → 0_reasoning → tool call → ... → 0_reasoning → done. - Follow an iterative loop: __reasoning_preamble → tool call → __reasoning_preamble → tool call → ... → __reasoning_preamble → done.
- Each 0_reasoning should reflect on previous results (if any) and state the next research step. No tool names in the reasoning. - Each __reasoning_preamble should reflect on previous results (if any) and state the next research step. No tool names in the reasoning.
- Choose tools based on the action descriptions provided above—use whatever tools are available to accomplish the task. - Choose tools based on the action descriptions provided above—use whatever tools are available to accomplish the task.
- Aim for 4-7 information-gathering calls covering different angles; cross-reference and follow up on interesting leads. - Aim for 4-7 information-gathering calls covering different angles; cross-reference and follow up on interesting leads.
- Call done only after comprehensive, multi-angle research is complete. - Call done only after comprehensive, multi-angle research is complete.

View File

@@ -1,4 +1,8 @@
export const getWriterPrompt = (context: string) => { export const getWriterPrompt = (
context: string,
systemInstructions: string,
mode: 'speed' | 'balanced' | 'quality',
) => {
return ` return `
You are Perplexica, an AI model skilled in web search and crafting detailed, engaging, and well-structured answers. You excel at summarizing web pages and extracting relevant information to create professional, blog-style responses. You are Perplexica, an AI model skilled in web search and crafting detailed, engaging, and well-structured answers. You excel at summarizing web pages and extracting relevant information to create professional, blog-style responses.
@@ -29,7 +33,11 @@ You are Perplexica, an AI model skilled in web search and crafting detailed, eng
- If the query involves technical, historical, or complex topics, provide detailed background and explanatory sections to ensure clarity. - If the query involves technical, historical, or complex topics, provide detailed background and explanatory sections to ensure clarity.
- If the user provides vague input or if relevant information is missing, explain what additional details might help refine the search. - If the user provides vague input or if relevant information is missing, explain what additional details might help refine the search.
- If no relevant information is found, say: "Hmm, sorry I could not find any relevant information on this topic. Would you like me to search again or ask something else?" Be transparent about limitations and suggest alternatives or ways to reframe the query. - If no relevant information is found, say: "Hmm, sorry I could not find any relevant information on this topic. Would you like me to search again or ask something else?" Be transparent about limitations and suggest alternatives or ways to reframe the query.
${mode === 'quality' ? "- YOU ARE CURRENTLY SET IN QUALITY MODE, GENERATE VERY DEEP, DETAILED AND COMPREHENSIVE RESPONSES USING THE FULL CONTEXT PROVIDED. ASSISTANT'S RESPONSES SHALL NOT BE LESS THAN AT LEAST 2000 WORDS, COVER EVERYTHING AND FRAME IT LIKE A RESEARCH REPORT." : ''}
### User instructions
These instructions are shared to you by the user and not by the system. You will have to follow them but give them less priority than the above instructions. If the user has provided specific instructions or preferences, incorporate them into your response while adhering to the overall guidelines.
${systemInstructions}
### Example Output ### Example Output
- Begin with a brief introduction summarizing the event or query topic. - Begin with a brief introduction summarizing the event or query topic.

View File

@@ -2,8 +2,14 @@ import { EventEmitter } from 'stream';
import { applyPatch } from 'rfc6902'; import { applyPatch } from 'rfc6902';
import { Block } from './types'; import { Block } from './types';
const sessions =
(global as any)._sessionManagerSessions || new Map<string, SessionManager>();
if (process.env.NODE_ENV !== 'production') {
(global as any)._sessionManagerSessions = sessions;
}
class SessionManager { class SessionManager {
private static sessions = new Map<string, SessionManager>(); private static sessions: Map<string, SessionManager> = sessions;
readonly id: string; readonly id: string;
private blocks = new Map<string, Block>(); private blocks = new Map<string, Block>();
private events: { event: string; data: any }[] = []; private events: { event: string; data: any }[] = [];
@@ -67,15 +73,32 @@ class SessionManager {
} }
} }
addListener(event: string, listener: (data: any) => void) { getAllBlocks() {
this.emitter.addListener(event, listener); return Array.from(this.blocks.values());
} }
replay() { subscribe(listener: (event: string, data: any) => void): () => void {
for (const { event, data } of this.events) { const currentEventsLength = this.events.length;
/* Using emitter directly to avoid infinite loop */
this.emitter.emit(event, data); const handler = (event: string) => (data: any) => listener(event, data);
const dataHandler = handler('data');
const endHandler = handler('end');
const errorHandler = handler('error');
this.emitter.on('data', dataHandler);
this.emitter.on('end', endHandler);
this.emitter.on('error', errorHandler);
for (let i = 0; i < currentEventsLength; i++) {
const { event, data } = this.events[i];
listener(event, data);
} }
return () => {
this.emitter.off('data', dataHandler);
this.emitter.off('end', endHandler);
this.emitter.off('error', errorHandler);
};
} }
} }

View File

@@ -91,7 +91,7 @@ class UploadManager {
case 'text/plain': case 'text/plain':
const content = fs.readFileSync(filePath, 'utf-8'); const content = fs.readFileSync(filePath, 'utf-8');
const splittedText = splitText(content, 256, 64) const splittedText = splitText(content, 512, 128)
const embeddings = await this.embeddingModel.embedText(splittedText) const embeddings = await this.embeddingModel.embedText(splittedText)
if (embeddings.length !== splittedText.length) { if (embeddings.length !== splittedText.length) {
@@ -121,7 +121,7 @@ class UploadManager {
const pdfText = await parser.getText().then(res => res.text) const pdfText = await parser.getText().then(res => res.text)
const pdfSplittedText = splitText(pdfText, 256, 64) const pdfSplittedText = splitText(pdfText, 512, 128)
const pdfEmbeddings = await this.embeddingModel.embedText(pdfSplittedText) const pdfEmbeddings = await this.embeddingModel.embedText(pdfSplittedText)
if (pdfEmbeddings.length !== pdfSplittedText.length) { if (pdfEmbeddings.length !== pdfSplittedText.length) {
@@ -147,7 +147,7 @@ class UploadManager {
const docText = await officeParser.parseOfficeAsync(docBuffer) const docText = await officeParser.parseOfficeAsync(docBuffer)
const docSplittedText = splitText(docText, 256, 64) const docSplittedText = splitText(docText, 512, 128)
const docEmbeddings = await this.embeddingModel.embedText(docSplittedText) const docEmbeddings = await this.embeddingModel.embedText(docSplittedText)
if (docEmbeddings.length !== docSplittedText.length) { if (docEmbeddings.length !== docSplittedText.length) {

2
uploads/.gitignore vendored
View File

@@ -1,2 +0,0 @@
*
!.gitignore

620
yarn.lock
View File

@@ -336,6 +336,13 @@
dependencies: dependencies:
"@floating-ui/utils" "^0.2.8" "@floating-ui/utils" "^0.2.8"
"@floating-ui/core@^1.7.3":
version "1.7.3"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.7.3.tgz#462d722f001e23e46d86fd2bd0d21b7693ccb8b7"
integrity sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==
dependencies:
"@floating-ui/utils" "^0.2.10"
"@floating-ui/dom@^1.0.0": "@floating-ui/dom@^1.0.0":
version "1.6.12" version "1.6.12"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.12.tgz#6333dcb5a8ead3b2bf82f33d6bc410e95f54e556" resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.12.tgz#6333dcb5a8ead3b2bf82f33d6bc410e95f54e556"
@@ -344,6 +351,21 @@
"@floating-ui/core" "^1.6.0" "@floating-ui/core" "^1.6.0"
"@floating-ui/utils" "^0.2.8" "@floating-ui/utils" "^0.2.8"
"@floating-ui/dom@^1.7.4":
version "1.7.4"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.7.4.tgz#ee667549998745c9c3e3e84683b909c31d6c9a77"
integrity sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==
dependencies:
"@floating-ui/core" "^1.7.3"
"@floating-ui/utils" "^0.2.10"
"@floating-ui/react-dom@^2.0.0":
version "2.1.6"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.6.tgz#189f681043c1400561f62972f461b93f01bf2231"
integrity sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==
dependencies:
"@floating-ui/dom" "^1.7.4"
"@floating-ui/react-dom@^2.1.2": "@floating-ui/react-dom@^2.1.2":
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31" resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.2.tgz#a1349bbf6a0e5cb5ded55d023766f20a4d439a31"
@@ -360,11 +382,24 @@
"@floating-ui/utils" "^0.2.8" "@floating-ui/utils" "^0.2.8"
tabbable "^6.0.0" tabbable "^6.0.0"
"@floating-ui/utils@^0.2.10":
version "0.2.10"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.10.tgz#a2a1e3812d14525f725d011a73eceb41fef5bc1c"
integrity sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==
"@floating-ui/utils@^0.2.8": "@floating-ui/utils@^0.2.8":
version "0.2.8" version "0.2.8"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62" resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.8.tgz#21a907684723bbbaa5f0974cf7730bd797eb8e62"
integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig== integrity sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==
"@google/genai@^1.34.0":
version "1.34.0"
resolved "https://registry.yarnpkg.com/@google/genai/-/genai-1.34.0.tgz#8a6a85c2c7eb94afbb1a999967e828cae43ee6dd"
integrity sha512-vu53UMPvjmb7PGzlYu6Tzxso8Dfhn+a7eQFaS2uNemVtDZKwzSpJ5+ikqBbXplF7RGB1STcVDqCkPvquiwb2sw==
dependencies:
google-auth-library "^10.3.0"
ws "^8.18.0"
"@headlessui/react@^2.2.0": "@headlessui/react@^2.2.0":
version "2.2.0" version "2.2.0"
resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-2.2.0.tgz#a8e32f0899862849a1ce1615fa280e7891431ab7" resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-2.2.0.tgz#a8e32f0899862849a1ce1615fa280e7891431ab7"
@@ -380,17 +415,17 @@
resolved "https://registry.yarnpkg.com/@headlessui/tailwindcss/-/tailwindcss-0.2.2.tgz#8ebde73fabca72d48636ea56ae790209dc5f0d49" resolved "https://registry.yarnpkg.com/@headlessui/tailwindcss/-/tailwindcss-0.2.2.tgz#8ebde73fabca72d48636ea56ae790209dc5f0d49"
integrity sha512-xNe42KjdyA4kfUKLLPGzME9zkH7Q3rOZ5huFihWNWOQFxnItxPB3/67yBI8/qBfY8nwBRx5GHn4VprsoluVMGw== integrity sha512-xNe42KjdyA4kfUKLLPGzME9zkH7Q3rOZ5huFihWNWOQFxnItxPB3/67yBI8/qBfY8nwBRx5GHn4VprsoluVMGw==
"@huggingface/jinja@^0.5.1": "@huggingface/jinja@^0.5.3":
version "0.5.1" version "0.5.3"
resolved "https://registry.yarnpkg.com/@huggingface/jinja/-/jinja-0.5.1.tgz#081d334ddcf6237f65561ae3d665bb713a8ff74f" resolved "https://registry.yarnpkg.com/@huggingface/jinja/-/jinja-0.5.3.tgz#8182a2d6fc9f84c1539fa12c219a5915cdc41264"
integrity sha512-yUZLld4lrM9iFxHCwFQ7D1HW2MWMwSbeB7WzWqFYDWK+rEb+WldkLdAJxUPOmgICMHZLzZGVcVjFh3w/YGubng== integrity sha512-asqfZ4GQS0hD876Uw4qiUb7Tr/V5Q+JZuo2L+BtdrD4U40QU58nIRq3ZSgAzJgT874VLjhGVacaYfrdpXtEvtA==
"@huggingface/transformers@^3.7.5": "@huggingface/transformers@^3.8.1":
version "3.7.5" version "3.8.1"
resolved "https://registry.yarnpkg.com/@huggingface/transformers/-/transformers-3.7.5.tgz#63d6b5792c74904168959561c6a05a05d680092f" resolved "https://registry.yarnpkg.com/@huggingface/transformers/-/transformers-3.8.1.tgz#317da003865322396796173223eeaaf0f9723f0a"
integrity sha512-5jvrIwHyRXfOKVaGKYvUZM6ZjJKQXWeKzIOdKBE5pdzPSNzTwBNx5NdWcGElf4Ddv7Dl2mWsvJh+G5RnCUxMmA== integrity sha512-tsTk4zVjImqdqjS8/AOZg2yNLd1z9S5v+7oUPpXaasDRwEDhB+xnglK1k5cad26lL5/ZIaeREgWWy0bs9y9pPA==
dependencies: dependencies:
"@huggingface/jinja" "^0.5.1" "@huggingface/jinja" "^0.5.3"
onnxruntime-node "1.21.0" onnxruntime-node "1.21.0"
onnxruntime-web "1.22.0-dev.20250409-89f8206ba4" onnxruntime-web "1.22.0-dev.20250409-89f8206ba4"
sharp "^0.34.1" sharp "^0.34.1"
@@ -962,6 +997,11 @@
resolved "https://registry.yarnpkg.com/@petamoriken/float16/-/float16-3.9.2.tgz#217a5d349f3655b8e286be447e0ed1eae063a78f" resolved "https://registry.yarnpkg.com/@petamoriken/float16/-/float16-3.9.2.tgz#217a5d349f3655b8e286be447e0ed1eae063a78f"
integrity sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog== integrity sha512-VgffxawQde93xKxT3qap3OH+meZf7VaSB5Sqd4Rqc+FP5alWbpOyan/7tRbOAvynjpG3GpdtAuGU/NdhQpmrog==
"@phosphor-icons/react@^2.1.10":
version "2.1.10"
resolved "https://registry.yarnpkg.com/@phosphor-icons/react/-/react-2.1.10.tgz#3a97ec5b7a4b8d53afeb29125bc17e74ed2daf92"
integrity sha512-vt8Tvq8GLjheAZZYa+YG/pW7HDbov8El/MANW8pOAz4eGxrwhnbfrQZq0Cp4q8zBEu8NIhHdnr+r8thnfRSNYA==
"@pkgjs/parseargs@^0.11.0": "@pkgjs/parseargs@^0.11.0":
version "0.11.0" version "0.11.0"
resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33"
@@ -1020,6 +1060,168 @@
resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570" resolved "https://registry.yarnpkg.com/@protobufjs/utf8/-/utf8-1.1.0.tgz#a777360b5b39a1a2e5106f8e858f2fd2d060c570"
integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw== integrity sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==
"@radix-ui/primitive@1.1.3":
version "1.1.3"
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.1.3.tgz#e2dbc13bdc5e4168f4334f75832d7bdd3e2de5ba"
integrity sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==
"@radix-ui/react-arrow@1.1.7":
version "1.1.7"
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz#e14a2657c81d961598c5e72b73dd6098acc04f09"
integrity sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==
dependencies:
"@radix-ui/react-primitive" "2.1.3"
"@radix-ui/react-compose-refs@1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz#a2c4c47af6337048ee78ff6dc0d090b390d2bb30"
integrity sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==
"@radix-ui/react-context@1.1.2":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.1.2.tgz#61628ef269a433382c364f6f1e3788a6dc213a36"
integrity sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==
"@radix-ui/react-dismissable-layer@1.1.11":
version "1.1.11"
resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz#e33ab6f6bdaa00f8f7327c408d9f631376b88b37"
integrity sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==
dependencies:
"@radix-ui/primitive" "1.1.3"
"@radix-ui/react-compose-refs" "1.1.2"
"@radix-ui/react-primitive" "2.1.3"
"@radix-ui/react-use-callback-ref" "1.1.1"
"@radix-ui/react-use-escape-keydown" "1.1.1"
"@radix-ui/react-id@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.1.1.tgz#1404002e79a03fe062b7e3864aa01e24bd1471f7"
integrity sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==
dependencies:
"@radix-ui/react-use-layout-effect" "1.1.1"
"@radix-ui/react-popper@1.2.8":
version "1.2.8"
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.2.8.tgz#a79f39cdd2b09ab9fb50bf95250918422c4d9602"
integrity sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==
dependencies:
"@floating-ui/react-dom" "^2.0.0"
"@radix-ui/react-arrow" "1.1.7"
"@radix-ui/react-compose-refs" "1.1.2"
"@radix-ui/react-context" "1.1.2"
"@radix-ui/react-primitive" "2.1.3"
"@radix-ui/react-use-callback-ref" "1.1.1"
"@radix-ui/react-use-layout-effect" "1.1.1"
"@radix-ui/react-use-rect" "1.1.1"
"@radix-ui/react-use-size" "1.1.1"
"@radix-ui/rect" "1.1.1"
"@radix-ui/react-portal@1.1.9":
version "1.1.9"
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.1.9.tgz#14c3649fe48ec474ac51ed9f2b9f5da4d91c4472"
integrity sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==
dependencies:
"@radix-ui/react-primitive" "2.1.3"
"@radix-ui/react-use-layout-effect" "1.1.1"
"@radix-ui/react-presence@1.1.5":
version "1.1.5"
resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.1.5.tgz#5d8f28ac316c32f078afce2996839250c10693db"
integrity sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==
dependencies:
"@radix-ui/react-compose-refs" "1.1.2"
"@radix-ui/react-use-layout-effect" "1.1.1"
"@radix-ui/react-primitive@2.1.3":
version "2.1.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz#db9b8bcff49e01be510ad79893fb0e4cda50f1bc"
integrity sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==
dependencies:
"@radix-ui/react-slot" "1.2.3"
"@radix-ui/react-slot@1.2.3":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.2.3.tgz#502d6e354fc847d4169c3bc5f189de777f68cfe1"
integrity sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==
dependencies:
"@radix-ui/react-compose-refs" "1.1.2"
"@radix-ui/react-tooltip@^1.2.8":
version "1.2.8"
resolved "https://registry.yarnpkg.com/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz#3f50267e25bccfc9e20bb3036bfd9ab4c2c30c2c"
integrity sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==
dependencies:
"@radix-ui/primitive" "1.1.3"
"@radix-ui/react-compose-refs" "1.1.2"
"@radix-ui/react-context" "1.1.2"
"@radix-ui/react-dismissable-layer" "1.1.11"
"@radix-ui/react-id" "1.1.1"
"@radix-ui/react-popper" "1.2.8"
"@radix-ui/react-portal" "1.1.9"
"@radix-ui/react-presence" "1.1.5"
"@radix-ui/react-primitive" "2.1.3"
"@radix-ui/react-slot" "1.2.3"
"@radix-ui/react-use-controllable-state" "1.2.2"
"@radix-ui/react-visually-hidden" "1.2.3"
"@radix-ui/react-use-callback-ref@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz#62a4dba8b3255fdc5cc7787faeac1c6e4cc58d40"
integrity sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==
"@radix-ui/react-use-controllable-state@1.2.2":
version "1.2.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz#905793405de57d61a439f4afebbb17d0645f3190"
integrity sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==
dependencies:
"@radix-ui/react-use-effect-event" "0.0.2"
"@radix-ui/react-use-layout-effect" "1.1.1"
"@radix-ui/react-use-effect-event@0.0.2":
version "0.0.2"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz#090cf30d00a4c7632a15548512e9152217593907"
integrity sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==
dependencies:
"@radix-ui/react-use-layout-effect" "1.1.1"
"@radix-ui/react-use-escape-keydown@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz#b3fed9bbea366a118f40427ac40500aa1423cc29"
integrity sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==
dependencies:
"@radix-ui/react-use-callback-ref" "1.1.1"
"@radix-ui/react-use-layout-effect@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz#0c4230a9eed49d4589c967e2d9c0d9d60a23971e"
integrity sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==
"@radix-ui/react-use-rect@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz#01443ca8ed071d33023c1113e5173b5ed8769152"
integrity sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==
dependencies:
"@radix-ui/rect" "1.1.1"
"@radix-ui/react-use-size@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz#6de276ffbc389a537ffe4316f5b0f24129405b37"
integrity sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==
dependencies:
"@radix-ui/react-use-layout-effect" "1.1.1"
"@radix-ui/react-visually-hidden@1.2.3":
version "1.2.3"
resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz#a8c38c8607735dc9f05c32f87ab0f9c2b109efbf"
integrity sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==
dependencies:
"@radix-ui/react-primitive" "2.1.3"
"@radix-ui/rect@1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.1.1.tgz#78244efe12930c56fd255d7923865857c41ac8cb"
integrity sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==
"@react-aria/focus@^3.17.1": "@react-aria/focus@^3.17.1":
version "3.18.4" version "3.18.4"
resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.18.4.tgz#a6e95896bc8680d1b5bcd855e983fc2c195a1a55" resolved "https://registry.yarnpkg.com/@react-aria/focus/-/focus-3.18.4.tgz#a6e95896bc8680d1b5bcd855e983fc2c195a1a55"
@@ -1117,6 +1319,13 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/hast@^3.0.0":
version "3.0.4"
resolved "https://registry.yarnpkg.com/@types/hast/-/hast-3.0.4.tgz#1d6b39993b82cea6ad783945b0508c25903e15aa"
integrity sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==
dependencies:
"@types/unist" "*"
"@types/json5@^0.0.29": "@types/json5@^0.0.29":
version "0.0.29" version "0.0.29"
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
@@ -1160,6 +1369,11 @@
resolved "https://registry.yarnpkg.com/@types/pdf-parse/-/pdf-parse-1.1.4.tgz#21a539efd2f16009d08aeed3350133948b5d7ed1" resolved "https://registry.yarnpkg.com/@types/pdf-parse/-/pdf-parse-1.1.4.tgz#21a539efd2f16009d08aeed3350133948b5d7ed1"
integrity sha512-+gbBHbNCVGGYw1S9lAIIvrHW47UYOhMIFUsJcMkMrzy1Jf0vulBN3XQIjPgnoOXveMuHnF3b57fXROnY/Or7eg== integrity sha512-+gbBHbNCVGGYw1S9lAIIvrHW47UYOhMIFUsJcMkMrzy1Jf0vulBN3XQIjPgnoOXveMuHnF3b57fXROnY/Or7eg==
"@types/prismjs@^1.0.0":
version "1.26.5"
resolved "https://registry.yarnpkg.com/@types/prismjs/-/prismjs-1.26.5.tgz#72499abbb4c4ec9982446509d2f14fb8483869d6"
integrity sha512-AUZTa7hQ2KY5L7AmtSiqxlhWxb4ina0yd8hNbl4TWuqnv/pFP0nDMb3YrfSBf4hJVGLh2YEIBfKaBW/9UEl6IQ==
"@types/prop-types@*": "@types/prop-types@*":
version "15.7.12" version "15.7.12"
resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6"
@@ -1177,6 +1391,13 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react-syntax-highlighter@^15.5.13":
version "15.5.13"
resolved "https://registry.yarnpkg.com/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz#c5baf62a3219b3bf28d39cfea55d0a49a263d1f2"
integrity sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^18": "@types/react@*", "@types/react@^18":
version "18.2.74" version "18.2.74"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.74.tgz#2d52eb80e4e7c4ea8812c89181d6d590b53f958c" resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.74.tgz#2d52eb80e4e7c4ea8812c89181d6d590b53f958c"
@@ -1195,6 +1416,16 @@
resolved "https://registry.yarnpkg.com/@types/turndown/-/turndown-5.0.6.tgz#42a27397298a312d6088f29c0ff4819c518c1ecb" resolved "https://registry.yarnpkg.com/@types/turndown/-/turndown-5.0.6.tgz#42a27397298a312d6088f29c0ff4819c518c1ecb"
integrity sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg== integrity sha512-ru00MoyeeouE5BX4gRL+6m/BsDfbRayOskWqUvh7CLGW+UXxHQItqALa38kKnOiZPqJrtzJUgAC2+F0rL1S4Pg==
"@types/unist@*":
version "3.0.3"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c"
integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==
"@types/unist@^2.0.0":
version "2.0.11"
resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4"
integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==
"@typescript-eslint/parser@^5.4.2 || ^6.0.0": "@typescript-eslint/parser@^5.4.2 || ^6.0.0":
version "6.21.0" version "6.21.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b" resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b"
@@ -1273,6 +1504,11 @@ acorn@^8.9.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a"
integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==
agent-base@^7.1.2:
version "7.1.4"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-7.1.4.tgz#e3cd76d4c548ee895d3c3fd8dc1f6c5b9032e7a8"
integrity sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==
ajv@^6.12.4: ajv@^6.12.4:
version "6.12.6" version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
@@ -1506,7 +1742,7 @@ base64-arraybuffer@^1.0.2:
resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc"
integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ== integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==
base64-js@^1.3.1, base64-js@^1.5.1: base64-js@^1.3.0, base64-js@^1.3.1, base64-js@^1.5.1:
version "1.5.1" version "1.5.1"
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA== integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
@@ -1519,6 +1755,11 @@ better-sqlite3@^11.9.1:
bindings "^1.5.0" bindings "^1.5.0"
prebuild-install "^7.1.1" prebuild-install "^7.1.1"
bignumber.js@^9.0.0:
version "9.3.1"
resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-9.3.1.tgz#759c5aaddf2ffdc4f154f7b493e1c8770f88c4d7"
integrity sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==
binary-extensions@^2.0.0: binary-extensions@^2.0.0:
version "2.3.0" version "2.3.0"
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522"
@@ -1587,6 +1828,11 @@ buffer-crc32@~0.2.3:
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
buffer-equal-constant-time@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==
buffer-from@^1.0.0: buffer-from@^1.0.0:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
@@ -1661,6 +1907,21 @@ chalk@^4.0.0:
ansi-styles "^4.1.0" ansi-styles "^4.1.0"
supports-color "^7.1.0" supports-color "^7.1.0"
character-entities-legacy@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b"
integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==
character-entities@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/character-entities/-/character-entities-2.0.2.tgz#2d09c2e72cd9523076ccb21157dff66ad43fcc22"
integrity sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==
character-reference-invalid@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/character-reference-invalid/-/character-reference-invalid-2.0.1.tgz#85c66b041e43b47210faf401278abf808ac45cb9"
integrity sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==
chokidar@^3.5.3: chokidar@^3.5.3:
version "3.6.0" version "3.6.0"
resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b"
@@ -1720,6 +1981,11 @@ combined-stream@^1.0.8:
dependencies: dependencies:
delayed-stream "~1.0.0" delayed-stream "~1.0.0"
comma-separated-tokens@^2.0.0:
version "2.0.3"
resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee"
integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
commander@^4.0.0: commander@^4.0.0:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068" resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
@@ -1786,6 +2052,11 @@ damerau-levenshtein@^1.0.8:
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7" resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz#b43d286ccbd36bc5b2f7ed41caf2d0aba1f8a6e7"
integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA== integrity sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==
data-uri-to-buffer@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz#d8feb2b2881e6a4f58c2e08acfd0e2834e26222e"
integrity sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==
data-view-buffer@^1.0.1: data-view-buffer@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2" resolved "https://registry.yarnpkg.com/data-view-buffer/-/data-view-buffer-1.0.1.tgz#8ea6326efec17a2e42620696e671d7d5a8bc66b2"
@@ -1813,6 +2084,13 @@ data-view-byte-offset@^1.0.0:
es-errors "^1.3.0" es-errors "^1.3.0"
is-data-view "^1.0.1" is-data-view "^1.0.1"
debug@4:
version "4.4.3"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
dependencies:
ms "^2.1.3"
debug@^3.2.7: debug@^3.2.7:
version "3.2.7" version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
@@ -1832,6 +2110,13 @@ decimal.js@^10.4.3:
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.6.0.tgz#e649a43e3ab953a72192ff5983865e509f37ed9a"
integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg== integrity sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==
decode-named-character-reference@^1.0.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz#25c32ae6dd5e21889549d40f676030e9514cc0ed"
integrity sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==
dependencies:
character-entities "^2.0.0"
decompress-response@^6.0.0: decompress-response@^6.0.0:
version "6.0.0" version "6.0.0"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-6.0.0.tgz#ca387612ddb7e104bd16d85aab00d5ecf09c66fc"
@@ -1963,6 +2248,13 @@ eastasianwidth@^0.2.0:
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
ecdsa-sig-formatter@1.0.11, ecdsa-sig-formatter@^1.0.11:
version "1.0.11"
resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf"
integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==
dependencies:
safe-buffer "^5.0.1"
electron-to-chromium@^1.4.668: electron-to-chromium@^1.4.668:
version "1.4.729" version "1.4.729"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.729.tgz#8477d21e2a50993781950885b2731d92ad532c00" resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.729.tgz#8477d21e2a50993781950885b2731d92ad532c00"
@@ -2426,6 +2718,11 @@ expand-template@^2.0.3:
resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c"
integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==
extend@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
fancy-canvas@2.1.0: fancy-canvas@2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/fancy-canvas/-/fancy-canvas-2.1.0.tgz#44b40e40419ad8ef8304df365e4276767e918552" resolved "https://registry.yarnpkg.com/fancy-canvas/-/fancy-canvas-2.1.0.tgz#44b40e40419ad8ef8304df365e4276767e918552"
@@ -2473,6 +2770,21 @@ fastq@^1.6.0:
dependencies: dependencies:
reusify "^1.0.4" reusify "^1.0.4"
fault@^1.0.0:
version "1.0.4"
resolved "https://registry.yarnpkg.com/fault/-/fault-1.0.4.tgz#eafcfc0a6d214fc94601e170df29954a4f842f13"
integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==
dependencies:
format "^0.2.0"
fetch-blob@^3.1.2, fetch-blob@^3.1.4:
version "3.2.0"
resolved "https://registry.yarnpkg.com/fetch-blob/-/fetch-blob-3.2.0.tgz#f09b8d4bbd45adc6f0c20b7e787e793e309dcce9"
integrity sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==
dependencies:
node-domexception "^1.0.0"
web-streams-polyfill "^3.0.3"
"fetch-mock-cache@npm:fetch-mock-cache@^2.1.3": "fetch-mock-cache@npm:fetch-mock-cache@^2.1.3":
version "2.3.1" version "2.3.1"
resolved "https://registry.yarnpkg.com/fetch-mock-cache/-/fetch-mock-cache-2.3.1.tgz#1018f5fc2f91cf2511abcea8a5e3a3b05e2d02bf" resolved "https://registry.yarnpkg.com/fetch-mock-cache/-/fetch-mock-cache-2.3.1.tgz#1018f5fc2f91cf2511abcea8a5e3a3b05e2d02bf"
@@ -2592,6 +2904,18 @@ form-data@^4.0.0:
combined-stream "^1.0.8" combined-stream "^1.0.8"
mime-types "^2.1.12" mime-types "^2.1.12"
format@^0.2.0:
version "0.2.2"
resolved "https://registry.yarnpkg.com/format/-/format-0.2.2.tgz#d6170107e9efdc4ed30c9dc39016df942b5cb58b"
integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==
formdata-polyfill@^4.0.10:
version "4.0.10"
resolved "https://registry.yarnpkg.com/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz#24807c31c9d402e002ab3d8c720144ceb8848423"
integrity sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==
dependencies:
fetch-blob "^3.1.2"
fraction.js@^4.3.7: fraction.js@^4.3.7:
version "4.3.7" version "4.3.7"
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.3.7.tgz#06ca0085157e42fda7f9e726e79fefc4068840f7"
@@ -2602,10 +2926,10 @@ fraction.js@^5.2.1:
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-5.3.4.tgz#8c0fcc6a9908262df4ed197427bdeef563e0699a" resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-5.3.4.tgz#8c0fcc6a9908262df4ed197427bdeef563e0699a"
integrity sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ== integrity sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==
framer-motion@^12.23.25: framer-motion@^12.23.26:
version "12.23.25" version "12.23.26"
resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-12.23.25.tgz#32d717f8b172c2673f573c0805ecc37d017441b2" resolved "https://registry.yarnpkg.com/framer-motion/-/framer-motion-12.23.26.tgz#2a684e9b156118e1c4989d7fc9327def83480391"
integrity sha512-gUHGl2e4VG66jOcH0JHhuJQr6ZNwrET9g31ZG0xdXzT0CznP7fHX4P8Bcvuc4MiUB90ysNnWX2ukHRIggkl6hQ== integrity sha512-cPcIhgR42xBn1Uj+PzOyheMtZ73H927+uWPDVhUMqxy8UHt6Okavb6xIz9J/phFUHUj0OncR6UvMfJTXoc/LKA==
dependencies: dependencies:
motion-dom "^12.23.23" motion-dom "^12.23.23"
motion-utils "^12.23.6" motion-utils "^12.23.6"
@@ -2646,6 +2970,25 @@ functions-have-names@^1.2.3:
resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
gaxios@^7.0.0:
version "7.1.3"
resolved "https://registry.yarnpkg.com/gaxios/-/gaxios-7.1.3.tgz#c5312f4254abc1b8ab53aef30c22c5229b80b1e1"
integrity sha512-YGGyuEdVIjqxkxVH1pUTMY/XtmmsApXrCVv5EU25iX6inEPbV+VakJfLealkBtJN69AQmh1eGOdCl9Sm1UP6XQ==
dependencies:
extend "^3.0.2"
https-proxy-agent "^7.0.1"
node-fetch "^3.3.2"
rimraf "^5.0.1"
gcp-metadata@^8.0.0:
version "8.1.2"
resolved "https://registry.yarnpkg.com/gcp-metadata/-/gcp-metadata-8.1.2.tgz#e62e3373ddf41fc727ccc31c55c687b798bee898"
integrity sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==
dependencies:
gaxios "^7.0.0"
google-logging-utils "^1.0.0"
json-bigint "^1.0.0"
gel@^2.0.0: gel@^2.0.0:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/gel/-/gel-2.0.1.tgz#587d89db54351c2d436de981d136481e37d06a7a" resolved "https://registry.yarnpkg.com/gel/-/gel-2.0.1.tgz#587d89db54351c2d436de981d136481e37d06a7a"
@@ -2733,6 +3076,18 @@ glob@^10.3.10:
minipass "^7.0.4" minipass "^7.0.4"
path-scurry "^1.10.2" path-scurry "^1.10.2"
glob@^10.3.7:
version "10.5.0"
resolved "https://registry.yarnpkg.com/glob/-/glob-10.5.0.tgz#8ec0355919cd3338c28428a23d4f24ecc5fe738c"
integrity sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==
dependencies:
foreground-child "^3.1.0"
jackspeak "^3.1.2"
minimatch "^9.0.4"
minipass "^7.1.2"
package-json-from-dist "^1.0.0"
path-scurry "^1.11.1"
glob@^7.1.3: glob@^7.1.3:
version "7.2.3" version "7.2.3"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
@@ -2791,6 +3146,24 @@ globby@^11.1.0:
merge2 "^1.4.1" merge2 "^1.4.1"
slash "^3.0.0" slash "^3.0.0"
google-auth-library@^10.3.0:
version "10.5.0"
resolved "https://registry.yarnpkg.com/google-auth-library/-/google-auth-library-10.5.0.tgz#3f0ebd47173496b91d2868f572bb8a8180c4b561"
integrity sha512-7ABviyMOlX5hIVD60YOfHw4/CxOfBhyduaYB+wbFWCWoni4N7SLcV46hrVRktuBbZjFC9ONyqamZITN7q3n32w==
dependencies:
base64-js "^1.3.0"
ecdsa-sig-formatter "^1.0.11"
gaxios "^7.0.0"
gcp-metadata "^8.0.0"
google-logging-utils "^1.0.0"
gtoken "^8.0.0"
jws "^4.0.0"
google-logging-utils@^1.0.0:
version "1.1.3"
resolved "https://registry.yarnpkg.com/google-logging-utils/-/google-logging-utils-1.1.3.tgz#17b71f1f95d266d2ddd356b8f00178433f041b17"
integrity sha512-eAmLkjDjAFCVXg7A1unxHsLf961m6y17QFqXqAXGj/gVkKFrEICfStRfwUlGNfeCEjNRa32JEWOUTlYXPyyKvA==
gopd@^1.0.1: gopd@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
@@ -2808,6 +3181,14 @@ graphemer@^1.4.0:
resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
gtoken@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/gtoken/-/gtoken-8.0.0.tgz#d67a0e346dd441bfb54ad14040ddc3b632886575"
integrity sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==
dependencies:
gaxios "^7.0.0"
jws "^4.0.0"
guid-typescript@^1.0.9: guid-typescript@^1.0.9:
version "1.0.9" version "1.0.9"
resolved "https://registry.yarnpkg.com/guid-typescript/-/guid-typescript-1.0.9.tgz#e35f77003535b0297ea08548f5ace6adb1480ddc" resolved "https://registry.yarnpkg.com/guid-typescript/-/guid-typescript-1.0.9.tgz#e35f77003535b0297ea08548f5ace6adb1480ddc"
@@ -2854,6 +3235,34 @@ hasown@^2.0.0, hasown@^2.0.1, hasown@^2.0.2:
dependencies: dependencies:
function-bind "^1.1.2" function-bind "^1.1.2"
hast-util-parse-selector@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz#352879fa86e25616036037dd8931fb5f34cb4a27"
integrity sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==
dependencies:
"@types/hast" "^3.0.0"
hastscript@^9.0.0:
version "9.0.1"
resolved "https://registry.yarnpkg.com/hastscript/-/hastscript-9.0.1.tgz#dbc84bef6051d40084342c229c451cd9dc567dff"
integrity sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==
dependencies:
"@types/hast" "^3.0.0"
comma-separated-tokens "^2.0.0"
hast-util-parse-selector "^4.0.0"
property-information "^7.0.0"
space-separated-tokens "^2.0.0"
highlight.js@^10.4.1, highlight.js@~10.7.0:
version "10.7.3"
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-10.7.3.tgz#697272e3991356e40c3cac566a74eef681756531"
integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==
highlightjs-vue@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz#fdfe97fbea6354e70ee44e3a955875e114db086d"
integrity sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==
html2canvas@^1.0.0-rc.5: html2canvas@^1.0.0-rc.5:
version "1.4.1" version "1.4.1"
resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543" resolved "https://registry.yarnpkg.com/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543"
@@ -2862,6 +3271,14 @@ html2canvas@^1.0.0-rc.5:
css-line-break "^2.1.0" css-line-break "^2.1.0"
text-segmentation "^1.0.3" text-segmentation "^1.0.3"
https-proxy-agent@^7.0.1:
version "7.0.6"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz#da8dfeac7da130b05c2ba4b59c9b6cd66611a6b9"
integrity sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==
dependencies:
agent-base "^7.1.2"
debug "4"
humanize-url@^2.1.1: humanize-url@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/humanize-url/-/humanize-url-2.1.1.tgz#1be3dc2b8a23ee28fdf9db95b22962b3eb5e4683" resolved "https://registry.yarnpkg.com/humanize-url/-/humanize-url-2.1.1.tgz#1be3dc2b8a23ee28fdf9db95b22962b3eb5e4683"
@@ -2929,6 +3346,19 @@ iobuffer@^5.3.2:
resolved "https://registry.yarnpkg.com/iobuffer/-/iobuffer-5.4.0.tgz#f85dff957fd0579257472f0a4cfe5ed3430e63e1" resolved "https://registry.yarnpkg.com/iobuffer/-/iobuffer-5.4.0.tgz#f85dff957fd0579257472f0a4cfe5ed3430e63e1"
integrity sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA== integrity sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==
is-alphabetical@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-alphabetical/-/is-alphabetical-2.0.1.tgz#01072053ea7c1036df3c7d19a6daaec7f19e789b"
integrity sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==
is-alphanumerical@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz#7c03fbe96e3e931113e57f964b0a368cc2dfd875"
integrity sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==
dependencies:
is-alphabetical "^2.0.0"
is-decimal "^2.0.0"
is-array-buffer@^3.0.4: is-array-buffer@^3.0.4:
version "3.0.4" version "3.0.4"
resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98" resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.4.tgz#7a1f92b3d61edd2bc65d24f130530ea93d7fae98"
@@ -2992,6 +3422,11 @@ is-date-object@^1.0.1, is-date-object@^1.0.5:
dependencies: dependencies:
has-tostringtag "^1.0.0" has-tostringtag "^1.0.0"
is-decimal@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-decimal/-/is-decimal-2.0.1.tgz#9469d2dc190d0214fd87d78b78caecc0cc14eef7"
integrity sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==
is-extglob@^2.1.1: is-extglob@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
@@ -3023,6 +3458,11 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1:
dependencies: dependencies:
is-extglob "^2.1.1" is-extglob "^2.1.1"
is-hexadecimal@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-hexadecimal/-/is-hexadecimal-2.0.1.tgz#86b5bf668fca307498d319dfc03289d781a90027"
integrity sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==
is-map@^2.0.3: is-map@^2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e" resolved "https://registry.yarnpkg.com/is-map/-/is-map-2.0.3.tgz#ede96b7fe1e270b3c4465e3a465658764926d62e"
@@ -3151,6 +3591,15 @@ jackspeak@^2.3.5, jackspeak@^2.3.6:
optionalDependencies: optionalDependencies:
"@pkgjs/parseargs" "^0.11.0" "@pkgjs/parseargs" "^0.11.0"
jackspeak@^3.1.2:
version "3.4.3"
resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a"
integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==
dependencies:
"@isaacs/cliui" "^8.0.2"
optionalDependencies:
"@pkgjs/parseargs" "^0.11.0"
javascript-natural-sort@^0.7.1: javascript-natural-sort@^0.7.1:
version "0.7.1" version "0.7.1"
resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59" resolved "https://registry.yarnpkg.com/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz#f9e2303d4507f6d74355a73664d1440fb5a0ef59"
@@ -3180,6 +3629,13 @@ js-yaml@^4.1.0:
dependencies: dependencies:
argparse "^2.0.1" argparse "^2.0.1"
json-bigint@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1"
integrity sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==
dependencies:
bignumber.js "^9.0.0"
json-buffer@3.0.1: json-buffer@3.0.1:
version "3.0.1" version "3.0.1"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13"
@@ -3246,6 +3702,23 @@ jszip@^3.7.1:
readable-stream "~2.3.6" readable-stream "~2.3.6"
setimmediate "^1.0.5" setimmediate "^1.0.5"
jwa@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/jwa/-/jwa-2.0.1.tgz#bf8176d1ad0cd72e0f3f58338595a13e110bc804"
integrity sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==
dependencies:
buffer-equal-constant-time "^1.0.1"
ecdsa-sig-formatter "1.0.11"
safe-buffer "^5.0.1"
jws@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/jws/-/jws-4.0.1.tgz#07edc1be8fac20e677b283ece261498bd38f0690"
integrity sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==
dependencies:
jwa "^2.0.1"
safe-buffer "^5.0.1"
keyv@^4.5.3: keyv@^4.5.3:
version "4.5.4" version "4.5.4"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93"
@@ -3345,6 +3818,14 @@ lop@^0.4.2:
option "~0.2.1" option "~0.2.1"
underscore "^1.13.1" underscore "^1.13.1"
lowlight@^1.17.0:
version "1.20.0"
resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.20.0.tgz#ddb197d33462ad0d93bf19d17b6c301aa3941888"
integrity sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==
dependencies:
fault "^1.0.0"
highlight.js "~10.7.0"
lru-cache@^10.2.0: lru-cache@^10.2.0:
version "10.2.0" version "10.2.0"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.2.0.tgz#0bd445ca57363465900f4d1f9bd8db343a4d95c3"
@@ -3456,6 +3937,13 @@ minimatch@^9.0.1:
dependencies: dependencies:
brace-expansion "^2.0.1" brace-expansion "^2.0.1"
minimatch@^9.0.4:
version "9.0.5"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5"
integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==
dependencies:
brace-expansion "^2.0.1"
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6: minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6:
version "1.2.8" version "1.2.8"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c"
@@ -3495,12 +3983,20 @@ motion-utils@^12.23.6:
resolved "https://registry.yarnpkg.com/motion-utils/-/motion-utils-12.23.6.tgz#fafef80b4ea85122dd0d6c599a0c63d72881f312" resolved "https://registry.yarnpkg.com/motion-utils/-/motion-utils-12.23.6.tgz#fafef80b4ea85122dd0d6c599a0c63d72881f312"
integrity sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ== integrity sha512-eAWoPgr4eFEOFfg2WjIsMoqJTW6Z8MTUCgn/GZ3VRpClWBdnbjryiA3ZSNLyxCTmCQx4RmYX6jX1iWHbenUPNQ==
motion@^12.23.26:
version "12.23.26"
resolved "https://registry.yarnpkg.com/motion/-/motion-12.23.26.tgz#7309d3f13df43795b774fa98821c6ee5d6fab4f1"
integrity sha512-Ll8XhVxY8LXMVYTCfme27WH2GjBrCIzY4+ndr5QKxsK+YwCtOi2B/oBi5jcIbik5doXuWT/4KKDOVAZJkeY5VQ==
dependencies:
framer-motion "^12.23.26"
tslib "^2.4.0"
ms@2.1.2: ms@2.1.2:
version "2.1.2" version "2.1.2"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
ms@^2.1.1: ms@^2.1.1, ms@^2.1.3:
version "2.1.3" version "2.1.3"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
@@ -3567,11 +4063,25 @@ node-abi@^3.3.0:
dependencies: dependencies:
semver "^7.3.5" semver "^7.3.5"
node-domexception@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5"
integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==
node-ensure@^0.0.0: node-ensure@^0.0.0:
version "0.0.0" version "0.0.0"
resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7" resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7"
integrity sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw== integrity sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==
node-fetch@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-3.3.2.tgz#d1e889bacdf733b4ff3b2b243eb7a12866a0b78b"
integrity sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==
dependencies:
data-uri-to-buffer "^4.0.0"
fetch-blob "^3.1.4"
formdata-polyfill "^4.0.10"
node-releases@^2.0.14: node-releases@^2.0.14:
version "2.0.14" version "2.0.14"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b"
@@ -3761,6 +4271,11 @@ p-locate@^5.0.0:
dependencies: dependencies:
p-limit "^3.0.2" p-limit "^3.0.2"
package-json-from-dist@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz#4f1471a010827a86f94cfd9b0727e36d267de505"
integrity sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==
pako@^2.1.0: pako@^2.1.0:
version "2.1.0" version "2.1.0"
resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86" resolved "https://registry.yarnpkg.com/pako/-/pako-2.1.0.tgz#266cc37f98c7d883545d11335c00fbd4062c9a86"
@@ -3778,6 +4293,19 @@ parent-module@^1.0.0:
dependencies: dependencies:
callsites "^3.0.0" callsites "^3.0.0"
parse-entities@^4.0.0:
version "4.0.2"
resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-4.0.2.tgz#61d46f5ed28e4ee62e9ddc43d6b010188443f159"
integrity sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==
dependencies:
"@types/unist" "^2.0.0"
character-entities-legacy "^3.0.0"
character-reference-invalid "^2.0.0"
decode-named-character-reference "^1.0.0"
is-alphanumerical "^2.0.0"
is-decimal "^2.0.0"
is-hexadecimal "^2.0.0"
partial-json@^0.1.7: partial-json@^0.1.7:
version "0.1.7" version "0.1.7"
resolved "https://registry.yarnpkg.com/partial-json/-/partial-json-0.1.7.tgz#b735a89edb3e25f231a3c4caeaae71dc9f578605" resolved "https://registry.yarnpkg.com/partial-json/-/partial-json-0.1.7.tgz#b735a89edb3e25f231a3c4caeaae71dc9f578605"
@@ -3811,6 +4339,14 @@ path-scurry@^1.10.1, path-scurry@^1.10.2:
lru-cache "^10.2.0" lru-cache "^10.2.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
path-scurry@^1.11.1:
version "1.11.1"
resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2"
integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==
dependencies:
lru-cache "^10.2.0"
minipass "^5.0.0 || ^6.0.2 || ^7.0.0"
path-type@^4.0.0: path-type@^4.0.0:
version "4.0.0" version "4.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
@@ -3981,6 +4517,11 @@ prettier@^3.2.5:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368" resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.2.5.tgz#e52bc3090586e824964a8813b09aba6233b28368"
integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==
prismjs@^1.30.0:
version "1.30.0"
resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.30.0.tgz#d9709969d9d4e16403f6f348c63553b19f0975a9"
integrity sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==
process-nextick-args@~2.0.0: process-nextick-args@~2.0.0:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
@@ -4000,6 +4541,11 @@ prop-types@^15.8.1:
object-assign "^4.1.1" object-assign "^4.1.1"
react-is "^16.13.1" react-is "^16.13.1"
property-information@^7.0.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/property-information/-/property-information-7.1.0.tgz#b622e8646e02b580205415586b40804d3e8bfd5d"
integrity sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==
protobufjs@^7.2.4: protobufjs@^7.2.4:
version "7.5.4" version "7.5.4"
resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.4.tgz#885d31fe9c4b37f25d1bb600da30b1c5b37d286a" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.5.4.tgz#885d31fe9c4b37f25d1bb600da30b1c5b37d286a"
@@ -4083,6 +4629,18 @@ react-is@^16.13.1:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-syntax-highlighter@^16.1.0:
version "16.1.0"
resolved "https://registry.yarnpkg.com/react-syntax-highlighter/-/react-syntax-highlighter-16.1.0.tgz#ebe0bb5ae7a3540859212cedafd767f0189c516c"
integrity sha512-E40/hBiP5rCNwkeBN1vRP+xow1X0pndinO+z3h7HLsHyjztbyjfzNWNKuAsJj+7DLam9iT4AaaOZnueCU+Nplg==
dependencies:
"@babel/runtime" "^7.28.4"
highlight.js "^10.4.1"
highlightjs-vue "^1.0.0"
lowlight "^1.17.0"
prismjs "^1.30.0"
refractor "^5.0.0"
react-text-to-speech@^0.14.5: react-text-to-speech@^0.14.5:
version "0.14.5" version "0.14.5"
resolved "https://registry.yarnpkg.com/react-text-to-speech/-/react-text-to-speech-0.14.5.tgz#f918786ab283311535682011045bd49777193300" resolved "https://registry.yarnpkg.com/react-text-to-speech/-/react-text-to-speech-0.14.5.tgz#f918786ab283311535682011045bd49777193300"
@@ -4171,6 +4729,16 @@ reflect.getprototypeof@^1.0.4:
globalthis "^1.0.3" globalthis "^1.0.3"
which-builtin-type "^1.1.3" which-builtin-type "^1.1.3"
refractor@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/refractor/-/refractor-5.0.0.tgz#85daf0448a6d947f5361796eb22c31733d61d904"
integrity sha512-QXOrHQF5jOpjjLfiNk5GFnWhRXvxjUVnlFxkeDmewR5sXkr3iM46Zo+CnRR8B+MDVqkULW4EcLVcRBNOPXHosw==
dependencies:
"@types/hast" "^3.0.0"
"@types/prismjs" "^1.0.0"
hastscript "^9.0.0"
parse-entities "^4.0.0"
regenerator-runtime@^0.13.7: regenerator-runtime@^0.13.7:
version "0.13.11" version "0.13.11"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
@@ -4246,6 +4814,13 @@ rimraf@^3.0.2:
dependencies: dependencies:
glob "^7.1.3" glob "^7.1.3"
rimraf@^5.0.1:
version "5.0.10"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.10.tgz#23b9843d3dc92db71f96e1a2ce92e39fd2a8221c"
integrity sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ==
dependencies:
glob "^10.3.7"
roarr@^2.15.3: roarr@^2.15.3:
version "2.15.4" version "2.15.4"
resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd" resolved "https://registry.yarnpkg.com/roarr/-/roarr-2.15.4.tgz#f5fe795b7b838ccfe35dc608e0282b9eba2e7afd"
@@ -4512,6 +5087,11 @@ source-map@^0.6.0:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
space-separated-tokens@^2.0.0:
version "2.0.2"
resolved "https://registry.yarnpkg.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f"
integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==
sprintf-js@^1.1.2: sprintf-js@^1.1.2:
version "1.1.3" version "1.1.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.1.3.tgz#4914b903a2f8b685d17fdf78a70e917e872e444a"
@@ -5070,6 +5650,11 @@ utrie@^1.0.2:
dependencies: dependencies:
base64-arraybuffer "^1.0.2" base64-arraybuffer "^1.0.2"
web-streams-polyfill@^3.0.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz#2073b91a2fdb1fbfbd401e7de0ac9f8214cecb4b"
integrity sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==
whatwg-fetch@^3.6.20: whatwg-fetch@^3.6.20:
version "3.6.20" version "3.6.20"
resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz#580ce6d791facec91d37c72890995a0b48d31c70"
@@ -5162,6 +5747,11 @@ wrappy@1:
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
ws@^8.18.0:
version "8.18.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.3.tgz#b56b88abffde62791c639170400c93dcb0c95472"
integrity sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==
xmlbuilder@^10.0.0: xmlbuilder@^10.0.0:
version "10.1.1" version "10.1.1"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-10.1.1.tgz#8cae6688cc9b38d850b7c8d3c0a4161dcaf475b0" resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-10.1.1.tgz#8cae6688cc9b38d850b7c8d3c0a4161dcaf475b0"