diff --git a/app/client/api.ts b/app/client/api.ts index f0176cb33..1f43565c4 100644 --- a/app/client/api.ts +++ b/app/client/api.ts @@ -36,9 +36,15 @@ export interface MultimodalContent { }; file_url?: { url: string; + name: string; }; } +export interface UploadFile { + name: string; + url: string; +} + export interface RequestMessage { role: MessageRole; content: string | MultimodalContent[]; diff --git a/app/components/chat.module.scss b/app/components/chat.module.scss index 4314370d2..69dc53eb4 100644 --- a/app/components/chat.module.scss +++ b/app/components/chat.module.scss @@ -71,16 +71,52 @@ border-radius: 5px; margin-right: 10px; + .attach-file-name-full { + max-width:calc(62vw); + display:flex; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + .attach-file-name-half { + max-width:calc(45vw); + display:flex; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + .attach-file-name-less { + max-width:calc(28vw); + display:flex; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + .attach-file-name-min { + max-width:calc(12vw); + display:flex; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } .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 { @@ -88,8 +124,8 @@ } .delete-image { - width: 24px; - height: 24px; + width: 16px; + height: 20px; cursor: pointer; border-radius: 5px; float: left; diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 439e1d934..41f1989b1 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -69,12 +69,15 @@ import { useMobileScreen, getMessageTextContent, getMessageImages, + getMessageFiles, isVisionModel, isDalle3, showPlugins, safeLocalStorage, } from "../utils"; +import type { UploadFile } from "../client/api"; + import { uploadImage as uploadImageRemote } from "@/app/utils/chat"; import dynamic from "next/dynamic"; @@ -448,7 +451,7 @@ export function ChatActions(props: { uploadDocument: () => void; uploadImage: () => void; setAttachImages: (images: string[]) => void; - setAttachFiles: (files: string[]) => void; + setAttachFiles: (files: UploadFile[]) => void; setUploading: (uploading: boolean) => void; showPromptModal: () => void; scrollToBottom: () => void; @@ -957,7 +960,7 @@ function _Chat() { const isMobileScreen = useMobileScreen(); const navigate = useNavigate(); const [attachImages, setAttachImages] = useState([]); - const [attachFiles, setAttachFiles] = useState([]); + const [attachFiles, setAttachFiles] = useState([]); const [uploading, setUploading] = useState(false); // prompt hints @@ -1475,11 +1478,11 @@ function _Chat() { ); async function uploadDocument() { - const files: string[] = []; + const files: UploadFile[] = []; files.push(...attachFiles); files.push( - ...(await new Promise((res, rej) => { + ...(await new Promise((res, rej) => { const fileInput = document.createElement("input"); fileInput.type = "file"; fileInput.accept = "text/*"; @@ -1487,12 +1490,12 @@ function _Chat() { fileInput.onchange = (event: any) => { setUploading(true); const files = event.target.files; - const imagesData: string[] = []; + const imagesData: UploadFile[] = []; for (let i = 0; i < files.length; i++) { const file = event.target.files[i]; uploadImageRemote(file) .then((dataUrl) => { - imagesData.push(dataUrl); + imagesData.push({ name: file.name, url: dataUrl }); if ( imagesData.length === 3 || imagesData.length === files.length @@ -1937,6 +1940,13 @@ function _Chat() { })} )} + {getMessageFiles(message).length > 0 && ( +
+ {getMessageFiles(message).map((file, index) => { + return
; + })} +
+ )}
@@ -2032,7 +2042,7 @@ function _Chat() { {attachFiles.length != 0 && (
{attachFiles.map((file, index) => { - const extension: DefaultExtensionType = file + const extension: DefaultExtensionType = file.url .split(".") .pop() ?.toLowerCase() as DefaultExtensionType; @@ -2045,7 +2055,27 @@ function _Chat() { >
- {extension} + {attachImages.length == 0 && ( +
+ {file.name} +
+ )} + {attachImages.length == 1 && ( +
+ {file.name} +
+ )} + {attachImages.length == 2 && ( +
+ {file.name} +
+ )} + {attachImages.length == 3 && ( +
+ {file.name} +
+ )} +
{ diff --git a/app/store/chat.ts b/app/store/chat.ts index 9256e56b5..b3f84d4e4 100644 --- a/app/store/chat.ts +++ b/app/store/chat.ts @@ -6,6 +6,7 @@ import type { ClientApi, MultimodalContent, RequestMessage, + UploadFile, } from "../client/api"; import { getClientApi } from "../client/api"; import { ChatControllerPool } from "../client/controller"; @@ -153,12 +154,12 @@ function fillTemplateWith(input: string, modelConfig: ModelConfig) { return output; } -const readFileContent = async (url: string): Promise => { +const readFileContent = async (file: UploadFile): Promise => { try { - const response = await fetch(url); + const response = await fetch(file.url); if (!response.ok) { throw new Error( - `Failed to fetch content from ${url}: ${response.statusText}`, + `Failed to fetch content from ${file.url}: ${response.statusText}`, ); } return await response.text(); @@ -344,7 +345,7 @@ export const useChatStore = createPersistStore( async onUserInput( content: string, attachImages?: string[], - attachFiles?: string[], + attachFiles?: UploadFile[], ) { const session = get().currentSession(); const modelConfig = session.mask.modelConfig; @@ -386,11 +387,12 @@ export const useChatStore = createPersistStore( }), ); displayContent = displayContent.concat( - attachFiles.map((url) => { + attachFiles.map((file) => { return { type: "file_url", file_url: { - url: url, + url: file.url, + name: file.name, }, }; }), diff --git a/app/utils.ts b/app/utils.ts index 9a8bebf38..05c012921 100644 --- a/app/utils.ts +++ b/app/utils.ts @@ -250,6 +250,19 @@ export function getMessageImages(message: RequestMessage): string[] { return urls; } +export function getMessageFiles(message: RequestMessage): string[] { + if (typeof message.content === "string") { + return []; + } + const urls: string[] = []; + for (const c of message.content) { + if (c.type === "file_url") { + urls.push(c.file_url?.url ?? ""); + } + } + return urls; +} + 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)