mirror of
https://github.com/Yanyutin753/ChatGPT-Next-Web-LangChain-Gpt-4-All.git
synced 2025-10-15 15:41:23 +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);
|
const buffer = Buffer.from(imageData);
|
||||||
|
|
||||||
var fileName = `${Date.now()}.png`;
|
// 使用获取到的文件后缀
|
||||||
|
var fileName = image.name;
|
||||||
var filePath = "";
|
var filePath = "";
|
||||||
const serverConfig = getServerSideConfig();
|
const serverConfig = getServerSideConfig();
|
||||||
if (serverConfig.isStoreFileToLocal) {
|
if (serverConfig.isStoreFileToLocal) {
|
||||||
|
@@ -83,7 +83,7 @@ export class ChatGPTApi implements LLMApi {
|
|||||||
const base64 = Buffer.from(response.data, "binary").toString("base64");
|
const base64 = Buffer.from(response.data, "binary").toString("base64");
|
||||||
return base64;
|
return base64;
|
||||||
};
|
};
|
||||||
if (options.config.model === "gpt-4-vision-preview") {
|
if (options.config.model.includes("vision")) {
|
||||||
for (const v of options.messages) {
|
for (const v of options.messages) {
|
||||||
let message: {
|
let message: {
|
||||||
role: string;
|
role: string;
|
||||||
@@ -102,10 +102,91 @@ export class ChatGPTApi implements LLMApi {
|
|||||||
});
|
});
|
||||||
if (v.image_url) {
|
if (v.image_url) {
|
||||||
var base64Data = await getImageBase64Data(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({
|
message.content.push({
|
||||||
type: "image_url",
|
type: "image_url",
|
||||||
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,
|
frequency_penalty: modelConfig.frequency_penalty,
|
||||||
top_p: modelConfig.top_p,
|
top_p: modelConfig.top_p,
|
||||||
max_tokens:
|
max_tokens:
|
||||||
modelConfig.model == "gpt-4-vision-preview"
|
modelConfig.model.includes("vision")
|
||||||
? modelConfig.max_tokens
|
? modelConfig.max_tokens
|
||||||
: null,
|
: null,
|
||||||
// max_tokens: Math.max(modelConfig.max_tokens, 1024),
|
// max_tokens: Math.max(modelConfig.max_tokens, 1024),
|
||||||
|
@@ -538,7 +538,7 @@ export function ChatActions(props: {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
if (currentModel === "gpt-4-vision-preview") {
|
if (currentModel.includes("vision")) {
|
||||||
window.addEventListener("paste", onPaste);
|
window.addEventListener("paste", onPaste);
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("paste", onPaste);
|
window.removeEventListener("paste", onPaste);
|
||||||
@@ -609,7 +609,7 @@ export function ChatActions(props: {
|
|||||||
|
|
||||||
{config.pluginConfig.enable &&
|
{config.pluginConfig.enable &&
|
||||||
/^gpt(?!.*03\d{2}$).*$/.test(currentModel) &&
|
/^gpt(?!.*03\d{2}$).*$/.test(currentModel) &&
|
||||||
currentModel != "gpt-4-vision-preview" && (
|
!currentModel.includes("vision") && (
|
||||||
<ChatAction
|
<ChatAction
|
||||||
onClick={switchUsePlugins}
|
onClick={switchUsePlugins}
|
||||||
text={
|
text={
|
||||||
@@ -620,16 +620,16 @@ export function ChatActions(props: {
|
|||||||
icon={usePlugins ? <EnablePluginIcon /> : <DisablePluginIcon />}
|
icon={usePlugins ? <EnablePluginIcon /> : <DisablePluginIcon />}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{currentModel == "gpt-4-vision-preview" && (
|
{currentModel.includes("vision") && (
|
||||||
<ChatAction
|
<ChatAction
|
||||||
onClick={selectImage}
|
onClick={selectImage}
|
||||||
text="选择图片"
|
text="选择文档"
|
||||||
loding={uploadLoading}
|
loding={uploadLoading}
|
||||||
icon={<UploadIcon />}
|
icon={<UploadIcon />}
|
||||||
innerNode={
|
innerNode={
|
||||||
<input
|
<input
|
||||||
type="file"
|
type="file"
|
||||||
accept=".png,.jpg,.webp,.jpeg"
|
accept="*/*"
|
||||||
id="chat-image-file-select-upload"
|
id="chat-image-file-select-upload"
|
||||||
style={{ display: "none" }}
|
style={{ display: "none" }}
|
||||||
onChange={onImageSelected}
|
onChange={onImageSelected}
|
||||||
@@ -1412,7 +1412,7 @@ function _Chat() {
|
|||||||
defaultShow={i >= messages.length - 6}
|
defaultShow={i >= messages.length - 6}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{!isUser && message.model == "gpt-4-vision-preview" && (
|
{!isUser && message?.model?.includes("vision") && (
|
||||||
<div
|
<div
|
||||||
className={[
|
className={[
|
||||||
styles["chat-message-actions"],
|
styles["chat-message-actions"],
|
||||||
|
@@ -121,10 +121,25 @@ function _MarkDownContent(props: { content: string; imageBase64?: string }) {
|
|||||||
() => escapeDollarNumber(props.content),
|
() => escapeDollarNumber(props.content),
|
||||||
[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 (
|
return (
|
||||||
<div style={{ fontSize: "inherit" }}>
|
<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
|
<ReactMarkdown
|
||||||
remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
|
remarkPlugins={[RemarkMath, RemarkGfm, RemarkBreaks]}
|
||||||
rehypePlugins={[
|
rehypePlugins={[
|
||||||
|
@@ -335,8 +335,7 @@ export const useChatStore = createPersistStore(
|
|||||||
config.pluginConfig.enable &&
|
config.pluginConfig.enable &&
|
||||||
session.mask.usePlugins &&
|
session.mask.usePlugins &&
|
||||||
allPlugins.length > 0 &&
|
allPlugins.length > 0 &&
|
||||||
modelConfig.model.startsWith("gpt") &&
|
!modelConfig.model.includes("vision")
|
||||||
modelConfig.model != "gpt-4-vision-preview"
|
|
||||||
) {
|
) {
|
||||||
console.log("[ToolAgent] start");
|
console.log("[ToolAgent] start");
|
||||||
const pluginToolNames = allPlugins.map((m) => m.toolName);
|
const pluginToolNames = allPlugins.map((m) => m.toolName);
|
||||||
|
Reference in New Issue
Block a user