diff --git a/app/api/file/upload/route.ts b/app/api/file/upload/route.ts index 6599147..1bc0fea 100644 --- a/app/api/file/upload/route.ts +++ b/app/api/file/upload/route.ts @@ -31,8 +31,9 @@ async function handle(req: NextRequest) { } const buffer = Buffer.from(imageData); - - var fileName = `${Date.now()}.png`; + + // 使用获取到的文件后缀 + var fileName = image.name; var filePath = ""; const serverConfig = getServerSideConfig(); if (serverConfig.isStoreFileToLocal) { diff --git a/app/client/platforms/openai.ts b/app/client/platforms/openai.ts index 72448d6..dd8c86d 100644 --- a/app/client/platforms/openai.ts +++ b/app/client/platforms/openai.ts @@ -83,7 +83,7 @@ export class ChatGPTApi implements LLMApi { const base64 = Buffer.from(response.data, "binary").toString("base64"); return base64; }; - if (options.config.model === "gpt-4-vision-preview") { + if (options.config.model.includes("vision")) { for (const v of options.messages) { let message: { role: string; @@ -102,10 +102,91 @@ export class ChatGPTApi implements LLMApi { }); if (v.image_url) { var base64Data = await getImageBase64Data(v.image_url); + interface MIMEMap { + [key: string]: string; + } + + const extensionToMIME: MIMEMap = { + 'png': 'image/png', + 'jpg': 'image/jpeg', + 'webp': 'image/webp', + 'gif': 'image/gif', + 'bmp': 'image/bmp', + 'svg': 'image/svg+xml', + 'txt': 'text/plain', + 'pdf': 'application/pdf', + 'doc': 'application/msword', + 'docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document', + 'xls': 'application/vnd.ms-excel', + 'xlsx': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', + 'ppt': 'application/vnd.ms-powerpoint', + 'pptx': 'application/vnd.openxmlformats-officedocument.presentationml.presentation', + 'zip': 'application/zip', + 'rar': 'application/x-rar-compressed', + 'bin': 'application/octet-stream', + + // Audio + 'mp3': 'audio/mpeg', + 'wav': 'audio/wav', + 'ogg': 'audio/ogg', + 'flac': 'audio/flac', + 'aac': 'audio/aac', + 'weba': 'audio/webm', + 'midi': 'audio/midi', + + // Video + 'mp4': 'video/mp4', + 'webm': 'video/webm', + 'avi': 'video/x-msvideo', + 'wmv': 'video/x-ms-wmv', + 'flv': 'video/x-flv', + '3gp': 'video/3gpp', + 'mkv': 'video/x-matroska', + + //编程 + 'js': 'application/javascript', + 'json': 'application/json', + 'html': 'text/html', + 'css': 'text/css', + 'xml': 'application/xml', + 'csv': 'text/csv', + 'ts': 'text/typescript', + 'java': 'text/x-java-source', + 'py': 'text/x-python', + 'c': 'text/x-csrc', + 'cpp': 'text/x-c++src', + 'h': 'text/x-chdr', + 'hpp': 'text/x-c++hdr', + 'php': 'application/x-httpd-php', + 'rb': 'text/x-ruby', + 'go': 'text/x-go', + 'rs': 'text/rust', + 'swift': 'text/x-swift', + 'kt': 'text/x-kotlin', + 'scala': 'text/x-scala', + }; + + let mimeType: string | undefined; + try { + // 使用正则表达式获取文件后缀 + const match = v.image_url.match(/\.(\w+)$/); + if (match) { + const fileExtension = match[1].toLowerCase(); + mimeType = extensionToMIME[fileExtension]; + if (!mimeType) { + throw new Error('Unknown file extension: ' + fileExtension); + } + } else { + throw new Error('Unable to extract file extension from the URL'); + } + } catch (error) { + // 使用通用的MIME类型 + mimeType = 'text/plain'; + } message.content.push({ type: "image_url", image_url: { - url: `data:image/jpeg;base64,${base64Data}`, + url: `data:${mimeType};base64,${base64Data}`, }, }); } @@ -136,7 +217,7 @@ export class ChatGPTApi implements LLMApi { frequency_penalty: modelConfig.frequency_penalty, top_p: modelConfig.top_p, max_tokens: - modelConfig.model == "gpt-4-vision-preview" + modelConfig.model.includes("vision") ? modelConfig.max_tokens : null, // max_tokens: Math.max(modelConfig.max_tokens, 1024), @@ -226,7 +307,7 @@ export class ChatGPTApi implements LLMApi { try { const resJson = await res.clone().json(); extraInfo = prettyObject(resJson); - } catch {} + } catch { } if (res.status === 401) { responseTexts.push(Locale.Error.Unauthorized); diff --git a/app/components/chat.tsx b/app/components/chat.tsx index 42f38a1..fe962db 100644 --- a/app/components/chat.tsx +++ b/app/components/chat.tsx @@ -373,17 +373,17 @@ function ChatAction(props: { style={ props.icon && !props.loding ? ({ - "--icon-width": `${width.icon}px`, - "--full-width": `${width.full}px`, - ...props.style, - } as React.CSSProperties) + "--icon-width": `${width.icon}px`, + "--full-width": `${width.full}px`, + ...props.style, + } as React.CSSProperties) : props.loding - ? ({ + ? ({ "--icon-width": `30px`, "--full-width": `30px`, ...props.style, } as React.CSSProperties) - : props.style + : props.style } > {props.icon ? ( @@ -538,7 +538,7 @@ export function ChatActions(props: { } } }; - if (currentModel === "gpt-4-vision-preview") { + if (currentModel.includes("vision")) { window.addEventListener("paste", onPaste); return () => { window.removeEventListener("paste", onPaste); @@ -609,7 +609,7 @@ export function ChatActions(props: { {config.pluginConfig.enable && /^gpt(?!.*03\d{2}$).*$/.test(currentModel) && - currentModel != "gpt-4-vision-preview" && ( + !currentModel.includes("vision") && ( : } /> )} - {currentModel == "gpt-4-vision-preview" && ( + {currentModel.includes("vision") && ( } innerNode={ 0 && config.sendPreviewBubble ? [ - { - ...createMessage({ - role: "user", - content: userInput, - image_url: userImage?.fileUrl, - }), - preview: true, - }, - ] + { + ...createMessage({ + role: "user", + content: userInput, + image_url: userImage?.fileUrl, + }), + preview: true, + }, + ] : [], ); }, [ @@ -1149,7 +1149,7 @@ function _Chat() { if (payload.key || payload.url) { showConfirm( Locale.URLCommand.Settings + - `\n${JSON.stringify(payload, null, 4)}`, + `\n${JSON.stringify(payload, null, 4)}`, ).then((res) => { if (!res) return; if (payload.key) { @@ -1412,7 +1412,7 @@ function _Chat() { defaultShow={i >= messages.length - 6} /> - {!isUser && message.model == "gpt-4-vision-preview" && ( + {!isUser && message?.model?.includes("vision") && (
escapeDollarNumber(props.content), [props.content], ); + // 判断文件路径 + const isImage = (filename: any) => { + const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.bmp', '.webp', '.tiff']; + return imageExtensions.some(ext => filename.toLowerCase().endsWith(ext)); + }; + const show_filename = (base64: any) => { + let parts = base64.split("/"); + return parts.pop(); + }; return (
- {props.imageBase64 && } + {props.imageBase64 && isImage(props.imageBase64) && } + {props.imageBase64 && !isImage(props.imageBase64) && + + 文件:{show_filename(props.imageBase64)} + + } + 0 && - modelConfig.model.startsWith("gpt") && - modelConfig.model != "gpt-4-vision-preview" + !modelConfig.model.includes("vision") ) { console.log("[ToolAgent] start"); const pluginToolNames = allPlugins.map((m) => m.toolName);