mirror of
https://github.com/luckyyzh/pi-agent-integrated.git
synced 2026-10-03 02:59:35 +00:00
feat: 识图提速(模型常驻/精简模板/磁盘缓存)+ WebUI 上传压缩 + 缓存清理脚本
- vision.ts: keep_alive:-1 常驻显存(治冷加载 7.7GB);转录缓存持久化 data/agent/vision-cache.json(重启后历史图秒回);hook 转录用精简模板 - ChatInput/image-attachments: 上传 JPEG 长边>1600px 自动压缩 q0.85 - scripts/cleanup-cache.mjs: npm/opengrep 缓存、过期会话、旧图瘦身清理 - README 双语同步
This commit is contained in:
@@ -6,6 +6,7 @@ import { clearDraft, getDraft, setDraft, type ChatDraftImage } from "@/lib/draft
|
||||
import {
|
||||
MAX_ATTACHED_IMAGE_BYTES,
|
||||
MAX_ATTACHED_IMAGES,
|
||||
compressImageFile,
|
||||
isBase64ImageWithinLimits,
|
||||
} from "@/lib/image-attachments";
|
||||
import {
|
||||
@@ -390,20 +391,21 @@ export const ChatInput = forwardRef<ChatInputHandle, Props>(function ChatInput({
|
||||
pendingImageCountRef.current += imageFiles.length;
|
||||
try {
|
||||
const newImages = await Promise.all(
|
||||
imageFiles.map(
|
||||
(file) =>
|
||||
new Promise<AttachedImage>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
const result = reader.result as string;
|
||||
// result is "data:<mime>;base64,<data>"
|
||||
const base64 = result.split(",")[1];
|
||||
resolve({ data: base64, mimeType: file.type, previewUrl: URL.createObjectURL(file) });
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
})
|
||||
)
|
||||
imageFiles.map(async (file) => {
|
||||
// 大 JPEG 先压缩(长边 1600/q0.85),会话不再内嵌数 MB base64,加载/传输/转录都更快
|
||||
const compressed = await compressImageFile(file);
|
||||
const base64 = await new Promise<string>((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => resolve((reader.result as string).split(",")[1]);
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(compressed);
|
||||
});
|
||||
return {
|
||||
data: base64,
|
||||
mimeType: compressed.type,
|
||||
previewUrl: URL.createObjectURL(compressed),
|
||||
};
|
||||
})
|
||||
);
|
||||
setAttachedImages((prev) => {
|
||||
const accepted = newImages.slice(0, Math.max(0, MAX_ATTACHED_IMAGES - prev.length));
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
export const MAX_ATTACHED_IMAGE_BYTES = 10 * 1024 * 1024;
|
||||
export const MAX_ATTACHED_IMAGES = 10;
|
||||
|
||||
/** 上传时自动压缩:长边超过此像素的 JPEG 缩到此边长(相机照片主场景,视觉模型输入上限 ~1344px,1600 无损)。 */
|
||||
export const MAX_IMAGE_EDGE_PX = 1600;
|
||||
/** JPEG 重编码质量:避免伪影糊掉边缘/小字。 */
|
||||
export const IMAGE_JPEG_QUALITY = 0.85;
|
||||
|
||||
export interface Base64ImageAttachment {
|
||||
data: string;
|
||||
mimeType: string;
|
||||
@@ -37,6 +42,41 @@ export function isBase64ImageWithinLimits(value: unknown): value is Base64ImageA
|
||||
return bytes !== null && bytes <= MAX_ATTACHED_IMAGE_BYTES;
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传前压缩:仅 JPEG 且长边超过 MAX_IMAGE_EDGE_PX 时缩放重编码(EXIF 方向由
|
||||
* createImageBitmap 默认 from-image 纠正)。PNG/WebP/GIF 原样返回(无损/动画场景)。
|
||||
* 重编码无收益(更小或失败)时退回原文件。
|
||||
*/
|
||||
export async function compressImageFile(file: File): Promise<File> {
|
||||
if (file.type !== "image/jpeg") return file;
|
||||
let bitmap: ImageBitmap;
|
||||
try {
|
||||
bitmap = await createImageBitmap(file);
|
||||
} catch {
|
||||
return file; // 解码失败退化为原文件
|
||||
}
|
||||
try {
|
||||
const edge = Math.max(bitmap.width, bitmap.height);
|
||||
if (edge <= MAX_IMAGE_EDGE_PX) return file; // 本就不大,避免无谓重编码
|
||||
const scale = MAX_IMAGE_EDGE_PX / edge;
|
||||
const width = Math.max(1, Math.round(bitmap.width * scale));
|
||||
const height = Math.max(1, Math.round(bitmap.height * scale));
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return file;
|
||||
ctx.drawImage(bitmap, 0, 0, width, height);
|
||||
const blob = await new Promise<Blob | null>((resolve) =>
|
||||
canvas.toBlob(resolve, "image/jpeg", IMAGE_JPEG_QUALITY)
|
||||
);
|
||||
if (!blob || blob.size >= file.size) return file; // 重编码无收益则用原文件
|
||||
return new File([blob], file.name.replace(/\.\w+$/, ".jpg"), { type: "image/jpeg" });
|
||||
} finally {
|
||||
bitmap.close();
|
||||
}
|
||||
}
|
||||
|
||||
/** Return an API-safe error for prompt, steering, and follow-up image arrays. */
|
||||
export function validateAgentImages(value: unknown): string | null {
|
||||
if (value === undefined) return null;
|
||||
|
||||
Reference in New Issue
Block a user