mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-11-12 20:23:45 +08:00
修改: app/api/bedrock.ts
修改: app/api/bedrock/models.ts 修改: app/api/bedrock/utils.ts 修改: app/client/api.ts 修改: app/client/platforms/bedrock.ts 新文件: app/components/chat-actions.tsx 修改: app/components/chat.module.scss 修改: app/components/chat.tsx 修改: app/constant.ts 新文件: app/icons/document.svg 修改: app/locales/cn.ts 修改: app/locales/en.ts
This commit is contained in:
188
app/components/chat-actions.tsx
Normal file
188
app/components/chat-actions.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
import { ChatActions as Actions } from "./chat";
|
||||
import DocumentIcon from "../icons/document.svg";
|
||||
import LoadingButtonIcon from "../icons/loading.svg";
|
||||
import { ServiceProvider } from "../constant";
|
||||
import { useChatStore } from "../store";
|
||||
import { showToast } from "./ui-lib";
|
||||
import { MultimodalContent, MessageRole } from "../client/api";
|
||||
import { ChatMessage } from "../store/chat";
|
||||
|
||||
export function ChatActions(props: Parameters<typeof Actions>[0]) {
|
||||
const chatStore = useChatStore();
|
||||
const currentProviderName =
|
||||
chatStore.currentSession().mask.modelConfig?.providerName;
|
||||
const isBedrockProvider = currentProviderName === ServiceProvider.Bedrock;
|
||||
|
||||
async function uploadDocument() {
|
||||
const fileInput = document.createElement("input");
|
||||
fileInput.type = "file";
|
||||
fileInput.accept = ".pdf,.csv,.doc,.docx,.xls,.xlsx,.html,.txt,.md";
|
||||
fileInput.onchange = async (event: any) => {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
props.setUploading(true);
|
||||
try {
|
||||
// Get file extension and MIME type
|
||||
const format = file.name.split(".").pop()?.toLowerCase() || "";
|
||||
const supportedFormats = [
|
||||
"pdf",
|
||||
"csv",
|
||||
"doc",
|
||||
"docx",
|
||||
"xls",
|
||||
"xlsx",
|
||||
"html",
|
||||
"txt",
|
||||
"md",
|
||||
];
|
||||
|
||||
if (!supportedFormats.includes(format)) {
|
||||
throw new Error("Unsupported file format");
|
||||
}
|
||||
|
||||
// Map file extensions to MIME types
|
||||
const mimeTypes: { [key: string]: string } = {
|
||||
pdf: "application/pdf",
|
||||
csv: "text/csv",
|
||||
doc: "application/msword",
|
||||
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
xls: "application/vnd.ms-excel",
|
||||
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||
html: "text/html",
|
||||
txt: "text/plain",
|
||||
md: "text/markdown",
|
||||
};
|
||||
|
||||
// Convert file to base64
|
||||
const base64 = await new Promise<string>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
if (!e.target?.result) return reject("Failed to read file");
|
||||
// Get just the base64 data without the data URL prefix
|
||||
const base64 = (e.target.result as string).split(",")[1];
|
||||
resolve(base64);
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
// Format file size
|
||||
const size = file.size;
|
||||
let sizeStr = "";
|
||||
if (size < 1024) {
|
||||
sizeStr = size + " B";
|
||||
} else if (size < 1024 * 1024) {
|
||||
sizeStr = (size / 1024).toFixed(2) + " KB";
|
||||
} else {
|
||||
sizeStr = (size / (1024 * 1024)).toFixed(2) + " MB";
|
||||
}
|
||||
|
||||
// Create document content
|
||||
const content: MultimodalContent[] = [
|
||||
{
|
||||
type: "text",
|
||||
text: `Document: ${file.name} (${sizeStr})`,
|
||||
},
|
||||
{
|
||||
type: "document",
|
||||
document: {
|
||||
format,
|
||||
name: file.name,
|
||||
source: {
|
||||
bytes: base64,
|
||||
media_type: mimeTypes[format] || `application/${format}`,
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Send content to Bedrock
|
||||
const session = chatStore.currentSession();
|
||||
const modelConfig = session.mask.modelConfig;
|
||||
const api = await import("../client/api").then((m) =>
|
||||
m.getClientApi(modelConfig.providerName),
|
||||
);
|
||||
|
||||
// Create user message
|
||||
const userMessage: ChatMessage = {
|
||||
id: Date.now().toString(),
|
||||
role: "user" as MessageRole,
|
||||
content,
|
||||
date: new Date().toLocaleString(),
|
||||
isError: false,
|
||||
};
|
||||
|
||||
// Create bot message
|
||||
const botMessage: ChatMessage = {
|
||||
id: (Date.now() + 1).toString(),
|
||||
role: "assistant" as MessageRole,
|
||||
content: "",
|
||||
date: new Date().toLocaleString(),
|
||||
streaming: true,
|
||||
isError: false,
|
||||
};
|
||||
|
||||
// Add messages to session
|
||||
chatStore.updateCurrentSession((session) => {
|
||||
session.messages.push(userMessage, botMessage);
|
||||
});
|
||||
|
||||
// Make request
|
||||
api.llm.chat({
|
||||
messages: [userMessage],
|
||||
config: { ...modelConfig, stream: true },
|
||||
onUpdate(message) {
|
||||
botMessage.streaming = true;
|
||||
if (message) {
|
||||
botMessage.content = message;
|
||||
}
|
||||
chatStore.updateCurrentSession((session) => {
|
||||
session.messages = session.messages.concat();
|
||||
});
|
||||
},
|
||||
onFinish(message) {
|
||||
botMessage.streaming = false;
|
||||
if (message) {
|
||||
botMessage.content = message;
|
||||
chatStore.onNewMessage(botMessage);
|
||||
}
|
||||
},
|
||||
onError(error) {
|
||||
botMessage.content = error.message;
|
||||
botMessage.streaming = false;
|
||||
userMessage.isError = true;
|
||||
botMessage.isError = true;
|
||||
chatStore.updateCurrentSession((session) => {
|
||||
session.messages = session.messages.concat();
|
||||
});
|
||||
console.error("[Chat] failed ", error);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
console.error("Failed to upload document:", error);
|
||||
showToast("Failed to upload document");
|
||||
} finally {
|
||||
props.setUploading(false);
|
||||
}
|
||||
};
|
||||
fileInput.click();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="chat-input-actions">
|
||||
{/* Original actions */}
|
||||
<Actions {...props} />
|
||||
|
||||
{/* Document upload button (only for Bedrock) */}
|
||||
{isBedrockProvider && (
|
||||
<div className="chat-input-action">
|
||||
<div className="icon" onClick={uploadDocument}>
|
||||
{props.uploading ? <LoadingButtonIcon /> : <DocumentIcon />}
|
||||
</div>
|
||||
<div className="text">Upload Document</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -75,6 +75,17 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
--delay: 0.5s;
|
||||
width: var(--full-width);
|
||||
@@ -393,8 +404,8 @@
|
||||
|
||||
button {
|
||||
padding: 7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Specific styles for iOS devices */
|
||||
@media screen and (max-device-width: 812px) and (-webkit-min-device-pixel-ratio: 2) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import React, {
|
||||
Fragment,
|
||||
RefObject,
|
||||
} from "react";
|
||||
|
||||
import DocumentIcon from "../icons/document.svg";
|
||||
import SendWhiteIcon from "../icons/send-white.svg";
|
||||
import BrainIcon from "../icons/brain.svg";
|
||||
import RenameIcon from "../icons/rename.svg";
|
||||
@@ -548,6 +548,91 @@ export function ChatActions(props: {
|
||||
);
|
||||
}
|
||||
}, [chatStore, currentModel, models]);
|
||||
const isBedrockProvider = currentProviderName === ServiceProvider.Bedrock;
|
||||
|
||||
// ... (rest of the existing state and functions)
|
||||
|
||||
async function uploadDocument() {
|
||||
const fileInput = document.createElement("input");
|
||||
fileInput.type = "file";
|
||||
fileInput.accept = ".pdf,.csv,.doc,.docx,.xls,.xlsx,.html,.txt,.md";
|
||||
fileInput.onchange = async (event: any) => {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
props.setUploading(true);
|
||||
try {
|
||||
// Convert file to base64
|
||||
const base64 = await new Promise<string>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
if (!e.target?.result) return reject("Failed to read file");
|
||||
const base64 = (e.target.result as string).split(",")[1];
|
||||
resolve(base64);
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
|
||||
// Get file extension
|
||||
const format = file.name.split(".").pop()?.toLowerCase() || "";
|
||||
const supportedFormats = [
|
||||
"pdf",
|
||||
"csv",
|
||||
"doc",
|
||||
"docx",
|
||||
"xls",
|
||||
"xlsx",
|
||||
"html",
|
||||
"txt",
|
||||
"md",
|
||||
];
|
||||
|
||||
if (!supportedFormats.includes(format)) {
|
||||
throw new Error("Unsupported file format");
|
||||
}
|
||||
|
||||
// Format file size
|
||||
const size = file.size;
|
||||
let sizeStr = "";
|
||||
if (size < 1024) {
|
||||
sizeStr = size + " B";
|
||||
} else if (size < 1024 * 1024) {
|
||||
sizeStr = (size / 1024).toFixed(2) + " KB";
|
||||
} else {
|
||||
sizeStr = (size / (1024 * 1024)).toFixed(2) + " MB";
|
||||
}
|
||||
|
||||
// Create document content with only filename and size
|
||||
const documentContent = {
|
||||
type: "document",
|
||||
document: {
|
||||
format,
|
||||
name: file.name,
|
||||
size: sizeStr,
|
||||
source: {
|
||||
bytes: base64,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Submit the document content as a JSON string but only display filename and size
|
||||
const displayContent = `Document: ${file.name} (${sizeStr})`;
|
||||
chatStore.onUserInput(displayContent);
|
||||
|
||||
// Store the actual document content separately if needed
|
||||
// chatStore.updateCurrentSession((session) => {
|
||||
// session.lastDocument = documentContent;
|
||||
// });
|
||||
} catch (error) {
|
||||
console.error("Failed to upload document:", error);
|
||||
showToast("Failed to upload document");
|
||||
} finally {
|
||||
props.setUploading(false);
|
||||
}
|
||||
};
|
||||
fileInput.click();
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles["chat-input-actions"]}>
|
||||
@@ -580,6 +665,14 @@ export function ChatActions(props: {
|
||||
icon={props.uploading ? <LoadingButtonIcon /> : <ImageIcon />}
|
||||
/>
|
||||
)}
|
||||
{/* Add document upload button for Bedrock */}
|
||||
{isBedrockProvider && (
|
||||
<ChatAction
|
||||
onClick={uploadDocument}
|
||||
text={Locale.Chat.InputActions.UploadDocument}
|
||||
icon={props.uploading ? <LoadingButtonIcon /> : <DocumentIcon />}
|
||||
/>
|
||||
)}
|
||||
<ChatAction
|
||||
onClick={nextTheme}
|
||||
text={Locale.Chat.InputActions.Theme[theme]}
|
||||
|
||||
Reference in New Issue
Block a user