✨ 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,60 @@
import { describe, expect, it } from 'vitest'
import { MAX_IMAGE_BYTES, MAX_IMAGES, pickImages, toAskImage, transferFiles } from './qaImages'
interface FakeFile { name: string, type: string, size: number }
const file = (name: string, type = 'image/png', size = 1024): FakeFile => ({ name, type, size })
/** 剪贴板 / 拖进来的一项:文件,或文字、网页片段这种字符串 */
function item(value: FakeFile | string): { kind: string, getAsFile: () => FakeFile | null } {
return typeof value === 'string'
? { kind: 'string', getAsFile: () => null }
: { kind: 'file', getAsFile: () => value }
}
describe('剪贴板里的东西', () => {
it('只取文件,文字、网页片段不算(照常粘成文字);取不出文件的跳过', () => {
const shot = file('截图.png')
const copied = file('image.jpg', 'image/jpeg')
expect(transferFiles([item('<img src="x">'), item(shot), item('说明文字'), item(copied)])).toEqual([shot, copied])
expect(transferFiles([{ kind: 'file', getAsFile: () => null }])).toEqual([])
expect(transferFiles([item('只有文字')])).toEqual([])
})
})
describe('能带上哪些图片', () => {
it('认的四种格式(PNG、JPEG、GIF、WebP)都带上,按先后', () => {
const files = [file('a.png'), file('b.jpg', 'image/jpeg'), file('c.gif', 'image/gif'), file('d.webp', 'image/webp')]
expect(pickImages(files, 0)).toEqual({ accepted: files })
})
it('不是这几种格式的不带(SVG、PDF 等),说一句原因', () => {
const png = file('a.png')
expect(pickImages([file('x.svg', 'image/svg+xml'), png, file('y.pdf', 'application/pdf')], 0))
.toEqual({ accepted: [png], notice: '只能贴 PNG、JPEG、GIF、WebP 图片,有 2 个没加上。' })
})
it('每张不超过 5 MB:正好 5 MB 的带上,大一点的不带', () => {
const edge = file('edge.png', 'image/png', MAX_IMAGE_BYTES)
expect(pickImages([edge, file('big.png', 'image/png', MAX_IMAGE_BYTES + 1)], 0))
.toEqual({ accepted: [edge], notice: '每张图片不能超过 5 MB,有 1 个没加上。' })
})
it('连同已经贴的最多 6 张:多出来的不带;已经满了一张都不带', () => {
const files = [file('1.png'), file('2.png'), file('3.png')]
expect(pickImages(files, MAX_IMAGES - 2)).toEqual({ accepted: files.slice(0, 2), notice: '一个问题最多带 6 张图片,有 1 个没加上。' })
expect(pickImages(files, MAX_IMAGES)).toEqual({ accepted: [], notice: '一个问题最多带 6 张图片,有 3 个没加上。' })
})
it('几种原因一起说', () => {
const picked = pickImages([file('a.txt', 'text/plain'), file('big.png', 'image/png', MAX_IMAGE_BYTES * 2), ...Array.from({ length: 7 }, (_, i) => file(`${i}.png`))], 0)
expect(picked.accepted).toHaveLength(MAX_IMAGES)
expect(picked.notice).toBe('只能贴 PNG、JPEG、GIF、WebP 图片;每张图片不能超过 5 MB;一个问题最多带 6 张图片,有 3 个没加上。')
})
})
describe('发给扩展端的样子', () => {
it('data: 地址去掉前缀剩下 base64,格式照原样', () => {
expect(toAskImage({ id: 1, mediaType: 'image/png', url: 'data:image/png;base64,iVBORw0KGgo=' })).toEqual({ mediaType: 'image/png', base64: 'iVBORw0KGgo=' })
})
})