perf(app): fix composer lag via buffered blob draft storage (#40207)
This commit is contained in:
parent
3307f99f1f
commit
6ff0adef22
32 changed files with 503 additions and 82 deletions
|
|
@ -78,6 +78,7 @@ export type PromptInputV2AttachmentConfig = {
|
|||
onError: (error: unknown) => void
|
||||
readClipboardImage?: () => Promise<File | null>
|
||||
getPathForFile?: (file: File) => string
|
||||
store?: (file: File) => Promise<{ id: string; url: string }>
|
||||
}
|
||||
|
||||
export function createPromptInputV2Attachments(
|
||||
|
|
@ -102,8 +103,7 @@ export function createPromptInputV2Attachments(
|
|||
if (toast) input.warn()
|
||||
return false
|
||||
}
|
||||
const url = await dataUrl(file, mime)
|
||||
if (!url) return false
|
||||
const blob = input.store ? await input.store(file) : await blobReference(file)
|
||||
const sourcePath = input.getPathForFile?.(file) || undefined
|
||||
// Native clipboard images arrive with a fresh timestamped filename on every paste, so identical
|
||||
// clipboard content is matched on bytes alone.
|
||||
|
|
@ -112,7 +112,7 @@ export function createPromptInputV2Attachments(
|
|||
.some(
|
||||
(part) =>
|
||||
part.type === "image" &&
|
||||
part.dataUrl === url &&
|
||||
part.blob.id === blob.id &&
|
||||
(sourcePath
|
||||
? part.sourcePath === sourcePath
|
||||
: !part.sourcePath && (clipboard || part.filename === file.name)),
|
||||
|
|
@ -127,7 +127,7 @@ export function createPromptInputV2Attachments(
|
|||
filename: file.name,
|
||||
sourcePath,
|
||||
mime,
|
||||
dataUrl: url,
|
||||
blob,
|
||||
}
|
||||
target.prompt.set([...target.prompt.current(), attachment], target.cursor)
|
||||
return true
|
||||
|
|
@ -219,20 +219,14 @@ export function createPromptInputV2Attachments(
|
|||
}
|
||||
}
|
||||
|
||||
function dataUrl(file: File, mime: string) {
|
||||
return new Promise<string>((resolve) => {
|
||||
const reader = new FileReader()
|
||||
reader.addEventListener("error", () => resolve(""))
|
||||
reader.addEventListener("load", () => {
|
||||
const value = typeof reader.result === "string" ? reader.result : ""
|
||||
const index = value.indexOf(",")
|
||||
resolve(index === -1 ? value : `data:${mime};base64,${value.slice(index + 1)}`)
|
||||
})
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
}
|
||||
|
||||
const imageMimes = new Set(["image/png", "image/jpeg", "image/gif", "image/webp"])
|
||||
|
||||
async function blobReference(file: File) {
|
||||
const id = Array.from(new Uint8Array(await crypto.subtle.digest("SHA-256", await file.arrayBuffer())))
|
||||
.map((byte) => byte.toString(16).padStart(2, "0"))
|
||||
.join("")
|
||||
return { id, url: URL.createObjectURL(file) }
|
||||
}
|
||||
const imageExtensions = new Map([
|
||||
["gif", "image/gif"],
|
||||
["jpeg", "image/jpeg"],
|
||||
|
|
|
|||
|
|
@ -425,7 +425,7 @@ export function PromptInputV2Attachments(props: {
|
|||
}
|
||||
>
|
||||
<img
|
||||
src={attachment.dataUrl}
|
||||
src={attachment.blob.url}
|
||||
alt={attachment.filename}
|
||||
class="w-[58px] h-[46px] rounded-[6px] object-cover"
|
||||
onClick={() => props.onAttachmentClick?.(attachment)}
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ function ControlledPromptInput() {
|
|||
id: "attachment-1",
|
||||
filename: "requirements.md",
|
||||
mime: "text/markdown",
|
||||
dataUrl: "data:text/markdown;base64,IyBSZXF1aXJlbWVudHM=",
|
||||
blob: { id: "requirements", url: "data:text/markdown;base64,IyBSZXF1aXJlbWVudHM=" },
|
||||
},
|
||||
],
|
||||
cursor: 0,
|
||||
|
|
@ -199,7 +199,7 @@ function ControlledPromptInput() {
|
|||
id: `attachment-${store.state.prompt.filter((part) => part.type === "image").length + 1}`,
|
||||
filename,
|
||||
mime,
|
||||
dataUrl: `data:${mime};base64,`,
|
||||
blob: { id: filename, url: `data:${mime};base64,` },
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ function createPromptStore() {
|
|||
id: "attachment-1",
|
||||
filename: "notes.txt",
|
||||
mime: "text/plain",
|
||||
dataUrl: "data:text/plain;base64,",
|
||||
blob: { id: "a", url: "blob:a" },
|
||||
},
|
||||
],
|
||||
cursor: 3,
|
||||
|
|
@ -50,7 +50,7 @@ describe("prompt input v2 store", () => {
|
|||
id: "attachment-1",
|
||||
filename: "notes.txt",
|
||||
mime: "text/plain",
|
||||
dataUrl: "data:text/plain;base64,",
|
||||
blob: { id: "a", url: "blob:a" },
|
||||
},
|
||||
])
|
||||
expect(prompt.state.cursor).toBe(7)
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ export type PromptInputV2Attachment = {
|
|||
filename: string
|
||||
sourcePath?: string
|
||||
mime: string
|
||||
dataUrl: string
|
||||
blob: { id: string; url: string }
|
||||
}
|
||||
|
||||
export type PromptInputV2Prompt = (
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue