mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-10-01 15:46:39 +08:00
Merge e86a39047c
into 8ad63a6c25
This commit is contained in:
commit
23d299c4ab
@ -29,11 +29,22 @@ export const TTSModels = ["tts-1", "tts-1-hd"] as const;
|
||||
export type ChatModel = ModelType;
|
||||
|
||||
export interface MultimodalContent {
|
||||
type: "text" | "image_url";
|
||||
type: "text" | "image_url" | "file_url";
|
||||
text?: string;
|
||||
image_url?: {
|
||||
url: string;
|
||||
};
|
||||
file_url?: {
|
||||
url: string;
|
||||
name: string;
|
||||
tokenCount?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface UploadFile {
|
||||
name: string;
|
||||
url: string;
|
||||
tokenCount?: number;
|
||||
}
|
||||
|
||||
export interface RequestMessage {
|
||||
|
@ -1,10 +1,18 @@
|
||||
@import "../styles/animation.scss";
|
||||
|
||||
.attach-images {
|
||||
.attachments {
|
||||
position: absolute;
|
||||
left: 30px;
|
||||
bottom: 32px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.attach-images {
|
||||
//position: absolute;
|
||||
//left: 30px;
|
||||
//bottom: 32px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.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 {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@ -471,6 +559,32 @@
|
||||
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) {
|
||||
$calc-image-width: calc(100vw/3*2/var(--image-count));
|
||||
@ -693,4 +807,4 @@
|
||||
.shortcut-key span {
|
||||
font-size: 12px;
|
||||
color: var(--black);
|
||||
}
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ import StyleIcon from "../icons/palette.svg";
|
||||
import PluginIcon from "../icons/plugin.svg";
|
||||
import ShortcutkeyIcon from "../icons/shortcutkey.svg";
|
||||
import ReloadIcon from "../icons/reload.svg";
|
||||
import UploadDocIcon from "../icons/upload-doc.svg";
|
||||
|
||||
import {
|
||||
ChatMessage,
|
||||
@ -68,13 +69,18 @@ import {
|
||||
useMobileScreen,
|
||||
getMessageTextContent,
|
||||
getMessageImages,
|
||||
getMessageFiles,
|
||||
isVisionModel,
|
||||
isDalle3,
|
||||
showPlugins,
|
||||
safeLocalStorage,
|
||||
countTokens,
|
||||
} from "../utils";
|
||||
|
||||
import type { UploadFile } from "../client/api";
|
||||
|
||||
import { uploadImage as uploadImageRemote } from "@/app/utils/chat";
|
||||
import { uploadImage as uploadFileRemote } from "@/app/utils/chat";
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
|
||||
@ -96,6 +102,8 @@ import {
|
||||
showToast,
|
||||
} from "./ui-lib";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { FileIcon, defaultStyles } from "react-file-icon";
|
||||
import type { DefaultExtensionType } from "react-file-icon";
|
||||
import {
|
||||
CHAT_PAGE_SIZE,
|
||||
DEFAULT_TTS_ENGINE,
|
||||
@ -442,8 +450,10 @@ function useScrollToBottom(
|
||||
}
|
||||
|
||||
export function ChatActions(props: {
|
||||
uploadDocument: () => void;
|
||||
uploadImage: () => void;
|
||||
setAttachImages: (images: string[]) => void;
|
||||
setAttachFiles: (files: UploadFile[]) => void;
|
||||
setUploading: (uploading: boolean) => void;
|
||||
showPromptModal: () => void;
|
||||
scrollToBottom: () => void;
|
||||
@ -577,6 +587,11 @@ export function ChatActions(props: {
|
||||
icon={props.uploading ? <LoadingButtonIcon /> : <ImageIcon />}
|
||||
/>
|
||||
)}
|
||||
<ChatAction
|
||||
onClick={props.uploadDocument}
|
||||
text={"Upload Plain Text File"}
|
||||
icon={props.uploading ? <LoadingButtonIcon /> : <UploadDocIcon />}
|
||||
/>
|
||||
<ChatAction
|
||||
onClick={nextTheme}
|
||||
text={Locale.Chat.InputActions.Theme[theme]}
|
||||
@ -945,6 +960,7 @@ function _Chat() {
|
||||
const isMobileScreen = useMobileScreen();
|
||||
const navigate = useNavigate();
|
||||
const [attachImages, setAttachImages] = useState<string[]>([]);
|
||||
const [attachFiles, setAttachFiles] = useState<UploadFile[]>([]);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
|
||||
// prompt hints
|
||||
@ -1025,9 +1041,10 @@ function _Chat() {
|
||||
}
|
||||
setIsLoading(true);
|
||||
chatStore
|
||||
.onUserInput(userInput, attachImages)
|
||||
.onUserInput(userInput, attachImages, attachFiles)
|
||||
.then(() => setIsLoading(false));
|
||||
setAttachImages([]);
|
||||
setAttachFiles([]);
|
||||
chatStore.setLastInput(userInput);
|
||||
setUserInput("");
|
||||
setPromptHints([]);
|
||||
@ -1177,7 +1194,9 @@ function _Chat() {
|
||||
setIsLoading(true);
|
||||
const textContent = getMessageTextContent(userMessage);
|
||||
const images = getMessageImages(userMessage);
|
||||
chatStore.onUserInput(textContent, images).then(() => setIsLoading(false));
|
||||
chatStore
|
||||
.onUserInput(textContent, images, attachFiles)
|
||||
.then(() => setIsLoading(false));
|
||||
inputRef.current?.focus();
|
||||
};
|
||||
|
||||
@ -1460,6 +1479,54 @@ function _Chat() {
|
||||
[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() {
|
||||
const images: string[] = [];
|
||||
images.push(...attachImages);
|
||||
@ -1878,6 +1945,41 @@ function _Chat() {
|
||||
})}
|
||||
</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 className={styles["chat-message-action-date"]}>
|
||||
@ -1897,8 +1999,10 @@ function _Chat() {
|
||||
<PromptHints prompts={promptHints} onPromptSelect={onPromptSelect} />
|
||||
|
||||
<ChatActions
|
||||
uploadDocument={uploadDocument}
|
||||
uploadImage={uploadImage}
|
||||
setAttachImages={setAttachImages}
|
||||
setAttachFiles={setAttachFiles}
|
||||
setUploading={setUploading}
|
||||
showPromptModal={() => setShowPromptModal(true)}
|
||||
scrollToBottom={scrollToBottom}
|
||||
@ -1920,7 +2024,7 @@ function _Chat() {
|
||||
/>
|
||||
<label
|
||||
className={`${styles["chat-input-panel-inner"]} ${
|
||||
attachImages.length != 0
|
||||
attachImages.length != 0 || attachFiles.length != 0
|
||||
? styles["chat-input-panel-inner-attach"]
|
||||
: ""
|
||||
}`}
|
||||
@ -1944,29 +2048,82 @@ function _Chat() {
|
||||
fontFamily: config.fontFamily,
|
||||
}}
|
||||
/>
|
||||
{attachImages.length != 0 && (
|
||||
<div className={styles["attach-images"]}>
|
||||
{attachImages.map((image, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={styles["attach-image"]}
|
||||
style={{ backgroundImage: `url("${image}")` }}
|
||||
>
|
||||
<div className={styles["attach-image-mask"]}>
|
||||
<DeleteImageButton
|
||||
deleteImage={() => {
|
||||
setAttachImages(
|
||||
attachImages.filter((_, i) => i !== index),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<div className={styles["attachments"]}>
|
||||
{attachImages.length != 0 && (
|
||||
<div className={styles["attach-images"]}>
|
||||
{attachImages.map((image, index) => {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={styles["attach-image"]}
|
||||
style={{ backgroundImage: `url("${image}")` }}
|
||||
>
|
||||
<div className={styles["attach-image-mask"]}>
|
||||
<DeleteImageButton
|
||||
deleteImage={() => {
|
||||
setAttachImages(
|
||||
attachImages.filter((_, i) => i !== index),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</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
|
||||
icon={<SendWhiteIcon />}
|
||||
text={Locale.Chat.Send}
|
||||
|
1
app/icons/upload-doc.svg
Normal file
1
app/icons/upload-doc.svg
Normal 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 |
@ -6,6 +6,7 @@ import type {
|
||||
ClientApi,
|
||||
MultimodalContent,
|
||||
RequestMessage,
|
||||
UploadFile,
|
||||
} from "../client/api";
|
||||
import { getClientApi } from "../client/api";
|
||||
import { ChatControllerPool } from "../client/controller";
|
||||
@ -18,7 +19,7 @@ import {
|
||||
StoreKey,
|
||||
} from "../constant";
|
||||
import Locale, { getLang } from "../locales";
|
||||
import { isDalle3, safeLocalStorage } from "../utils";
|
||||
import { isDalle3, safeLocalStorage, readFileContent } from "../utils";
|
||||
import { prettyObject } from "../utils/format";
|
||||
import { createPersistStore } from "../utils/store";
|
||||
import { estimateTokenLength } from "../utils/token";
|
||||
@ -326,16 +327,75 @@ export const useChatStore = createPersistStore(
|
||||
get().summarizeSession();
|
||||
},
|
||||
|
||||
async onUserInput(content: string, attachImages?: string[]) {
|
||||
async onUserInput(
|
||||
content: string,
|
||||
attachImages?: string[],
|
||||
attachFiles?: UploadFile[],
|
||||
) {
|
||||
const session = get().currentSession();
|
||||
const modelConfig = session.mask.modelConfig;
|
||||
|
||||
//read file content from the url
|
||||
const userContent = fillTemplateWith(content, modelConfig);
|
||||
console.log("[User Input] after template: ", userContent);
|
||||
|
||||
let mContent: string | MultimodalContent[] = userContent;
|
||||
let displayContent: string | MultimodalContent[] = userContent;
|
||||
displayContent = [
|
||||
{
|
||||
type: "text",
|
||||
text: userContent,
|
||||
},
|
||||
];
|
||||
|
||||
if (attachImages && attachImages.length > 0) {
|
||||
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) {
|
||||
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 = [
|
||||
{
|
||||
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({
|
||||
role: "user",
|
||||
@ -373,7 +446,8 @@ export const useChatStore = createPersistStore(
|
||||
get().updateCurrentSession((session) => {
|
||||
const savedUserMessage = {
|
||||
...userMessage,
|
||||
content: mContent,
|
||||
//content: mContent,
|
||||
content: displayContent,
|
||||
};
|
||||
session.messages = session.messages.concat([
|
||||
savedUserMessage,
|
||||
|
67
app/utils.ts
67
app/utils.ts
@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { showToast } from "./components/ui-lib";
|
||||
import Locale from "./locales";
|
||||
import { RequestMessage } from "./client/api";
|
||||
import { RequestMessage, UploadFile } from "./client/api";
|
||||
import { ServiceProvider, REQUEST_TIMEOUT_MS } from "./constant";
|
||||
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) {
|
||||
try {
|
||||
if (window.__TAURI__) {
|
||||
@ -250,6 +302,19 @@ export function getMessageImages(message: RequestMessage): string[] {
|
||||
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) {
|
||||
// Note: This is a better way using the TypeScript feature instead of `&&` or `||` (ts v5.5.0-dev.20240314 I've been using)
|
||||
|
||||
|
@ -22,6 +22,7 @@
|
||||
"@hello-pangea/dnd": "^16.5.0",
|
||||
"@next/third-parties": "^14.1.0",
|
||||
"@svgr/webpack": "^6.5.1",
|
||||
"@types/react-file-icon": "^1.0.4",
|
||||
"@vercel/analytics": "^0.1.11",
|
||||
"@vercel/speed-insights": "^1.0.2",
|
||||
"axios": "^1.7.5",
|
||||
@ -31,14 +32,15 @@
|
||||
"html-to-image": "^1.11.11",
|
||||
"idb-keyval": "^6.2.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mermaid": "^10.6.1",
|
||||
"markdown-to-txt": "^2.0.1",
|
||||
"mermaid": "^10.6.1",
|
||||
"nanoid": "^5.0.3",
|
||||
"next": "^14.1.1",
|
||||
"node-fetch": "^3.3.1",
|
||||
"openapi-client-axios": "^7.5.5",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-file-icon": "^1.5.0",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"rehype-highlight": "^6.0.0",
|
||||
@ -80,4 +82,4 @@
|
||||
"lint-staged/yaml": "^2.2.2"
|
||||
},
|
||||
"packageManager": "yarn@1.22.19"
|
||||
}
|
||||
}
|
||||
|
22
yarn.lock
22
yarn.lock
@ -1762,6 +1762,13 @@
|
||||
dependencies:
|
||||
"@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":
|
||||
version "3.0.0"
|
||||
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"
|
||||
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:
|
||||
version "2.0.19"
|
||||
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"
|
||||
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"
|
||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
|
||||
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
|
||||
@ -5453,6 +5465,14 @@ react-dom@^18.2.0:
|
||||
loose-envify "^1.1.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:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
|
Loading…
Reference in New Issue
Block a user