Compare commits

..

2 Commits

Author SHA1 Message Date
Willie Zutz
3ed7f6ad17 Merge 8241c87784 into 68e151b2bd 2025-05-03 22:03:08 +00:00
Willie Zutz
8241c87784 feat(app):
Adds auto scrolling.
Adds syntax highlighting to code blocks.
2025-05-03 16:03:04 -06:00
6 changed files with 1383 additions and 171 deletions

1124
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
"license": "MIT", "license": "MIT",
"author": "ItzCrazyKns", "author": "ItzCrazyKns",
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev --turbopack",
"build": "npm run db:push && next build", "build": "npm run db:push && next build",
"start": "next start", "start": "next start",
"lint": "next lint", "lint": "next lint",
@@ -19,10 +19,11 @@
"@langchain/community": "^0.3.36", "@langchain/community": "^0.3.36",
"@langchain/core": "^0.3.42", "@langchain/core": "^0.3.42",
"@langchain/google-genai": "^0.1.12", "@langchain/google-genai": "^0.1.12",
"@langchain/openai": "^0.0.25",
"@langchain/ollama": "^0.2.0", "@langchain/ollama": "^0.2.0",
"@langchain/openai": "^0.0.25",
"@langchain/textsplitters": "^0.1.0", "@langchain/textsplitters": "^0.1.0",
"@tailwindcss/typography": "^0.5.12", "@tailwindcss/typography": "^0.5.12",
"@types/react-syntax-highlighter": "^15.5.13",
"@xenova/transformers": "^2.17.2", "@xenova/transformers": "^2.17.2",
"axios": "^1.8.3", "axios": "^1.8.3",
"better-sqlite3": "^11.9.1", "better-sqlite3": "^11.9.1",
@@ -39,6 +40,7 @@
"pdf-parse": "^1.1.1", "pdf-parse": "^1.1.1",
"react": "^18", "react": "^18",
"react-dom": "^18", "react-dom": "^18",
"react-syntax-highlighter": "^15.6.1",
"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",
"sonner": "^1.4.41", "sonner": "^1.4.41",

View File

@@ -5,12 +5,13 @@ import MessageInput from './MessageInput';
import { File, Message } from './ChatWindow'; import { File, Message } from './ChatWindow';
import MessageBox from './MessageBox'; import MessageBox from './MessageBox';
import MessageBoxLoading from './MessageBoxLoading'; import MessageBoxLoading from './MessageBoxLoading';
import { check } from 'drizzle-orm/gel-core';
const Chat = ({ const Chat = ({
loading, loading,
messages, messages,
sendMessage, sendMessage,
messageAppeared, scrollTrigger,
rewrite, rewrite,
fileIds, fileIds,
setFileIds, setFileIds,
@@ -29,7 +30,7 @@ const Chat = ({
}, },
) => void; ) => void;
loading: boolean; loading: boolean;
messageAppeared: boolean; scrollTrigger: number;
rewrite: (messageId: string) => void; rewrite: (messageId: string) => void;
fileIds: string[]; fileIds: string[];
setFileIds: (fileIds: string[]) => void; setFileIds: (fileIds: string[]) => void;
@@ -39,8 +40,72 @@ const Chat = ({
setOptimizationMode: (mode: string) => void; setOptimizationMode: (mode: string) => void;
}) => { }) => {
const [dividerWidth, setDividerWidth] = useState(0); const [dividerWidth, setDividerWidth] = useState(0);
const [isAtBottom, setIsAtBottom] = useState(true);
const [manuallyScrolledUp, setManuallyScrolledUp] = useState(false);
const dividerRef = useRef<HTMLDivElement | null>(null); const dividerRef = useRef<HTMLDivElement | null>(null);
const messageEnd = useRef<HTMLDivElement | null>(null); const messageEnd = useRef<HTMLDivElement | null>(null);
const SCROLL_THRESHOLD = 200; // pixels from bottom to consider "at bottom"
// Check if user is at bottom of page
useEffect(() => {
const checkIsAtBottom = () => {
const position = window.innerHeight + window.scrollY;
const height = document.body.scrollHeight;
const atBottom = position >= height - SCROLL_THRESHOLD;
setIsAtBottom(atBottom);
};
// Initial check
checkIsAtBottom();
// Add scroll event listener
window.addEventListener('scroll', checkIsAtBottom);
return () => {
window.removeEventListener('scroll', checkIsAtBottom);
};
}, []);
// Detect wheel and touch events to identify user's scrolling direction
useEffect(() => {
const checkIsAtBottom = () => {
const position = window.innerHeight + window.scrollY;
const height = document.body.scrollHeight;
const atBottom = position >= height - SCROLL_THRESHOLD;
// If user scrolls to bottom, reset the manuallyScrolledUp flag
if (atBottom) {
setManuallyScrolledUp(false);
}
setIsAtBottom(atBottom);
};
const handleWheel = (e: WheelEvent) => {
// Positive deltaY means scrolling down, negative means scrolling up
if (e.deltaY < 0) {
// User is scrolling up
setManuallyScrolledUp(true);
} else if (e.deltaY > 0) {
checkIsAtBottom();
}
};
const handleTouchStart = (e: TouchEvent) => {
// Immediately stop auto-scrolling on any touch interaction
setManuallyScrolledUp(true);
};
// Add event listeners
window.addEventListener('wheel', handleWheel, { passive: true });
window.addEventListener('touchstart', handleTouchStart, { passive: true });
return () => {
window.removeEventListener('wheel', handleWheel);
window.removeEventListener('touchstart', handleTouchStart);
};
}, [isAtBottom]);
useEffect(() => { useEffect(() => {
const updateDividerWidth = () => { const updateDividerWidth = () => {
@@ -58,6 +123,7 @@ const Chat = ({
}; };
}); });
// Scroll when user sends a message
useEffect(() => { useEffect(() => {
const scroll = () => { const scroll = () => {
messageEnd.current?.scrollIntoView({ behavior: 'smooth' }); messageEnd.current?.scrollIntoView({ behavior: 'smooth' });
@@ -67,11 +133,27 @@ const Chat = ({
document.title = `${messages[0].content.substring(0, 30)} - Perplexica`; document.title = `${messages[0].content.substring(0, 30)} - Perplexica`;
} }
if (messages[messages.length - 1]?.role == 'user') { // Always scroll when user sends a message
if (messages[messages.length - 1]?.role === 'user') {
scroll(); scroll();
setIsAtBottom(true); // Reset to true when user sends a message
setManuallyScrolledUp(false); // Reset manually scrolled flag when user sends a message
} }
}, [messages]); }, [messages]);
// Auto-scroll for assistant responses only if user is at bottom and hasn't manually scrolled up
useEffect(() => {
const position = window.innerHeight + window.scrollY;
const height = document.body.scrollHeight;
const atBottom = position >= height - SCROLL_THRESHOLD;
console.log('scrollTrigger', scrollTrigger);
setIsAtBottom(atBottom);
if (isAtBottom && !manuallyScrolledUp && messages.length > 0) {
messageEnd.current?.scrollIntoView({ behavior: 'smooth' });
}
}, [scrollTrigger, isAtBottom, messages.length, manuallyScrolledUp]);
return ( return (
<div className="flex flex-col space-y-6 pt-8 pb-44 lg:pb-32 sm:mx-4 md:mx-8"> <div className="flex flex-col space-y-6 pt-8 pb-44 lg:pb-32 sm:mx-4 md:mx-8">
{messages.map((msg, i) => { {messages.map((msg, i) => {
@@ -96,13 +178,44 @@ const Chat = ({
</Fragment> </Fragment>
); );
})} })}
{loading && !messageAppeared && <MessageBoxLoading />} {loading && <MessageBoxLoading />}
<div ref={messageEnd} className="h-0" /> <div ref={messageEnd} className="h-0" />
{dividerWidth > 0 && ( {dividerWidth > 0 && (
<div <div
className="bottom-24 lg:bottom-10 fixed z-40" className="bottom-24 lg:bottom-10 fixed z-40"
style={{ width: dividerWidth }} style={{ width: dividerWidth }}
> >
{/* Scroll to bottom button - appears above the MessageInput when user has scrolled up */}
{manuallyScrolledUp && !isAtBottom && (
<div className="absolute -top-14 right-2 z-10">
<button
onClick={() => {
setManuallyScrolledUp(false);
setIsAtBottom(true);
messageEnd.current?.scrollIntoView({ behavior: 'smooth' });
}}
className="bg-[#24A0ED] text-white hover:bg-opacity-85 transition duration-100 rounded-full px-4 py-2 shadow-lg flex items-center justify-center"
aria-label="Scroll to bottom"
>
<svg
xmlns="http://www.w3.org/2000/svg"
className="h-5 w-5 mr-1"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fillRule="evenodd"
d="M14.707 12.707a1 1 0 01-1.414 0L10 9.414l-3.293 3.293a1 1 0 01-1.414-1.414l4-4a1 1 0 011.414 0l4 4a1 1 0 010 1.414z"
clipRule="evenodd"
transform="rotate(180 10 10)"
/>
</svg>
<span className="text-sm">Scroll to bottom</span>
</button>
</div>
)}
<MessageInput <MessageInput
loading={loading} loading={loading}
sendMessage={sendMessage} sendMessage={sendMessage}

View File

@@ -278,7 +278,7 @@ const ChatWindow = ({ id }: { id?: string }) => {
}, []); }, []);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [messageAppeared, setMessageAppeared] = useState(false); const [scrollTrigger, setScrollTrigger] = useState(0);
const [chatHistory, setChatHistory] = useState<[string, string][]>([]); const [chatHistory, setChatHistory] = useState<[string, string][]>([]);
const [messages, setMessages] = useState<Message[]>([]); const [messages, setMessages] = useState<Message[]>([]);
@@ -351,6 +351,7 @@ const ChatWindow = ({ id }: { id?: string }) => {
suggestions?: string[]; suggestions?: string[];
}, },
) => { ) => {
setScrollTrigger((x) => (x === 0 ? -1 : 0));
// Special case: If we're just updating an existing message with suggestions // Special case: If we're just updating an existing message with suggestions
if (options?.suggestions && options.messageId) { if (options?.suggestions && options.messageId) {
setMessages((prev) => setMessages((prev) =>
@@ -371,7 +372,6 @@ const ChatWindow = ({ id }: { id?: string }) => {
} }
setLoading(true); setLoading(true);
setMessageAppeared(false);
let sources: Document[] | undefined = undefined; let sources: Document[] | undefined = undefined;
let recievedMessage = ''; let recievedMessage = '';
@@ -389,6 +389,8 @@ const ChatWindow = ({ id }: { id?: string }) => {
messages.length > 2 ? rewriteIndex - 1 : 0, messages.length > 2 ? rewriteIndex - 1 : 0,
); );
setChatHistory(messageChatHistory); setChatHistory(messageChatHistory);
setScrollTrigger((prev) => prev + 1);
} }
const messageId = const messageId =
@@ -427,8 +429,8 @@ const ChatWindow = ({ id }: { id?: string }) => {
}, },
]); ]);
added = true; added = true;
setScrollTrigger((prev) => prev + 1);
} }
setMessageAppeared(true);
} }
if (data.type === 'message') { if (data.type === 'message') {
@@ -461,7 +463,7 @@ const ChatWindow = ({ id }: { id?: string }) => {
); );
recievedMessage += data.data; recievedMessage += data.data;
setMessageAppeared(true); setScrollTrigger((prev) => prev + 1);
} }
if (data.type === 'messageEnd') { if (data.type === 'messageEnd') {
@@ -486,6 +488,7 @@ const ChatWindow = ({ id }: { id?: string }) => {
); );
setLoading(false); setLoading(false);
setScrollTrigger((prev) => prev + 1);
const lastMsg = messagesRef.current[messagesRef.current.length - 1]; const lastMsg = messagesRef.current[messagesRef.current.length - 1];
@@ -634,7 +637,7 @@ const ChatWindow = ({ id }: { id?: string }) => {
loading={loading} loading={loading}
messages={messages} messages={messages}
sendMessage={sendMessage} sendMessage={sendMessage}
messageAppeared={messageAppeared} scrollTrigger={scrollTrigger}
rewrite={rewrite} rewrite={rewrite}
fileIds={fileIds} fileIds={fileIds}
setFileIds={setFileIds} setFileIds={setFileIds}

View File

@@ -13,6 +13,8 @@ import {
Layers3, Layers3,
Plus, Plus,
Sparkles, Sparkles,
Copy as CopyIcon,
CheckCheck,
} from 'lucide-react'; } from 'lucide-react';
import Markdown, { MarkdownToJSX } from 'markdown-to-jsx'; import Markdown, { MarkdownToJSX } from 'markdown-to-jsx';
import Copy from './MessageActions/Copy'; import Copy from './MessageActions/Copy';
@@ -23,11 +25,79 @@ import SearchImages from './SearchImages';
import SearchVideos from './SearchVideos'; 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 { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { oneDark } from 'react-syntax-highlighter/dist/cjs/styles/prism';
const ThinkTagProcessor = ({ children }: { children: React.ReactNode }) => { const ThinkTagProcessor = ({ children }: { children: React.ReactNode }) => {
return <ThinkBox content={children as string} />; return <ThinkBox content={children as string} />;
}; };
const CodeBlock = ({
className,
children,
}: {
className?: string;
children: React.ReactNode;
}) => {
// Extract language from className (format could be "language-javascript" or "lang-javascript")
let language = '';
if (className) {
if (className.startsWith('language-')) {
language = className.replace('language-', '');
} else if (className.startsWith('lang-')) {
language = className.replace('lang-', '');
}
}
const content = children as string;
const [isCopied, setIsCopied] = useState(false);
const handleCopyCode = () => {
navigator.clipboard.writeText(content);
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
};
console.log('Code block language:', language, 'Class name:', className); // For debugging
return (
<div className="rounded-md overflow-hidden my-4 relative group border border-dark-secondary">
<div className="flex justify-between items-center px-4 py-2 bg-dark-200 border-b border-dark-secondary text-xs text-white/70 font-mono">
<span>{language}</span>
<button
onClick={handleCopyCode}
className="p-1 rounded-md hover:bg-dark-secondary transition duration-200"
aria-label="Copy code to clipboard"
>
{isCopied ? (
<CheckCheck size={14} className="text-green-500" />
) : (
<CopyIcon size={14} className="text-white/70" />
)}
</button>
</div>
<SyntaxHighlighter
language={language || 'text'}
style={oneDark}
customStyle={{
margin: 0,
padding: '1rem',
borderRadius: 0,
backgroundColor: '#1c1c1c',
}}
wrapLines={true}
wrapLongLines={true}
showLineNumbers={language !== '' && content.split('\n').length > 1}
useInlineStyles={true}
PreTag="div"
>
{content}
</SyntaxHighlighter>
</div>
);
};
const MessageBox = ({ const MessageBox = ({
message, message,
messageIndex, messageIndex,
@@ -157,6 +227,24 @@ const MessageBox = ({
think: { think: {
component: ThinkTagProcessor, component: ThinkTagProcessor,
}, },
code: {
component: ({ className, children }) => {
// Check if it's an inline code block or a fenced code block
if (className) {
// This is a fenced code block (```code```)
return <CodeBlock className={className}>{children}</CodeBlock>;
}
// This is an inline code block (`code`)
return (
<code className="px-1.5 py-0.5 rounded bg-dark-secondary text-white font-mono text-sm">
{children}
</code>
);
},
},
pre: {
component: ({ children }) => children,
},
}, },
}; };
@@ -212,8 +300,10 @@ const MessageBox = ({
</div> </div>
<Markdown <Markdown
className={cn( className={cn(
'prose prose-h1:mb-3 prose-h2:mb-2 prose-h2:mt-6 prose-h2:font-[800] prose-h3:mt-4 prose-h3:mb-1.5 prose-h3:font-[600] dark:prose-invert prose-p:leading-relaxed prose-pre:p-0 font-[400]', 'prose prose-h1:mb-3 prose-h2:mb-2 prose-h2:mt-6 prose-h2:font-[800] prose-h3:mt-4 prose-h3:mb-1.5 prose-h3:font-[600] prose-invert prose-p:leading-relaxed prose-pre:p-0 font-[400]',
'max-w-none break-words text-black dark:text-white', 'prose-code:bg-transparent prose-code:p-0 prose-code:text-inherit prose-code:font-normal prose-code:before:content-none prose-code:after:content-none',
'prose-pre:bg-transparent prose-pre:border-0 prose-pre:m-0 prose-pre:p-0',
'max-w-none break-words text-white',
)} )}
options={markdownOverrides} options={markdownOverrides}
> >

196
yarn.lock
View File

@@ -35,7 +35,7 @@
node-fetch "^2.6.7" node-fetch "^2.6.7"
web-streams-polyfill "^3.2.1" web-streams-polyfill "^3.2.1"
"@babel/runtime@^7.20.13": "@babel/runtime@^7.20.13", "@babel/runtime@^7.3.1":
version "7.27.0" version "7.27.0"
resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz" resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz"
integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw== integrity sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==
@@ -666,9 +666,9 @@
zod-to-json-schema "^3.22.3" zod-to-json-schema "^3.22.3"
"@langchain/core@^0.3.42": "@langchain/core@^0.3.42":
version "0.3.45" version "0.3.51"
resolved "https://registry.npmjs.org/@langchain/core/-/core-0.3.45.tgz" resolved "https://registry.npmjs.org/@langchain/core/-/core-0.3.51.tgz"
integrity sha512-4icCMCeuauVvwH43zgCE6Hk2Zbke5OccfVhFpTiIJPo8WoU/ALdaZlS22S+6+qZ9lFzFHsgZ6K33Av9aWhxkQA== integrity sha512-2nE30uuomSQrIQKB3BLgQtECZLWj5gwPEzQ+I6Ot6s9DKd133nXp3eZeggkAJ/uuc4WVROYVNJnmxepeAWo02Q==
dependencies: dependencies:
"@cfworker/json-schema" "^4.0.2" "@cfworker/json-schema" "^4.0.2"
ansi-styles "^5.0.0" ansi-styles "^5.0.0"
@@ -710,7 +710,7 @@
"@langchain/ollama@^0.2.0": "@langchain/ollama@^0.2.0":
version "0.2.0" version "0.2.0"
resolved "https://registry.yarnpkg.com/@langchain/ollama/-/ollama-0.2.0.tgz#43b3c90e9bd82256e26b70edf37304694aeaf5d1" resolved "https://registry.npmjs.org/@langchain/ollama/-/ollama-0.2.0.tgz"
integrity sha512-jLlYFqt+nbhaJKLakk7lRTWHZJ7wHeJLM6yuv4jToQ8zPzpL//372+MjggDoW0mnw8ofysg1T2C6mEJspKJtiA== integrity sha512-jLlYFqt+nbhaJKLakk7lRTWHZJ7wHeJLM6yuv4jToQ8zPzpL//372+MjggDoW0mnw8ofysg1T2C6mEJspKJtiA==
dependencies: dependencies:
ollama "^0.5.12" ollama "^0.5.12"
@@ -1048,6 +1048,13 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/hast@^2.0.0":
version "2.3.10"
resolved "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz"
integrity sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==
dependencies:
"@types/unist" "^2"
"@types/html-to-text@^9.0.4": "@types/html-to-text@^9.0.4":
version "9.0.4" version "9.0.4"
resolved "https://registry.npmjs.org/@types/html-to-text/-/html-to-text-9.0.4.tgz" resolved "https://registry.npmjs.org/@types/html-to-text/-/html-to-text-9.0.4.tgz"
@@ -1100,7 +1107,14 @@
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.6.tgz" resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.6.tgz"
integrity sha512-nf22//wEbKXusP6E9pfOCDwFdHAX4u172eaJI4YkDRQEZiorm6KfYnSC2SWLDMVWUOWPERmJnN0ujeAfTBLvrw== integrity sha512-nf22//wEbKXusP6E9pfOCDwFdHAX4u172eaJI4YkDRQEZiorm6KfYnSC2SWLDMVWUOWPERmJnN0ujeAfTBLvrw==
"@types/react@^18": "@types/react-syntax-highlighter@^15.5.13":
version "15.5.13"
resolved "https://registry.npmjs.org/@types/react-syntax-highlighter/-/react-syntax-highlighter-15.5.13.tgz"
integrity sha512-uLGJ87j6Sz8UaBAooU0T6lWJ0dBmjZgN1PZTrj05TNql2/XpC6+4HhMT5syIdFUUt+FASfCeLLv4kBygNU+8qA==
dependencies:
"@types/react" "*"
"@types/react@*", "@types/react@^18":
version "18.3.20" version "18.3.20"
resolved "https://registry.npmjs.org/@types/react/-/react-18.3.20.tgz" resolved "https://registry.npmjs.org/@types/react/-/react-18.3.20.tgz"
integrity sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg== integrity sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==
@@ -1118,6 +1132,11 @@
resolved "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz" resolved "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.5.tgz"
integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw== integrity sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==
"@types/unist@^2":
version "2.0.11"
resolved "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz"
integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==
"@types/uuid@^10.0.0": "@types/uuid@^10.0.0":
version "10.0.0" version "10.0.0"
resolved "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz" resolved "https://registry.npmjs.org/@types/uuid/-/uuid-10.0.0.tgz"
@@ -1701,6 +1720,21 @@ chalk@^4.0.0, chalk@^4.1.2:
ansi-styles "^4.1.0" ansi-styles "^4.1.0"
supports-color "^7.1.0" supports-color "^7.1.0"
character-entities-legacy@^1.0.0:
version "1.1.4"
resolved "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.4.tgz"
integrity sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==
character-entities@^1.0.0:
version "1.2.4"
resolved "https://registry.npmjs.org/character-entities/-/character-entities-1.2.4.tgz"
integrity sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==
character-reference-invalid@^1.0.0:
version "1.1.4"
resolved "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.4.tgz"
integrity sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==
charenc@0.0.2: charenc@0.0.2:
version "0.0.2" version "0.0.2"
resolved "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz" resolved "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz"
@@ -1799,6 +1833,11 @@ combined-stream@^1.0.8:
dependencies: dependencies:
delayed-stream "~1.0.0" delayed-stream "~1.0.0"
comma-separated-tokens@^1.0.0:
version "1.0.8"
resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz"
integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==
commander@^10.0.1: commander@^10.0.1:
version "10.0.1" version "10.0.1"
resolved "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz" resolved "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz"
@@ -2585,6 +2624,13 @@ 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.npmjs.org/fault/-/fault-1.0.4.tgz"
integrity sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==
dependencies:
format "^0.2.0"
fdir@^6.4.3: fdir@^6.4.3:
version "6.4.4" version "6.4.4"
resolved "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz" resolved "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz"
@@ -2685,6 +2731,11 @@ 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.npmjs.org/format/-/format-0.2.2.tgz"
integrity sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==
formdata-node@^4.3.2: formdata-node@^4.3.2:
version "4.4.1" version "4.4.1"
resolved "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz" resolved "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz"
@@ -2914,6 +2965,32 @@ hasown@^2.0.2:
dependencies: dependencies:
function-bind "^1.1.2" function-bind "^1.1.2"
hast-util-parse-selector@^2.0.0:
version "2.2.5"
resolved "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-2.2.5.tgz"
integrity sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==
hastscript@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/hastscript/-/hastscript-6.0.0.tgz"
integrity sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==
dependencies:
"@types/hast" "^2.0.0"
comma-separated-tokens "^1.0.0"
hast-util-parse-selector "^2.0.0"
property-information "^5.0.0"
space-separated-tokens "^1.0.0"
highlight.js@^10.4.1, highlight.js@~10.7.0:
version "10.7.3"
resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.3.tgz"
integrity sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==
highlightjs-vue@^1.0.0:
version "1.0.0"
resolved "https://registry.npmjs.org/highlightjs-vue/-/highlightjs-vue-1.0.0.tgz"
integrity sha512-PDEfEF102G23vHmPhLyPboFCD+BkMGu+GuJe2d9/eH4FsCwvgBpnc9n0pGE+ffKdph38s6foEZiEjdgHdzp+IA==
html-to-text@^9.0.5: html-to-text@^9.0.5:
version "9.0.5" version "9.0.5"
resolved "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz" resolved "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz"
@@ -2992,6 +3069,19 @@ internal-slot@^1.1.0:
hasown "^2.0.2" hasown "^2.0.2"
side-channel "^1.1.0" side-channel "^1.1.0"
is-alphabetical@^1.0.0:
version "1.0.4"
resolved "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-1.0.4.tgz"
integrity sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==
is-alphanumerical@^1.0.0:
version "1.0.4"
resolved "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-1.0.4.tgz"
integrity sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==
dependencies:
is-alphabetical "^1.0.0"
is-decimal "^1.0.0"
is-any-array@^2.0.0: is-any-array@^2.0.0:
version "2.0.1" version "2.0.1"
resolved "https://registry.npmjs.org/is-any-array/-/is-any-array-2.0.1.tgz" resolved "https://registry.npmjs.org/is-any-array/-/is-any-array-2.0.1.tgz"
@@ -3085,6 +3175,11 @@ is-date-object@^1.0.5, is-date-object@^1.1.0:
call-bound "^1.0.2" call-bound "^1.0.2"
has-tostringtag "^1.0.2" has-tostringtag "^1.0.2"
is-decimal@^1.0.0:
version "1.0.4"
resolved "https://registry.npmjs.org/is-decimal/-/is-decimal-1.0.4.tgz"
integrity sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==
is-extglob@^2.1.1: is-extglob@^2.1.1:
version "2.1.1" version "2.1.1"
resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" resolved "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz"
@@ -3119,6 +3214,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@^1.0.0:
version "1.0.4"
resolved "https://registry.npmjs.org/is-hexadecimal/-/is-hexadecimal-1.0.4.tgz"
integrity sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==
is-map@^2.0.3: is-map@^2.0.3:
version "2.0.3" version "2.0.3"
resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz" resolved "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz"
@@ -3478,6 +3578,14 @@ loose-envify@^1.1.0, loose-envify@^1.4.0:
dependencies: dependencies:
js-tokens "^3.0.0 || ^4.0.0" js-tokens "^3.0.0 || ^4.0.0"
lowlight@^1.17.0:
version "1.20.0"
resolved "https://registry.npmjs.org/lowlight/-/lowlight-1.20.0.tgz"
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.4.3" version "10.4.3"
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz" resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz"
@@ -3936,6 +4044,18 @@ parent-module@^1.0.0:
dependencies: dependencies:
callsites "^3.0.0" callsites "^3.0.0"
parse-entities@^2.0.0:
version "2.0.0"
resolved "https://registry.npmjs.org/parse-entities/-/parse-entities-2.0.0.tgz"
integrity sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==
dependencies:
character-entities "^1.0.0"
character-entities-legacy "^1.0.0"
character-reference-invalid "^1.0.0"
is-alphanumerical "^1.0.0"
is-decimal "^1.0.0"
is-hexadecimal "^1.0.0"
parseley@^0.12.0: parseley@^0.12.0:
version "0.12.1" version "0.12.1"
resolved "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz" resolved "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz"
@@ -4123,6 +4243,16 @@ prettier@^3.2.5:
resolved "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz" resolved "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz"
integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A== integrity sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==
prismjs@^1.27.0:
version "1.30.0"
resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz"
integrity sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==
prismjs@~1.27.0:
version "1.27.0"
resolved "https://registry.npmjs.org/prismjs/-/prismjs-1.27.0.tgz"
integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==
prop-types@^15.8.1: prop-types@^15.8.1:
version "15.8.1" version "15.8.1"
resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz"
@@ -4132,6 +4262,13 @@ 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@^5.0.0:
version "5.6.0"
resolved "https://registry.npmjs.org/property-information/-/property-information-5.6.0.tgz"
integrity sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==
dependencies:
xtend "^4.0.0"
protobufjs@^6.8.8: protobufjs@^6.8.8:
version "6.11.4" version "6.11.4"
resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz" resolved "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz"
@@ -4202,6 +4339,18 @@ react-is@^16.13.1:
resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz"
integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
react-syntax-highlighter@^15.6.1:
version "15.6.1"
resolved "https://registry.npmjs.org/react-syntax-highlighter/-/react-syntax-highlighter-15.6.1.tgz"
integrity sha512-OqJ2/vL7lEeV5zTJyG7kmARppUjiB9h9udl4qHQjjgEos66z00Ia0OckwYfRxCSFrW8RJIBnsBwQsHZbVPspqg==
dependencies:
"@babel/runtime" "^7.3.1"
highlight.js "^10.4.1"
highlightjs-vue "^1.0.0"
lowlight "^1.17.0"
prismjs "^1.27.0"
refractor "^3.6.0"
react-text-to-speech@^0.14.5: react-text-to-speech@^0.14.5:
version "0.14.9" version "0.14.9"
resolved "https://registry.npmjs.org/react-text-to-speech/-/react-text-to-speech-0.14.9.tgz" resolved "https://registry.npmjs.org/react-text-to-speech/-/react-text-to-speech-0.14.9.tgz"
@@ -4260,6 +4409,15 @@ reflect.getprototypeof@^1.0.6, reflect.getprototypeof@^1.0.9:
get-proto "^1.0.1" get-proto "^1.0.1"
which-builtin-type "^1.2.1" which-builtin-type "^1.2.1"
refractor@^3.6.0:
version "3.6.0"
resolved "https://registry.npmjs.org/refractor/-/refractor-3.6.0.tgz"
integrity sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==
dependencies:
hastscript "^6.0.0"
parse-entities "^2.0.0"
prismjs "~1.27.0"
regenerator-runtime@^0.14.0: regenerator-runtime@^0.14.0:
version "0.14.1" version "0.14.1"
resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz" resolved "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz"
@@ -4340,10 +4498,10 @@ safe-array-concat@^1.1.3:
has-symbols "^1.1.0" has-symbols "^1.1.0"
isarray "^2.0.5" isarray "^2.0.5"
safe-buffer@^5.0.1, safe-buffer@~5.1.0: safe-buffer@^5.0.1, safe-buffer@~5.2.0:
version "5.1.2" version "5.2.1"
resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==
safe-push-apply@^1.0.0: safe-push-apply@^1.0.0:
version "1.0.0" version "1.0.0"
@@ -4582,6 +4740,11 @@ source-map@^0.6.0:
resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" resolved "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
space-separated-tokens@^1.0.0:
version "1.1.5"
resolved "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-1.1.5.tgz"
integrity sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==
stable-hash@^0.0.5: stable-hash@^0.0.5:
version "0.0.5" version "0.0.5"
resolved "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz" resolved "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz"
@@ -4703,11 +4866,11 @@ string.prototype.trimstart@^1.0.8:
es-object-atoms "^1.0.0" es-object-atoms "^1.0.0"
string_decoder@^1.1.1: string_decoder@^1.1.1:
version "1.1.1" version "1.3.0"
resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz" resolved "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz"
integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
dependencies: dependencies:
safe-buffer "~5.1.0" safe-buffer "~5.2.0"
"strip-ansi-cjs@npm:strip-ansi@^6.0.1": "strip-ansi-cjs@npm:strip-ansi@^6.0.1":
version "6.0.1" version "6.0.1"
@@ -5252,6 +5415,11 @@ wrappy@1:
resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" resolved "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz"
integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
xtend@^4.0.0:
version "4.0.2"
resolved "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
yaml@^2.2.1, yaml@^2.3.4: yaml@^2.2.1, yaml@^2.3.4:
version "2.4.1" version "2.4.1"
resolved "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz" resolved "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz"