thread-master/apps/web/src/lib/attachImage.ts

73 lines
2.4 KiB
TypeScript
Raw Normal View History

2026-07-10 05:10:31 +00:00
import { newId } from "./id";
/** 編輯器內附圖(本地選檔或 mock 產圖) */
export type AttachedImage = {
id: string;
url: string;
name?: string;
/** 產圖 prompt選檔可無 */
prompt?: string;
};
export const MAX_ATTACHED_IMAGES = 10;
/** 單檔上限(讀成 data URL 前) */
const MAX_FILE_BYTES = 8 * 1024 * 1024;
export function withAttachedImageNote(text: string, count: number): string {
const body = text.trim();
if (!count || count <= 0) return body;
if (/(附圖\s*\d+\s*張)/.test(body)) return body;
return `${body}\n\n附圖 ${count} 張)`;
}
function readFileAsDataUrl(file: File): Promise<string> {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(String(reader.result || ""));
reader.onerror = () => reject(new Error(`讀取失敗:${file.name}`));
reader.readAsDataURL(file);
});
}
/**
* data URL
* mock repo
*/
export async function filesToAttachedImages(
files: FileList | File[],
opts?: { max?: number; existingCount?: number },
): Promise<AttachedImage[]> {
const max = opts?.max ?? MAX_ATTACHED_IMAGES;
const existing = opts?.existingCount ?? 0;
const room = Math.max(0, max - existing);
const list = Array.from(files).filter((f) => f.type.startsWith("image/"));
if (list.length === 0) throw new Error("請選擇圖片檔");
const take = list.slice(0, room);
if (take.length === 0) throw new Error(`最多附 ${max} 張圖`);
const out: AttachedImage[] = [];
for (const file of take) {
if (file.size > MAX_FILE_BYTES) {
throw new Error(`${file.name} 超過 8MB`);
}
const url = await readFileAsDataUrl(file);
if (!url.startsWith("data:image")) throw new Error(`無法讀取:${file.name}`);
out.push({
id: newId("img"),
url,
name: file.name,
});
}
return out;
}
/** 大 data URL 不寫進 localStorage改成可顯示的 mock 縮圖 seed */
export function toPersistableImageUrl(img: AttachedImage): string {
if (img.url.startsWith("data:") && img.url.length > 1800) {
const seed = encodeURIComponent((img.name || img.id || "attach").slice(0, 40));
return `https://api.dicebear.com/9.x/shapes/svg?seed=${seed}&backgroundColor=c0aede,ffd5dc,b6e3f4`;
}
return img.url;
}