✨ feat(vscode): 问 AI 输入框能粘贴、拖进图片随问题发给 Claude Code,缩略图能点开看大图,图片存进 .spx/code-qa 跟着问答删

This commit is contained in:
cruldra committed 2026-09-26 23:15:02 +08:00
1 parent d9a5c3673e
commit 36d842e18e
28 files changed
+825 -121

No files matched your search

@@ -0,0 +1,61 @@
/**
* 「问 AI」输入框里贴图、拖图的规则(不碰 React):认哪些格式、每张多大、最多几张,
* 贴进来还没发的图片在页面上的样子,发给扩展端的样子。
*/
import type { AskImage } from './types'
/** 认的格式:Claude 认的这几种 */
export const IMAGE_TYPES: readonly string[] = ['image/png', 'image/jpeg', 'image/gif', 'image/webp']
export const MAX_IMAGE_BYTES = 5 * 1024 * 1024
/** 一个问题最多带几张 */
export const MAX_IMAGES = 6
/** 贴进输入框、还没发的一张图片:url 是 data: 地址,缩略图、大图、发送都用它 */
export interface PendingImage {
id: number
mediaType: string
url: string
}
/** 剪贴板 / 拖进来的东西里的文件;文字、网页片段这些不算(照常粘成文字) */
export function transferFiles<F>(items: ArrayLike<{ kind: string, getAsFile: () => F | null }>): F[] {
return Array.from(items).flatMap(item => item.kind === 'file' ? item.getAsFile() ?? [] : [])
}
/**
* 挑出能带上的:只认 PNG、JPEG、GIF、WebP,每张不超过 MAX_IMAGE_BYTES,连同已经有的 have 张最多 MAX_IMAGES 张,按先后取;
* 有没加上的给一句原因。
*/
export function pickImages<F extends { type: string, size: number }>(files: readonly F[], have: number): { accepted: F[], notice?: string } {
const images = files.filter(file => IMAGE_TYPES.includes(file.type))
const small = images.filter(file => file.size <= MAX_IMAGE_BYTES)
const accepted = small.slice(0, Math.max(0, MAX_IMAGES - have))
const reasons = [
...(images.length < files.length ? ['只能贴 PNG、JPEG、GIF、WebP 图片'] : []),
...(small.length < images.length ? [`每张图片不能超过 ${MAX_IMAGE_BYTES / 1024 / 1024} MB`] : []),
...(accepted.length < small.length ? [`一个问题最多带 ${MAX_IMAGES} 张图片`] : []),
]
return reasons.length === 0
? { accepted }
: { accepted, notice: `${reasons.join(';')},有 ${files.length - accepted.length} 个没加上。` }
}
let imageCounter = 0
/** 读成 data: 地址(缩略图直接用,页面的安全策略允许 data: 图片) */
export function readImage(file: File): Promise<PendingImage> {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = () => resolve({ id: ++imageCounter, mediaType: file.type, url: String(reader.result) })
reader.onerror = () => reject(reader.error ?? new Error('读不出图片'))
reader.readAsDataURL(file)
})
}
/** 发给扩展端的样子:data: 地址去掉前缀剩下的 base64 */
export function toAskImage(image: PendingImage): AskImage {
return { mediaType: image.mediaType, base64: image.url.slice(image.url.indexOf(',') + 1) }
}