θΏ™ζ˜―indexlocζδΎ›ηš„ζœεŠ‘οΌŒδΈθ¦θΎ“ε…₯任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import useUser from "@/hooks/useUser";
export const DndUploaderContext = createContext();
export const REMOVE_ATTACHMENT_EVENT = "ATTACHMENT_REMOVE";
export const CLEAR_ATTACHMENTS_EVENT = "ATTACHMENT_CLEAR";
export const PASTE_ATTACHMENT_EVENT = "ATTACHMENT_PASTED";

/**
* File Attachment for automatic upload on the chat container page.
Expand Down Expand Up @@ -36,10 +37,15 @@ export function DnDFileUploaderProvider({ workspace, children }) {
useEffect(() => {
window.addEventListener(REMOVE_ATTACHMENT_EVENT, handleRemove);
window.addEventListener(CLEAR_ATTACHMENTS_EVENT, resetAttachments);
window.addEventListener(PASTE_ATTACHMENT_EVENT, handlePastedAttachment);

return () => {
window.removeEventListener(REMOVE_ATTACHMENT_EVENT, handleRemove);
window.removeEventListener(CLEAR_ATTACHMENTS_EVENT, resetAttachments);
window.removeEventListener(
PASTE_ATTACHMENT_EVENT,
handlePastedAttachment
);
};
}, []);

Expand Down Expand Up @@ -86,6 +92,39 @@ export function DnDFileUploaderProvider({ workspace, children }) {
);
}

/**
* Handle pasted attachments.
* @param {CustomEvent<{files: File[]}>} event
*/
async function handlePastedAttachment(event) {
const { files = [] } = event.detail;
if (!files.length) return;
const newAccepted = [];
for (const file of files) {
if (file.type.startsWith("image/")) {
newAccepted.push({
uid: v4(),
file,
contentString: await toBase64(file),
status: "success",
error: null,
type: "attachment",
});
} else {
newAccepted.push({
uid: v4(),
file,
contentString: null,
status: "in_progress",
error: null,
type: "upload",
});
}
}
setFiles((prev) => [...prev, ...newAccepted]);
embedEligibleAttachments(newAccepted);
}

/**
* Handle dropped files.
* @param {Attachment[]} acceptedFiles
Expand Down Expand Up @@ -119,8 +158,15 @@ export function DnDFileUploaderProvider({ workspace, children }) {
}

setFiles((prev) => [...prev, ...newAccepted]);
embedEligibleAttachments(newAccepted);
}

for (const attachment of newAccepted) {
/**
* Embeds attachments that are eligible for embedding - basically files that are not images.
* @param {Attachment[]} newAttachments
*/
function embedEligibleAttachments(newAttachments = []) {
for (const attachment of newAttachments) {
// Images/attachments are chat specific.
if (attachment.type === "attachment") continue;

Expand Down Expand Up @@ -200,7 +246,7 @@ export default function DnDFileUploaderWrapper({ children }) {
/**
* Convert image types into Base64 strings for requests.
* @param {File} file
* @returns {string}
* @returns {Promise<string>}
*/
async function toBase64(file) {
return new Promise((resolve, reject) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export default function AttachmentManager({ attachments }) {
* @param {{attachment: import("../../DnDWrapper").Attachment}}
*/
function AttachmentItem({ attachment }) {
const { uid, file, status, error, document, type } = attachment;
const { uid, file, status, error, document, type, contentString } =
attachment;
const { iconBgColor, Icon } = displayFromFile(file);

function removeFileFromQueue() {
Expand Down Expand Up @@ -127,11 +128,18 @@ function AttachmentItem({ attachment }) {
/>
</button>
</div>
<div
className={`${iconBgColor} rounded-lg flex items-center justify-center flex-shrink-0 p-1`}
>
<Icon size={30} className="text-white" />
</div>
{contentString ? (
<img
src={contentString}
className={`${iconBgColor} w-[30px] h-[30px] rounded-lg flex items-center justify-center`}
/>
) : (
<div
className={`${iconBgColor} rounded-lg flex items-center justify-center flex-shrink-0 p-1`}
>
<Icon size={30} className="text-white" />
</div>
)}
<div className="flex flex-col w-[130px]">
<p className="text-white text-xs font-medium truncate">
{file.name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import SpeechToText from "./SpeechToText";
import { Tooltip } from "react-tooltip";
import AttachmentManager from "./Attachments";
import AttachItem from "./AttachItem";
import { PASTE_ATTACHMENT_EVENT } from "../DnDWrapper";

export const PROMPT_INPUT_EVENT = "set_prompt_input";
export default function PromptInput({
Expand Down Expand Up @@ -91,6 +92,39 @@ export default function PromptInput({
element.style.height = `${element.scrollHeight}px`;
};

const handlePasteEvent = (e) => {
e.preventDefault();
if (e.clipboardData.items.length === 0) return false;

// paste any clipboard items that are images.
for (const item of e.clipboardData.items) {
if (item.type.startsWith("image/")) {
const file = item.getAsFile();
window.dispatchEvent(
new CustomEvent(PASTE_ATTACHMENT_EVENT, {
detail: { files: [file] },
})
);
continue;
}

// handle files specifically that are not images as uploads
if (item.kind === "file") {
const file = item.getAsFile();
window.dispatchEvent(
new CustomEvent(PASTE_ATTACHMENT_EVENT, {
detail: { files: [file] },
})
);
continue;
}
}

const pasteText = e.clipboardData.getData("text/plain");
if (pasteText) setPromptInput(pasteText.trim());
return;
};

const watchForSlash = debounce(checkForSlash, 300);
const watchForAt = debounce(checkForAt, 300);

Expand Down Expand Up @@ -125,6 +159,7 @@ export default function PromptInput({
setPromptInput(e.target.value);
}}
onKeyDown={captureEnter}
onPaste={handlePasteEvent}
required={true}
disabled={inputDisabled}
onFocus={() => setFocused(true)}
Expand Down