feat(core): resolve prompt attachments
This commit is contained in:
parent
c13f06c30c
commit
4d100de194
29 changed files with 610 additions and 426 deletions
|
|
@ -1,41 +1,6 @@
|
|||
export * as Mime from "./mime.js"
|
||||
|
||||
import { Effect, FileSystem, Option } from "effect"
|
||||
import { fileURLToPath } from "url"
|
||||
import { FSUtil } from "./fs-util"
|
||||
|
||||
const SAMPLE_BYTES = 8192
|
||||
|
||||
export const resolve = Effect.fn("Mime.resolve")(function* (uri: string) {
|
||||
const data = dataSample(uri)
|
||||
if (data) return detect(data)
|
||||
|
||||
const target = yield* Effect.try({
|
||||
try: () => localPath(uri),
|
||||
catch: () => new Error("Invalid file URI"),
|
||||
}).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
||||
if (!target) return "application/octet-stream"
|
||||
|
||||
const fs = yield* FSUtil.Service
|
||||
const local = yield* Effect.scoped(
|
||||
Effect.gen(function* () {
|
||||
const info = yield* fs.stat(target)
|
||||
if (info.type === "Directory") return { type: "directory" as const }
|
||||
if (info.type !== "File") return
|
||||
const file = yield* fs.open(target)
|
||||
return {
|
||||
type: "file" as const,
|
||||
sample: Option.getOrElse(yield* file.readAlloc(FileSystem.Size(SAMPLE_BYTES)), () => new Uint8Array()),
|
||||
}
|
||||
}),
|
||||
).pipe(Effect.catch(() => Effect.succeed(undefined)))
|
||||
|
||||
if (local?.type === "directory") return "application/x-directory"
|
||||
if (local?.type === "file") return detect(local.sample)
|
||||
return "application/octet-stream"
|
||||
})
|
||||
|
||||
function detect(bytes: Uint8Array) {
|
||||
export function detect(bytes: Uint8Array) {
|
||||
if (startsWith(bytes, [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a])) return "image/png"
|
||||
if (startsWith(bytes, [0xff, 0xd8, 0xff])) return "image/jpeg"
|
||||
if (startsWith(bytes, [0x47, 0x49, 0x46, 0x38])) return "image/gif"
|
||||
|
|
@ -52,25 +17,6 @@ function detect(bytes: Uint8Array) {
|
|||
return isText(bytes) ? "text/plain" : "application/octet-stream"
|
||||
}
|
||||
|
||||
function dataSample(uri: string) {
|
||||
if (!uri.startsWith("data:")) return
|
||||
const comma = uri.indexOf(",")
|
||||
if (comma === -1) return new Uint8Array()
|
||||
const metadata = uri.slice(5, comma)
|
||||
const payload = uri.slice(comma + 1)
|
||||
if (metadata.split(";").some((part) => part.toLowerCase() === "base64")) {
|
||||
return Buffer.from(payload.slice(0, Math.ceil((SAMPLE_BYTES * 4) / 3) + 4), "base64").subarray(0, SAMPLE_BYTES)
|
||||
}
|
||||
return new TextEncoder().encode(payload.slice(0, SAMPLE_BYTES))
|
||||
}
|
||||
|
||||
function localPath(uri: string) {
|
||||
if (!URL.canParse(uri)) return
|
||||
const url = new URL(uri)
|
||||
if (url.protocol !== "file:") return
|
||||
return fileURLToPath(url)
|
||||
}
|
||||
|
||||
function startsWith(bytes: Uint8Array, prefix: number[]) {
|
||||
return prefix.every((value, index) => bytes[index] === value)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue