mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-05-24 22:50:22 +09:00
feat: support other type file upload
This commit is contained in:
parent
aea5bedb68
commit
80a077a3db
@ -2,6 +2,7 @@ import { getServerSideConfig } from "@/app/config/server";
|
|||||||
import LocalFileStorage from "@/app/utils/local_file_storage";
|
import LocalFileStorage from "@/app/utils/local_file_storage";
|
||||||
import S3FileStorage from "@/app/utils/s3_file_storage";
|
import S3FileStorage from "@/app/utils/s3_file_storage";
|
||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
import mime from "mime";
|
||||||
|
|
||||||
async function handle(
|
async function handle(
|
||||||
req: NextRequest,
|
req: NextRequest,
|
||||||
@ -13,19 +14,27 @@ async function handle(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const serverConfig = getServerSideConfig();
|
const serverConfig = getServerSideConfig();
|
||||||
|
const fileName = params.path[0];
|
||||||
|
const contentType = mime.getType(fileName);
|
||||||
|
|
||||||
if (serverConfig.isStoreFileToLocal) {
|
if (serverConfig.isStoreFileToLocal) {
|
||||||
var fileBuffer = await LocalFileStorage.get(params.path[0]);
|
var fileBuffer = await LocalFileStorage.get(fileName);
|
||||||
return new Response(fileBuffer, {
|
return new Response(fileBuffer, {
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "image/png",
|
"Content-Type": contentType ?? "application/octet-stream",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
var file = await S3FileStorage.get(params.path[0]);
|
var file = await S3FileStorage.get(fileName);
|
||||||
return new Response(file?.transformToWebStream(), {
|
if (file) {
|
||||||
headers: {
|
return new Response(file?.transformToWebStream(), {
|
||||||
"Content-Type": "image/png",
|
headers: {
|
||||||
},
|
"Content-Type": contentType ?? "application/octet-stream",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return new Response("not found", {
|
||||||
|
status: 404,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -4,6 +4,7 @@ import { auth } from "@/app/api/auth";
|
|||||||
import LocalFileStorage from "@/app/utils/local_file_storage";
|
import LocalFileStorage from "@/app/utils/local_file_storage";
|
||||||
import { getServerSideConfig } from "@/app/config/server";
|
import { getServerSideConfig } from "@/app/config/server";
|
||||||
import S3FileStorage from "@/app/utils/s3_file_storage";
|
import S3FileStorage from "@/app/utils/s3_file_storage";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
async function handle(req: NextRequest) {
|
async function handle(req: NextRequest) {
|
||||||
if (req.method === "OPTIONS") {
|
if (req.method === "OPTIONS") {
|
||||||
@ -19,20 +20,21 @@ async function handle(req: NextRequest) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const formData = await req.formData();
|
const formData = await req.formData();
|
||||||
const image = formData.get("file") as File;
|
const file = formData.get("file") as File;
|
||||||
|
const originalFileName = file?.name;
|
||||||
|
|
||||||
const imageReader = image.stream().getReader();
|
const fileReader = file.stream().getReader();
|
||||||
const imageData: number[] = [];
|
const fileData: number[] = [];
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const { done, value } = await imageReader.read();
|
const { done, value } = await fileReader.read();
|
||||||
if (done) break;
|
if (done) break;
|
||||||
imageData.push(...value);
|
fileData.push(...value);
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = Buffer.from(imageData);
|
const buffer = Buffer.from(fileData);
|
||||||
|
const fileType = path.extname(originalFileName).slice(1);
|
||||||
var fileName = `${Date.now()}.png`;
|
var fileName = `${Date.now()}.${fileType}`;
|
||||||
var filePath = "";
|
var filePath = "";
|
||||||
const serverConfig = getServerSideConfig();
|
const serverConfig = getServerSideConfig();
|
||||||
if (serverConfig.isStoreFileToLocal) {
|
if (serverConfig.isStoreFileToLocal) {
|
||||||
|
@ -1,5 +1,66 @@
|
|||||||
@import "../styles/animation.scss";
|
@import "../styles/animation.scss";
|
||||||
|
|
||||||
|
.attach-files {
|
||||||
|
position: absolute;
|
||||||
|
left: 30px;
|
||||||
|
bottom: 32px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attach-file {
|
||||||
|
cursor: default;
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
border: rgba($color: #888, $alpha: 0.2) 1px solid;
|
||||||
|
border-radius: 5px;
|
||||||
|
margin-right: 10px;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
background-color: var(--second);
|
||||||
|
|
||||||
|
.attach-file-mask {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
opacity: 0;
|
||||||
|
transition: all ease 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attach-file-mask:hover {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-file {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
float: right;
|
||||||
|
background-color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
.attach-file-name {
|
||||||
|
font-size: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
padding: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-break: break-all;
|
||||||
|
// line-height: 1.2;
|
||||||
|
// max-height: 2.4em;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.attach-images {
|
.attach-images {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 30px;
|
left: 30px;
|
||||||
@ -232,10 +293,12 @@
|
|||||||
|
|
||||||
animation: slide-in ease 0.3s;
|
animation: slide-in ease 0.3s;
|
||||||
|
|
||||||
$linear: linear-gradient(to right,
|
$linear: linear-gradient(
|
||||||
rgba(0, 0, 0, 0),
|
to right,
|
||||||
rgba(0, 0, 0, 1),
|
rgba(0, 0, 0, 0),
|
||||||
rgba(0, 0, 0, 0));
|
rgba(0, 0, 0, 1),
|
||||||
|
rgba(0, 0, 0, 0)
|
||||||
|
);
|
||||||
mask-image: $linear;
|
mask-image: $linear;
|
||||||
|
|
||||||
@mixin show {
|
@mixin show {
|
||||||
@ -368,7 +431,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-message-user>.chat-message-container {
|
.chat-message-user > .chat-message-container {
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -482,23 +545,27 @@
|
|||||||
border: rgba($color: #888, $alpha: 0.2) 1px solid;
|
border: rgba($color: #888, $alpha: 0.2) 1px solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@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));
|
||||||
|
|
||||||
.chat-message-item-image-multi {
|
.chat-message-item-image-multi {
|
||||||
width: $calc-image-width;
|
width: $calc-image-width;
|
||||||
height: $calc-image-width;
|
height: $calc-image-width;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-message-item-image {
|
.chat-message-item-image {
|
||||||
max-width: calc(100vw/3*2);
|
max-width: calc(100vw / 3 * 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 600px) {
|
@media screen and (min-width: 600px) {
|
||||||
$max-image-width: calc(calc(1200px - var(--sidebar-width))/3*2/var(--image-count));
|
$max-image-width: calc(
|
||||||
$image-width: calc(calc(var(--window-width) - var(--sidebar-width))/3*2/var(--image-count));
|
calc(1200px - var(--sidebar-width)) / 3 * 2 / var(--image-count)
|
||||||
|
);
|
||||||
|
$image-width: calc(
|
||||||
|
calc(var(--window-width) - var(--sidebar-width)) / 3 * 2 /
|
||||||
|
var(--image-count)
|
||||||
|
);
|
||||||
|
|
||||||
.chat-message-item-image-multi {
|
.chat-message-item-image-multi {
|
||||||
width: $image-width;
|
width: $image-width;
|
||||||
@ -508,7 +575,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chat-message-item-image {
|
.chat-message-item-image {
|
||||||
max-width: calc(calc(1200px - var(--sidebar-width))/3*2);
|
max-width: calc(calc(1200px - var(--sidebar-width)) / 3 * 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -526,7 +593,7 @@
|
|||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-message-user>.chat-message-container>.chat-message-item {
|
.chat-message-user > .chat-message-container > .chat-message-item {
|
||||||
background-color: var(--second);
|
background-color: var(--second);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@ -637,7 +704,8 @@
|
|||||||
min-height: 68px;
|
min-height: 68px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.chat-input:focus {}
|
.chat-input:focus {
|
||||||
|
}
|
||||||
|
|
||||||
.chat-input-send {
|
.chat-input-send {
|
||||||
background-color: var(--primary);
|
background-color: var(--primary);
|
||||||
@ -656,4 +724,4 @@
|
|||||||
.chat-input-send {
|
.chat-input-send {
|
||||||
bottom: 30px;
|
bottom: 30px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,6 +69,7 @@ import {
|
|||||||
isVisionModel,
|
isVisionModel,
|
||||||
compressImage,
|
compressImage,
|
||||||
isFirefox,
|
isFirefox,
|
||||||
|
isSupportRAGModel,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
|
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
@ -116,6 +117,7 @@ import {
|
|||||||
SpeechApi,
|
SpeechApi,
|
||||||
WebTranscriptionApi,
|
WebTranscriptionApi,
|
||||||
} from "../utils/speech";
|
} from "../utils/speech";
|
||||||
|
import { getServerSideConfig } from "../config/server";
|
||||||
|
|
||||||
const ttsPlayer = createTTSPlayer();
|
const ttsPlayer = createTTSPlayer();
|
||||||
|
|
||||||
@ -460,6 +462,8 @@ function useScrollToBottom(
|
|||||||
export function ChatActions(props: {
|
export function ChatActions(props: {
|
||||||
uploadImage: () => void;
|
uploadImage: () => void;
|
||||||
setAttachImages: (images: string[]) => void;
|
setAttachImages: (images: string[]) => void;
|
||||||
|
uploadFile: () => void;
|
||||||
|
setAttachFiles: (files: string[]) => void;
|
||||||
setUploading: (uploading: boolean) => void;
|
setUploading: (uploading: boolean) => void;
|
||||||
showPromptModal: () => void;
|
showPromptModal: () => void;
|
||||||
scrollToBottom: () => void;
|
scrollToBottom: () => void;
|
||||||
@ -503,9 +507,15 @@ export function ChatActions(props: {
|
|||||||
const [showModelSelector, setShowModelSelector] = useState(false);
|
const [showModelSelector, setShowModelSelector] = useState(false);
|
||||||
const [showUploadImage, setShowUploadImage] = useState(false);
|
const [showUploadImage, setShowUploadImage] = useState(false);
|
||||||
|
|
||||||
|
const [showUploadFile, setShowUploadFile] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const show = isVisionModel(currentModel);
|
const show = isVisionModel(currentModel);
|
||||||
setShowUploadImage(show);
|
setShowUploadImage(show);
|
||||||
|
const serverConfig = getServerSideConfig();
|
||||||
|
setShowUploadFile(
|
||||||
|
serverConfig.isEnableRAG && !show && isSupportRAGModel(currentModel),
|
||||||
|
);
|
||||||
if (!show) {
|
if (!show) {
|
||||||
props.setAttachImages([]);
|
props.setAttachImages([]);
|
||||||
props.setUploading(false);
|
props.setUploading(false);
|
||||||
@ -555,6 +565,14 @@ export function ChatActions(props: {
|
|||||||
icon={props.uploading ? <LoadingButtonIcon /> : <ImageIcon />}
|
icon={props.uploading ? <LoadingButtonIcon /> : <ImageIcon />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{showUploadFile && (
|
||||||
|
<ChatAction
|
||||||
|
onClick={props.uploadFile}
|
||||||
|
text={Locale.Chat.InputActions.UploadFle}
|
||||||
|
icon={props.uploading ? <LoadingButtonIcon /> : <UploadIcon />}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<ChatAction
|
<ChatAction
|
||||||
onClick={nextTheme}
|
onClick={nextTheme}
|
||||||
text={Locale.Chat.InputActions.Theme[theme]}
|
text={Locale.Chat.InputActions.Theme[theme]}
|
||||||
@ -713,6 +731,14 @@ export function DeleteImageButton(props: { deleteImage: () => void }) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function DeleteFileButton(props: { deleteFile: () => void }) {
|
||||||
|
return (
|
||||||
|
<div className={styles["delete-file"]} onClick={props.deleteFile}>
|
||||||
|
<DeleteIcon />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function _Chat() {
|
function _Chat() {
|
||||||
type RenderMessage = ChatMessage & { preview?: boolean };
|
type RenderMessage = ChatMessage & { preview?: boolean };
|
||||||
|
|
||||||
@ -743,6 +769,7 @@ function _Chat() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [attachImages, setAttachImages] = useState<string[]>([]);
|
const [attachImages, setAttachImages] = useState<string[]>([]);
|
||||||
const [uploading, setUploading] = useState(false);
|
const [uploading, setUploading] = useState(false);
|
||||||
|
const [attachFiles, setAttachFiles] = useState<string[]>([]);
|
||||||
|
|
||||||
// prompt hints
|
// prompt hints
|
||||||
const promptStore = usePromptStore();
|
const promptStore = usePromptStore();
|
||||||
@ -851,6 +878,7 @@ function _Chat() {
|
|||||||
.onUserInput(userInput, attachImages)
|
.onUserInput(userInput, attachImages)
|
||||||
.then(() => setIsLoading(false));
|
.then(() => setIsLoading(false));
|
||||||
setAttachImages([]);
|
setAttachImages([]);
|
||||||
|
setAttachFiles([]);
|
||||||
localStorage.setItem(LAST_INPUT_KEY, userInput);
|
localStorage.setItem(LAST_INPUT_KEY, userInput);
|
||||||
setUserInput("");
|
setUserInput("");
|
||||||
setPromptHints([]);
|
setPromptHints([]);
|
||||||
@ -1324,6 +1352,53 @@ function _Chat() {
|
|||||||
setAttachImages(images);
|
setAttachImages(images);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function uploadFile() {
|
||||||
|
const uploadFiles: string[] = [];
|
||||||
|
uploadFiles.push(...attachFiles);
|
||||||
|
|
||||||
|
uploadFiles.push(
|
||||||
|
...(await new Promise<string[]>((res, rej) => {
|
||||||
|
const fileInput = document.createElement("input");
|
||||||
|
fileInput.type = "file";
|
||||||
|
fileInput.accept = ".pdf,.txt,.json,.csv,.md";
|
||||||
|
fileInput.multiple = true;
|
||||||
|
fileInput.onchange = (event: any) => {
|
||||||
|
setUploading(true);
|
||||||
|
const files = event.target.files;
|
||||||
|
const api = new ClientApi();
|
||||||
|
const fileDatas: string[] = [];
|
||||||
|
for (let i = 0; i < files.length; i++) {
|
||||||
|
const file = event.target.files[i];
|
||||||
|
api.file
|
||||||
|
.upload(file)
|
||||||
|
.then((fileInfo) => {
|
||||||
|
console.log(fileInfo);
|
||||||
|
fileDatas.push(fileInfo);
|
||||||
|
if (
|
||||||
|
fileDatas.length === 5 ||
|
||||||
|
fileDatas.length === files.length
|
||||||
|
) {
|
||||||
|
setUploading(false);
|
||||||
|
res(fileDatas);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((e) => {
|
||||||
|
setUploading(false);
|
||||||
|
rej(e);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fileInput.click();
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
|
||||||
|
const filesLength = uploadFiles.length;
|
||||||
|
if (filesLength > 5) {
|
||||||
|
uploadFiles.splice(5, filesLength - 5);
|
||||||
|
}
|
||||||
|
setAttachFiles(uploadFiles);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.chat} key={session.id}>
|
<div className={styles.chat} key={session.id}>
|
||||||
<div className="window-header" data-tauri-drag-region>
|
<div className="window-header" data-tauri-drag-region>
|
||||||
@ -1632,6 +1707,8 @@ function _Chat() {
|
|||||||
<ChatActions
|
<ChatActions
|
||||||
uploadImage={uploadImage}
|
uploadImage={uploadImage}
|
||||||
setAttachImages={setAttachImages}
|
setAttachImages={setAttachImages}
|
||||||
|
uploadFile={uploadFile}
|
||||||
|
setAttachFiles={setAttachFiles}
|
||||||
setUploading={setUploading}
|
setUploading={setUploading}
|
||||||
showPromptModal={() => setShowPromptModal(true)}
|
showPromptModal={() => setShowPromptModal(true)}
|
||||||
scrollToBottom={scrollToBottom}
|
scrollToBottom={scrollToBottom}
|
||||||
@ -1651,7 +1728,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"]
|
||||||
: ""
|
: ""
|
||||||
}`}
|
}`}
|
||||||
@ -1697,7 +1774,26 @@ function _Chat() {
|
|||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{attachFiles.length != 0 && (
|
||||||
|
<div className={styles["attach-files"]}>
|
||||||
|
{attachFiles.map((file, index) => {
|
||||||
|
return (
|
||||||
|
<div key={index} className={styles["attach-file"]}>
|
||||||
|
<div className={styles["attach-file-mask"]}>
|
||||||
|
<DeleteFileButton
|
||||||
|
deleteFile={() => {
|
||||||
|
setAttachFiles(
|
||||||
|
attachFiles.filter((_, i) => i !== index),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles["attach-file-name"]}>${file}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{config.sttConfig.enable ? (
|
{config.sttConfig.enable ? (
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={<VoiceWhiteIcon />}
|
icon={<VoiceWhiteIcon />}
|
||||||
|
@ -111,5 +111,7 @@ export const getServerSideConfig = () => {
|
|||||||
!!process.env.NEXT_PUBLIC_ENABLE_NODEJS_PLUGIN &&
|
!!process.env.NEXT_PUBLIC_ENABLE_NODEJS_PLUGIN &&
|
||||||
!process.env.R2_ACCOUNT_ID &&
|
!process.env.R2_ACCOUNT_ID &&
|
||||||
!process.env.S3_ENDPOINT,
|
!process.env.S3_ENDPOINT,
|
||||||
|
|
||||||
|
isEnableRAG: !!process.env.NEXT_PUBLIC_ENABLE_RAG,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -68,6 +68,7 @@ const cn = {
|
|||||||
EnablePlugins: "开启插件",
|
EnablePlugins: "开启插件",
|
||||||
DisablePlugins: "关闭插件",
|
DisablePlugins: "关闭插件",
|
||||||
UploadImage: "上传图片",
|
UploadImage: "上传图片",
|
||||||
|
UploadFle: "上传文件",
|
||||||
},
|
},
|
||||||
Rename: "重命名对话",
|
Rename: "重命名对话",
|
||||||
Typing: "正在输入…",
|
Typing: "正在输入…",
|
||||||
|
@ -296,3 +296,9 @@ export function isVisionModel(model: string) {
|
|||||||
|
|
||||||
return visionKeywords.some((keyword) => model.includes(keyword));
|
return visionKeywords.some((keyword) => model.includes(keyword));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isSupportRAGModel(modelName: string) {
|
||||||
|
return DEFAULT_MODELS.filter((model) => model.provider.id === "openai").some(
|
||||||
|
(model) => model.name === modelName,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
"https-proxy-agent": "^7.0.2",
|
"https-proxy-agent": "^7.0.2",
|
||||||
"langchain": "0.1.20",
|
"langchain": "0.1.20",
|
||||||
"mermaid": "^10.6.1",
|
"mermaid": "^10.6.1",
|
||||||
|
"mime": "^4.0.1",
|
||||||
"nanoid": "^5.0.3",
|
"nanoid": "^5.0.3",
|
||||||
"next": "^13.4.9",
|
"next": "^13.4.9",
|
||||||
"node-fetch": "^3.3.1",
|
"node-fetch": "^3.3.1",
|
||||||
@ -83,4 +84,4 @@
|
|||||||
"openai": "4.28.4"
|
"openai": "4.28.4"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.19"
|
"packageManager": "yarn@1.22.19"
|
||||||
}
|
}
|
||||||
|
@ -6274,6 +6274,11 @@ mime-types@^2.1.12, mime-types@^2.1.27:
|
|||||||
dependencies:
|
dependencies:
|
||||||
mime-db "1.52.0"
|
mime-db "1.52.0"
|
||||||
|
|
||||||
|
mime@^4.0.1:
|
||||||
|
version "4.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime/-/mime-4.0.1.tgz#ad7563d1bfe30253ad97dedfae2b1009d01b9470"
|
||||||
|
integrity sha512-5lZ5tyrIfliMXzFtkYyekWbtRXObT9OWa8IwQ5uxTBDHucNNwniRqo0yInflj+iYi5CBa6qxadGzGarDfuEOxA==
|
||||||
|
|
||||||
mimic-fn@^2.1.0:
|
mimic-fn@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
|
||||||
|
Loading…
Reference in New Issue
Block a user