This commit is contained in:
MatrixDynamo 2024-10-08 15:27:00 +08:00 committed by GitHub
commit 23d299c4ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 480 additions and 36 deletions

View File

@ -29,11 +29,22 @@ export const TTSModels = ["tts-1", "tts-1-hd"] as const;
export type ChatModel = ModelType; export type ChatModel = ModelType;
export interface MultimodalContent { export interface MultimodalContent {
type: "text" | "image_url"; type: "text" | "image_url" | "file_url";
text?: string; text?: string;
image_url?: { image_url?: {
url: string; url: string;
}; };
file_url?: {
url: string;
name: string;
tokenCount?: number;
};
}
export interface UploadFile {
name: string;
url: string;
tokenCount?: number;
} }
export interface RequestMessage { export interface RequestMessage {

View File

@ -1,10 +1,18 @@
@import "../styles/animation.scss"; @import "../styles/animation.scss";
.attach-images { .attachments {
position: absolute; position: absolute;
left: 30px; left: 30px;
bottom: 32px; bottom: 32px;
display: flex; display: flex;
flex-direction: row;
}
.attach-images {
//position: absolute;
//left: 30px;
//bottom: 32px;
display: flex;
} }
.attach-image { .attach-image {
@ -42,6 +50,86 @@
} }
} }
.attach-files {
//position: absolute;
//left: 30px;
//bottom: 32px;
display: flex;
flex-direction: column;
//row-gap: 11px;
max-height: 64px;
}
.attach-file {
cursor: default;
display: flex;
flex-direction: row;
column-gap: 4px;
justify-content: flex-start;
color: var(--black);
font-size: 14px;
border-radius: 5px;
margin-right: 10px;
%attach-file-name-common {
display:flex;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.attach-file-name-full {
@extend %attach-file-name-common;
max-width:calc(62vw);
}
.attach-file-name-half {
@extend %attach-file-name-common;
max-width:calc(45vw);
}
.attach-file-name-less {
@extend %attach-file-name-common;
max-width:calc(28vw);
}
.attach-file-name-min {
@extend %attach-file-name-common;
max-width:calc(12vw);
}
.attach-file-icon {
min-width: 16px;
max-width: 16px;
}
.attach-file-icon:hover {
opacity: 0;
}
.attach-image-mask {
width: 100%;
height: 100%;
opacity: 0;
transition: all ease 0.2s;
position: absolute;
}
.attach-image-mask:hover {
opacity: 1;
}
.delete-image {
width: 16px;
height: 20px;
cursor: pointer;
border-radius: 5px;
float: left;
background-color: var(--white);
}
}
.chat-input-actions { .chat-input-actions {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
@ -471,6 +559,32 @@
border: rgba($color: #888, $alpha: 0.2) 1px solid; border: rgba($color: #888, $alpha: 0.2) 1px solid;
} }
.chat-message-item-files {
width: 100%;
display: flex;
flex-direction: column;
row-gap: 10px;
flex-wrap: wrap;
margin-top: 10px;
}
.chat-message-item-file {
display: flex;
flex-direction: row;
column-gap: 6px;
}
.chat-message-item-file-icon {
max-width: 16px;
}
.chat-message-item-file-name {
max-width:100%;
}
@media only screen and (max-width: 600px) { @media only screen and (max-width: 600px) {
$calc-image-width: calc(100vw/3*2/var(--image-count)); $calc-image-width: calc(100vw/3*2/var(--image-count));

View File

@ -46,6 +46,7 @@ import StyleIcon from "../icons/palette.svg";
import PluginIcon from "../icons/plugin.svg"; import PluginIcon from "../icons/plugin.svg";
import ShortcutkeyIcon from "../icons/shortcutkey.svg"; import ShortcutkeyIcon from "../icons/shortcutkey.svg";
import ReloadIcon from "../icons/reload.svg"; import ReloadIcon from "../icons/reload.svg";
import UploadDocIcon from "../icons/upload-doc.svg";
import { import {
ChatMessage, ChatMessage,
@ -68,13 +69,18 @@ import {
useMobileScreen, useMobileScreen,
getMessageTextContent, getMessageTextContent,
getMessageImages, getMessageImages,
getMessageFiles,
isVisionModel, isVisionModel,
isDalle3, isDalle3,
showPlugins, showPlugins,
safeLocalStorage, safeLocalStorage,
countTokens,
} from "../utils"; } from "../utils";
import type { UploadFile } from "../client/api";
import { uploadImage as uploadImageRemote } from "@/app/utils/chat"; import { uploadImage as uploadImageRemote } from "@/app/utils/chat";
import { uploadImage as uploadFileRemote } from "@/app/utils/chat";
import dynamic from "next/dynamic"; import dynamic from "next/dynamic";
@ -96,6 +102,8 @@ import {
showToast, showToast,
} from "./ui-lib"; } from "./ui-lib";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { FileIcon, defaultStyles } from "react-file-icon";
import type { DefaultExtensionType } from "react-file-icon";
import { import {
CHAT_PAGE_SIZE, CHAT_PAGE_SIZE,
DEFAULT_TTS_ENGINE, DEFAULT_TTS_ENGINE,
@ -442,8 +450,10 @@ function useScrollToBottom(
} }
export function ChatActions(props: { export function ChatActions(props: {
uploadDocument: () => void;
uploadImage: () => void; uploadImage: () => void;
setAttachImages: (images: string[]) => void; setAttachImages: (images: string[]) => void;
setAttachFiles: (files: UploadFile[]) => void;
setUploading: (uploading: boolean) => void; setUploading: (uploading: boolean) => void;
showPromptModal: () => void; showPromptModal: () => void;
scrollToBottom: () => void; scrollToBottom: () => void;
@ -577,6 +587,11 @@ export function ChatActions(props: {
icon={props.uploading ? <LoadingButtonIcon /> : <ImageIcon />} icon={props.uploading ? <LoadingButtonIcon /> : <ImageIcon />}
/> />
)} )}
<ChatAction
onClick={props.uploadDocument}
text={"Upload Plain Text File"}
icon={props.uploading ? <LoadingButtonIcon /> : <UploadDocIcon />}
/>
<ChatAction <ChatAction
onClick={nextTheme} onClick={nextTheme}
text={Locale.Chat.InputActions.Theme[theme]} text={Locale.Chat.InputActions.Theme[theme]}
@ -945,6 +960,7 @@ function _Chat() {
const isMobileScreen = useMobileScreen(); const isMobileScreen = useMobileScreen();
const navigate = useNavigate(); const navigate = useNavigate();
const [attachImages, setAttachImages] = useState<string[]>([]); const [attachImages, setAttachImages] = useState<string[]>([]);
const [attachFiles, setAttachFiles] = useState<UploadFile[]>([]);
const [uploading, setUploading] = useState(false); const [uploading, setUploading] = useState(false);
// prompt hints // prompt hints
@ -1025,9 +1041,10 @@ function _Chat() {
} }
setIsLoading(true); setIsLoading(true);
chatStore chatStore
.onUserInput(userInput, attachImages) .onUserInput(userInput, attachImages, attachFiles)
.then(() => setIsLoading(false)); .then(() => setIsLoading(false));
setAttachImages([]); setAttachImages([]);
setAttachFiles([]);
chatStore.setLastInput(userInput); chatStore.setLastInput(userInput);
setUserInput(""); setUserInput("");
setPromptHints([]); setPromptHints([]);
@ -1177,7 +1194,9 @@ function _Chat() {
setIsLoading(true); setIsLoading(true);
const textContent = getMessageTextContent(userMessage); const textContent = getMessageTextContent(userMessage);
const images = getMessageImages(userMessage); const images = getMessageImages(userMessage);
chatStore.onUserInput(textContent, images).then(() => setIsLoading(false)); chatStore
.onUserInput(textContent, images, attachFiles)
.then(() => setIsLoading(false));
inputRef.current?.focus(); inputRef.current?.focus();
}; };
@ -1460,6 +1479,54 @@ function _Chat() {
[attachImages, chatStore], [attachImages, chatStore],
); );
async function uploadDocument() {
const files: UploadFile[] = [];
files.push(...attachFiles);
files.push(
...(await new Promise<UploadFile[]>((res, rej) => {
const fileInput = document.createElement("input");
fileInput.type = "file";
fileInput.accept = "text/*";
fileInput.multiple = true;
fileInput.onchange = (event: any) => {
setUploading(true);
const inputFiles = event.target.files;
const imagesData: UploadFile[] = [];
(async () => {
for (let i = 0; i < inputFiles.length; i++) {
const file = inputFiles[i];
try {
const dataUrl = await uploadFileRemote(file);
const fileData: UploadFile = { name: file.name, url: dataUrl };
const tokenCount: number = await countTokens(fileData);
fileData.tokenCount = tokenCount;
imagesData.push(fileData);
if (
imagesData.length === 3 ||
imagesData.length === inputFiles.length
) {
setUploading(false);
res(imagesData);
}
} catch (e) {
setUploading(false);
rej(e);
}
}
})();
};
fileInput.click();
})),
);
const filesLength = files.length;
if (filesLength > 3) {
files.splice(3, filesLength - 3);
}
setAttachFiles(files);
}
async function uploadImage() { async function uploadImage() {
const images: string[] = []; const images: string[] = [];
images.push(...attachImages); images.push(...attachImages);
@ -1878,6 +1945,41 @@ function _Chat() {
})} })}
</div> </div>
)} )}
{getMessageFiles(message).length > 0 && (
<div className={styles["chat-message-item-files"]}>
{getMessageFiles(message).map((file, index) => {
const extension: DefaultExtensionType = file.name
.split(".")
.pop()
?.toLowerCase() as DefaultExtensionType;
const style = defaultStyles[extension];
return (
<a
href={file.url}
target="_blank"
key={index}
className={styles["chat-message-item-file"]}
>
<div
className={
styles["chat-message-item-file-icon"] +
" no-dark"
}
>
<FileIcon {...style} glyphColor="#303030" />
</div>
<div
className={
styles["chat-message-item-file-name"]
}
>
{file.name} {file.tokenCount}K
</div>
</a>
);
})}
</div>
)}
</div> </div>
<div className={styles["chat-message-action-date"]}> <div className={styles["chat-message-action-date"]}>
@ -1897,8 +1999,10 @@ function _Chat() {
<PromptHints prompts={promptHints} onPromptSelect={onPromptSelect} /> <PromptHints prompts={promptHints} onPromptSelect={onPromptSelect} />
<ChatActions <ChatActions
uploadDocument={uploadDocument}
uploadImage={uploadImage} uploadImage={uploadImage}
setAttachImages={setAttachImages} setAttachImages={setAttachImages}
setAttachFiles={setAttachFiles}
setUploading={setUploading} setUploading={setUploading}
showPromptModal={() => setShowPromptModal(true)} showPromptModal={() => setShowPromptModal(true)}
scrollToBottom={scrollToBottom} scrollToBottom={scrollToBottom}
@ -1920,7 +2024,7 @@ function _Chat() {
/> />
<label <label
className={`${styles["chat-input-panel-inner"]} ${ className={`${styles["chat-input-panel-inner"]} ${
attachImages.length != 0 attachImages.length != 0 || attachFiles.length != 0
? styles["chat-input-panel-inner-attach"] ? styles["chat-input-panel-inner-attach"]
: "" : ""
}`} }`}
@ -1944,6 +2048,7 @@ function _Chat() {
fontFamily: config.fontFamily, fontFamily: config.fontFamily,
}} }}
/> />
<div className={styles["attachments"]}>
{attachImages.length != 0 && ( {attachImages.length != 0 && (
<div className={styles["attach-images"]}> <div className={styles["attach-images"]}>
{attachImages.map((image, index) => { {attachImages.map((image, index) => {
@ -1967,6 +2072,58 @@ function _Chat() {
})} })}
</div> </div>
)} )}
{attachFiles.length != 0 && (
<div className={styles["attach-files"]}>
{attachFiles.map((file, index) => {
const extension: DefaultExtensionType = file.name
.split(".")
.pop()
?.toLowerCase() as DefaultExtensionType;
const style = defaultStyles[extension];
return (
<div key={index} className={styles["attach-file"]}>
<div
className={styles["attach-file-icon"] + " no-dark"}
key={extension}
>
<FileIcon {...style} glyphColor="#303030" />
</div>
{attachImages.length == 0 && (
<div className={styles["attach-file-name-full"]}>
{file.name} {file.tokenCount}K
</div>
)}
{attachImages.length == 1 && (
<div className={styles["attach-file-name-half"]}>
{file.name} {file.tokenCount}K
</div>
)}
{attachImages.length == 2 && (
<div className={styles["attach-file-name-less"]}>
{file.name} {file.tokenCount}K
</div>
)}
{attachImages.length == 3 && (
<div className={styles["attach-file-name-min"]}>
{file.name} {file.tokenCount}K
</div>
)}
<div className={styles["attach-image-mask"]}>
<DeleteImageButton
deleteImage={() => {
setAttachFiles(
attachFiles.filter((_, i) => i !== index),
);
}}
/>
</div>
</div>
);
})}
</div>
)}
</div>
<IconButton <IconButton
icon={<SendWhiteIcon />} icon={<SendWhiteIcon />}
text={Locale.Chat.Send} text={Locale.Chat.Send}

1
app/icons/upload-doc.svg Normal file
View File

@ -0,0 +1 @@
<svg version="1.2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><defs><image width="32" height="40" id="img1" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAoCAMAAACo9wirAAAAAXNSR0IB2cksfwAAAIpQTFRFAAAAVVVVUVFRUVFRUFBQUVFRUFBQUVFRf39/UFBQUVFRUFBQZmZmVVVVUVFRAAAAUVFRUFBQUVFRUVFRUFBQX19fUFBQUVFRUVFRU1NTT09PUVFRUVFRUVFRUlJSUFBQTU1NT09PUVFRUlJSUFBQUVFRUVFRUVFRUFBQUFBQUFBQUFBQUFBQUVFReUEd1wAAAC50Uk5TAAmB3Pz/+ZACxJx1BQadAZaed4CRCH7bMjEw7O81Iu0hdq8l4NXWhveF83jawwNJtaIAAAEdSURBVHicpdRrSwJBFAbg8+7WskarlIqhtBAS1f//LwaFIJkWiX3wgotbTrNIeubMBaL32+x5dnYunAWhCh2jR4UxTL9I5HTHBKJY1k2BOKIEK1YUAiegdEkCMAH9oCQT1DZMuMAZ1lptCy8oG5U43xReQEy4ARMecBQW+N12muz3YgF9cFnJhAWaCz7Sn5JAXB4sQG1gzq7fBmJLAlzNWtFbAHTxQR2MvaCqExcC7OtcmCDH9CBHLnDzSt14TNffU0rWLtAHVtsF1ZNsp4a+beqDrs9D5/B/cLmk7DME8gn1XkJA5E/gDkNW6eNRgEbv2Xj3diBneFDhGfxrkO3PkxWkcOgDRz1/0msXfSBz4fqJsdy/Q/YBi2oqNfsBQGqACm/REAgAAAAASUVORK5CYII="/></defs><style></style><use href="#img1" transform="matrix(.333,0,0,.333,2.667,1.333)"/></svg>

After

Width:  |  Height:  |  Size: 1020 B

View File

@ -6,6 +6,7 @@ import type {
ClientApi, ClientApi,
MultimodalContent, MultimodalContent,
RequestMessage, RequestMessage,
UploadFile,
} from "../client/api"; } from "../client/api";
import { getClientApi } from "../client/api"; import { getClientApi } from "../client/api";
import { ChatControllerPool } from "../client/controller"; import { ChatControllerPool } from "../client/controller";
@ -18,7 +19,7 @@ import {
StoreKey, StoreKey,
} from "../constant"; } from "../constant";
import Locale, { getLang } from "../locales"; import Locale, { getLang } from "../locales";
import { isDalle3, safeLocalStorage } from "../utils"; import { isDalle3, safeLocalStorage, readFileContent } from "../utils";
import { prettyObject } from "../utils/format"; import { prettyObject } from "../utils/format";
import { createPersistStore } from "../utils/store"; import { createPersistStore } from "../utils/store";
import { estimateTokenLength } from "../utils/token"; import { estimateTokenLength } from "../utils/token";
@ -326,16 +327,75 @@ export const useChatStore = createPersistStore(
get().summarizeSession(); get().summarizeSession();
}, },
async onUserInput(content: string, attachImages?: string[]) { async onUserInput(
content: string,
attachImages?: string[],
attachFiles?: UploadFile[],
) {
const session = get().currentSession(); const session = get().currentSession();
const modelConfig = session.mask.modelConfig; const modelConfig = session.mask.modelConfig;
//read file content from the url
const userContent = fillTemplateWith(content, modelConfig); const userContent = fillTemplateWith(content, modelConfig);
console.log("[User Input] after template: ", userContent); console.log("[User Input] after template: ", userContent);
let mContent: string | MultimodalContent[] = userContent; let mContent: string | MultimodalContent[] = userContent;
let displayContent: string | MultimodalContent[] = userContent;
displayContent = [
{
type: "text",
text: userContent,
},
];
if (attachFiles && attachFiles.length > 0) {
let fileContent = userContent + " Here are the files: \n";
for (let i = 0; i < attachFiles.length; i++) {
fileContent += attachFiles[i].name + "\n";
fileContent += await readFileContent(attachFiles[i]);
}
mContent = [
{
type: "text",
text: fileContent,
},
];
displayContent = displayContent.concat(
attachFiles.map((file) => {
return {
type: "file_url",
file_url: {
url: file.url,
name: file.name,
tokenCount: file.tokenCount,
},
};
}),
);
if (attachImages && attachImages.length > 0) { if (attachImages && attachImages.length > 0) {
mContent = mContent.concat(
attachImages.map((url) => {
return {
type: "image_url",
image_url: {
url: url,
},
};
}),
);
displayContent = displayContent.concat(
attachImages.map((url) => {
return {
type: "image_url",
image_url: {
url: url,
},
};
}),
);
}
} else if (attachImages && attachImages.length > 0) {
mContent = [ mContent = [
{ {
type: "text", type: "text",
@ -352,6 +412,19 @@ export const useChatStore = createPersistStore(
}; };
}), }),
); );
displayContent = displayContent.concat(
attachImages.map((url) => {
return {
type: "image_url",
image_url: {
url: url,
},
};
}),
);
} else {
mContent = userContent;
displayContent = userContent;
} }
let userMessage: ChatMessage = createMessage({ let userMessage: ChatMessage = createMessage({
role: "user", role: "user",
@ -373,7 +446,8 @@ export const useChatStore = createPersistStore(
get().updateCurrentSession((session) => { get().updateCurrentSession((session) => {
const savedUserMessage = { const savedUserMessage = {
...userMessage, ...userMessage,
content: mContent, //content: mContent,
content: displayContent,
}; };
session.messages = session.messages.concat([ session.messages = session.messages.concat([
savedUserMessage, savedUserMessage,

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { showToast } from "./components/ui-lib"; import { showToast } from "./components/ui-lib";
import Locale from "./locales"; import Locale from "./locales";
import { RequestMessage } from "./client/api"; import { RequestMessage, UploadFile } from "./client/api";
import { ServiceProvider, REQUEST_TIMEOUT_MS } from "./constant"; import { ServiceProvider, REQUEST_TIMEOUT_MS } from "./constant";
import { fetch as tauriFetch, ResponseType } from "@tauri-apps/api/http"; import { fetch as tauriFetch, ResponseType } from "@tauri-apps/api/http";
@ -17,6 +17,58 @@ export function trimTopic(topic: string) {
); );
} }
export const readFileContent = async (file: UploadFile): Promise<string> => {
const host_url = new URL(window.location.href);
if (!file.url.includes(host_url.host)) {
throw new Error(`The URL ${file.url} is not allowed to access.`);
}
try {
const response = await fetch(file.url);
if (!response.ok) {
throw new Error(
`Failed to fetch content from ${file.url}: ${response.statusText}`,
);
}
//const content = await response.text();
//const result = file.name + "\n" + content;
//return result;
return await response.text();
} catch (error) {
console.error("Error reading file content:", error);
throw error;
}
};
export const countTokens = async (file: UploadFile) => {
const text = await readFileContent(file);
let totalTokens = 0;
for (let i = 0; i < text.length; i++) {
const char = text[i];
const nextChar = text[i + 1];
if (char === " " && nextChar === " ") {
totalTokens += 0.081;
} else if ("NORabcdefghilnopqrstuvy ".includes(char)) {
totalTokens += 0.202;
} else if ("CHLMPQSTUVfkmspwx".includes(char)) {
totalTokens += 0.237;
} else if ("-.ABDEFGIKWY_\\r\\tz{ü".includes(char)) {
totalTokens += 0.304;
} else if ("!{{input}}(/;=JX`j\\n}ö".includes(char)) {
totalTokens += 0.416;
} else if ('"#%)*+56789<>?@Z[\\]^|§«äç’'.includes(char)) {
totalTokens += 0.479;
} else if (",01234:~Üß".includes(char) || char.charCodeAt(0) > 255) {
totalTokens += 0.658;
} else {
totalTokens += 0.98;
}
}
const totalTokenCount: number = +(totalTokens / 1000).toFixed(2);
return totalTokenCount;
};
export async function copyToClipboard(text: string) { export async function copyToClipboard(text: string) {
try { try {
if (window.__TAURI__) { if (window.__TAURI__) {
@ -250,6 +302,19 @@ export function getMessageImages(message: RequestMessage): string[] {
return urls; return urls;
} }
export function getMessageFiles(message: RequestMessage): UploadFile[] {
if (typeof message.content === "string") {
return [];
}
const files: UploadFile[] = [];
for (const c of message.content) {
if (c.type === "file_url" && c.file_url) {
files.push(c.file_url);
}
}
return files;
}
export function isVisionModel(model: string) { export function isVisionModel(model: string) {
// Note: This is a better way using the TypeScript feature instead of `&&` or `||` (ts v5.5.0-dev.20240314 I've been using) // Note: This is a better way using the TypeScript feature instead of `&&` or `||` (ts v5.5.0-dev.20240314 I've been using)

View File

@ -22,6 +22,7 @@
"@hello-pangea/dnd": "^16.5.0", "@hello-pangea/dnd": "^16.5.0",
"@next/third-parties": "^14.1.0", "@next/third-parties": "^14.1.0",
"@svgr/webpack": "^6.5.1", "@svgr/webpack": "^6.5.1",
"@types/react-file-icon": "^1.0.4",
"@vercel/analytics": "^0.1.11", "@vercel/analytics": "^0.1.11",
"@vercel/speed-insights": "^1.0.2", "@vercel/speed-insights": "^1.0.2",
"axios": "^1.7.5", "axios": "^1.7.5",
@ -31,14 +32,15 @@
"html-to-image": "^1.11.11", "html-to-image": "^1.11.11",
"idb-keyval": "^6.2.1", "idb-keyval": "^6.2.1",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mermaid": "^10.6.1",
"markdown-to-txt": "^2.0.1", "markdown-to-txt": "^2.0.1",
"mermaid": "^10.6.1",
"nanoid": "^5.0.3", "nanoid": "^5.0.3",
"next": "^14.1.1", "next": "^14.1.1",
"node-fetch": "^3.3.1", "node-fetch": "^3.3.1",
"openapi-client-axios": "^7.5.5", "openapi-client-axios": "^7.5.5",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-file-icon": "^1.5.0",
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",
"react-router-dom": "^6.15.0", "react-router-dom": "^6.15.0",
"rehype-highlight": "^6.0.0", "rehype-highlight": "^6.0.0",

View File

@ -1762,6 +1762,13 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react-file-icon@^1.0.4":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@types/react-file-icon/-/react-file-icon-1.0.4.tgz#6825b0e6b8ab639f7f25a6cd52499650d3afcd89"
integrity sha512-c1mIklUDaxm9odxf8RTiy/EAxsblZliJ86EKIOAyuafP9eK3iudyn4ATv53DX6ZvgGymc7IttVNm97LTGnTiYA==
dependencies:
"@types/react" "*"
"@types/react-katex@^3.0.0": "@types/react-katex@^3.0.0":
version "3.0.0" version "3.0.0"
resolved "https://registry.yarnpkg.com/@types/react-katex/-/react-katex-3.0.0.tgz#119a902bff10eb52f449fac744aaed8c4909391f" resolved "https://registry.yarnpkg.com/@types/react-katex/-/react-katex-3.0.0.tgz#119a902bff10eb52f449fac744aaed8c4909391f"
@ -2416,6 +2423,11 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
colord@^2.9.3:
version "2.9.3"
resolved "https://registry.yarnpkg.com/colord/-/colord-2.9.3.tgz#4f8ce919de456f1d5c1c368c307fe20f3e59fb43"
integrity sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==
colorette@^2.0.19: colorette@^2.0.19:
version "2.0.19" version "2.0.19"
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798" resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.19.tgz#cdf044f47ad41a0f4b56b3a0d5b4e6e1a2d5a798"
@ -5404,7 +5416,7 @@ prettier@^3.0.2:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.2.tgz#78fcecd6d870551aa5547437cdae39d4701dca5b" resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.2.tgz#78fcecd6d870551aa5547437cdae39d4701dca5b"
integrity sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ== integrity sha512-o2YR9qtniXvwEZlOKbveKfDQVyqxbEIWn48Z8m3ZJjBjcCmUy3xZGIv+7AkaeuaTr6yPXJjwv07ZWlsWbEy1rQ==
prop-types@^15.0.0, prop-types@^15.8.1: prop-types@^15.0.0, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1" version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@ -5453,6 +5465,14 @@ react-dom@^18.2.0:
loose-envify "^1.1.0" loose-envify "^1.1.0"
scheduler "^0.23.0" scheduler "^0.23.0"
react-file-icon@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/react-file-icon/-/react-file-icon-1.5.0.tgz#cccc8827d927291b8a52fab41afbe5b3625ddbf4"
integrity sha512-6K2/nAI69CS838HOS+4S95MLXwf1neWywek1FgqcTFPTYjnM8XT7aBLz4gkjoqQKY9qPhu3A2tu+lvxhmZYY9w==
dependencies:
colord "^2.9.3"
prop-types "^15.7.2"
react-is@^16.13.1, react-is@^16.7.0: react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1" version "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"