mirror of
https://github.com/Yanyutin753/ChatGPT-Next-Web-LangChain-Gpt-4-All.git
synced 2025-10-14 07:00:58 +00:00
适配上传不同类型的文件,适配gpt-4-all,支持包含vision字段的模型,自动打开上传文件按钮
This commit is contained in:
@@ -32,7 +32,8 @@ 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) {
|
||||
|
@@ -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);
|
||||
|
@@ -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") && (
|
||||
<ChatAction
|
||||
onClick={switchUsePlugins}
|
||||
text={
|
||||
@@ -620,16 +620,16 @@ export function ChatActions(props: {
|
||||
icon={usePlugins ? <EnablePluginIcon /> : <DisablePluginIcon />}
|
||||
/>
|
||||
)}
|
||||
{currentModel == "gpt-4-vision-preview" && (
|
||||
{currentModel.includes("vision") && (
|
||||
<ChatAction
|
||||
onClick={selectImage}
|
||||
text="选择图片"
|
||||
text="选择文档"
|
||||
loding={uploadLoading}
|
||||
icon={<UploadIcon />}
|
||||
innerNode={
|
||||
<input
|
||||
type="file"
|
||||
accept=".png,.jpg,.webp,.jpeg"
|
||||
accept="*/*"
|
||||
id="chat-image-file-select-upload"
|
||||
style={{ display: "none" }}
|
||||
onChange={onImageSelected}
|
||||
@@ -1031,28 +1031,28 @@ function _Chat() {
|
||||
.concat(
|
||||
isLoading
|
||||
? [
|
||||
{
|
||||
...createMessage({
|
||||
role: "assistant",
|
||||
content: "……",
|
||||
}),
|
||||
preview: true,
|
||||
},
|
||||
]
|
||||
{
|
||||
...createMessage({
|
||||
role: "assistant",
|
||||
content: "……",
|
||||
}),
|
||||
preview: true,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
)
|
||||
.concat(
|
||||
userInput.length > 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}
|
||||
/>
|
||||
</div>
|
||||
{!isUser && message.model == "gpt-4-vision-preview" && (
|
||||
{!isUser && message?.model?.includes("vision") && (
|
||||
<div
|
||||
className={[
|
||||
styles["chat-message-actions"],
|
||||
|
@@ -121,10 +121,25 @@ function _MarkDownContent(props: { content: string; imageBase64?: string }) {
|
||||
() => 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 (
|
||||
<div style={{ fontSize: "inherit" }}>
|
||||
{props.imageBase64 && <img src={props.imageBase64} alt="" />}
|
||||
{props.imageBase64 && isImage(props.imageBase64) && <img src={props.imageBase64} alt="" />}
|
||||
{props.imageBase64 && !isImage(props.imageBase64) &&
|
||||
<a href={props.imageBase64} style={{ fontWeight: 'bold' }}>
|
||||
文件:{show_filename(props.imageBase64)}
|
||||
</a>
|
||||
}
|
||||
|
||||
<ReactMarkdown
|
||||
remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
|
||||
rehypePlugins={[
|
||||
|
@@ -335,8 +335,7 @@ export const useChatStore = createPersistStore(
|
||||
config.pluginConfig.enable &&
|
||||
session.mask.usePlugins &&
|
||||
allPlugins.length > 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);
|
||||
|
Reference in New Issue
Block a user