feat: configure canvas pages sent for note analysis
Canvas analysis previously sent every page. Add a Magic Notes setting for 1-8 canvas pages, defaulting to 1 for new and historical settings. Select the first N pages in current canvas order before capture and use the same text/image range for saved entries, drafts and canvas-source todos. Keep full 50-page saving/export, expose the same setting through config tools, and use the shared settings select with an inline help icon. Include pagination, persistence, production IPC and isolated Electron regressions, including text-only input and scaled page boundaries. Validation: focused feature tests and isolated Electron regressions passed; the final compact UI passed 14 tests. Typecheck and lint were reported passing by the coordinating agent. No full-suite pass is claimed because the completed full test command exposed no summary. No live model calls were made for this commit preparation. Release note: 魔法笔记新增“发送画布页数”设置,可选 1~8 页,默认 1 页,按当前画布顺序发送前 N 页的文字和图片;标题旁问号可查看说明,保存和导出完整画布不受影响。
This commit is contained in:
@@ -84,7 +84,7 @@ describe('magic note analyzer', () => {
|
||||
const { runtime, requests } = recordingRuntime()
|
||||
const comments = await analyzeMagicTodo(
|
||||
runtime, canvasTodo, options, undefined, undefined,
|
||||
{ supportsImageInput, content: canvasEntry.content }
|
||||
{ supportsImageInput, content: canvasEntry.content, canvasPageCount: 2 }
|
||||
)
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(requests[0]?.prompt).toContain(canvasTodo.title)
|
||||
@@ -127,7 +127,7 @@ describe('magic note analyzer', () => {
|
||||
const usage: RuntimeModelUsageEvent[] = []
|
||||
const comments = await analyzeMagicNoteEntry(
|
||||
runtime, canvasEntry, options, undefined,
|
||||
(event) => usage.push(event), { supportsImageInput: true }
|
||||
(event) => usage.push(event), { supportsImageInput: true, canvasPageCount: 2 }
|
||||
)
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(requests[0]?.images).toEqual([
|
||||
@@ -149,34 +149,59 @@ describe('magic note analyzer', () => {
|
||||
pages: canvasEntry.content.pages.map((page) => ({ ...page, objects: [] }))
|
||||
}
|
||||
const comments = await analyzeMagicNoteDraft(runtime, '', options, undefined, undefined, { supportsImageInput: true, content })
|
||||
expect(requests[0]?.images).toHaveLength(2)
|
||||
expect(requests[0]?.images).toHaveLength(1)
|
||||
expect(comments[0]?.inputMode).toBe('canvas-images')
|
||||
})
|
||||
|
||||
it('falls back to all extracted text for a text model, without silently slicing later pages', async () => {
|
||||
it('limits both continuous flow and objects to the first page for a text model', async () => {
|
||||
const { runtime, requests } = recordingRuntime()
|
||||
const content = {
|
||||
...canvasEntry.content,
|
||||
pages: canvasEntry.content.pages.map((page) => ({
|
||||
...page, objects: [{ text: '最后一页的结论' }]
|
||||
pages: canvasEntry.content.pages.map((page, index) => ({
|
||||
...page, objects: [{ text: index === 0 ? '首页结论' : '最后一页的结论' }]
|
||||
})),
|
||||
flow: { ops: [{ insert: '文'.repeat(30_001) }] }
|
||||
flow: { version: 1 as const, ops: [{ insert: '首页正文\n末页正文\n' }] }
|
||||
}
|
||||
const comments = await analyzeMagicNoteEntry(runtime, { ...canvasEntry, content }, options, undefined, undefined, { supportsImageInput: false })
|
||||
const comments = await analyzeMagicNoteEntry(runtime, { ...canvasEntry, content }, {
|
||||
...options, canvasPageText: [{ pageId: 'first', text: '首页正文\n' }, { pageId: 'second', text: '末页正文\n' }]
|
||||
}, undefined, undefined, { supportsImageInput: false })
|
||||
expect(requests[0]?.images).toBeUndefined()
|
||||
expect(requests[0]?.prompt).toContain('最后一页的结论')
|
||||
expect(requests[0]?.prompt).toContain('首页正文')
|
||||
expect(requests[0]?.prompt).toContain('首页结论')
|
||||
expect(requests[0]?.prompt).not.toContain('最后一页的结论')
|
||||
expect(requests[0]?.prompt).not.toContain('末页正文')
|
||||
expect(requests[0]?.prompt).toContain('未查看页面图像')
|
||||
expect(comments[0]?.inputMode).toBe('text-fallback')
|
||||
})
|
||||
|
||||
it('rejects incomplete flow page text without sending the full cross-page body', async () => {
|
||||
const { runtime, requests } = recordingRuntime()
|
||||
const content = { ...canvasEntry.content, flow: { version: 1 as const, ops: [{ insert: 'First and later pages\n' }] } }
|
||||
for (const canvasPageText of [undefined, [], [{ pageId: 'second', text: 'Later' }],
|
||||
[{ pageId: 'first', text: 'A' }, { pageId: 'first', text: 'B' }]]) {
|
||||
await expect(analyzeMagicNoteDraft(runtime, 'Full text', { ...options, canvasPageText }, undefined, undefined,
|
||||
{ supportsImageInput: false, content })).rejects.toThrow('分页文字缺失')
|
||||
}
|
||||
expect(requests).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('does not use later-page text when the selected first page is visual-only', async () => {
|
||||
const { runtime, requests } = recordingRuntime()
|
||||
const content = { ...canvasEntry.content, pages: canvasEntry.content.pages.map((page, index) => ({ ...page,
|
||||
objects: index === 0 ? [] : [{ text: 'Later-page text' }] })) }
|
||||
await expect(analyzeMagicNoteDraft(runtime, 'Later-page text', options, undefined, undefined,
|
||||
{ supportsImageInput: false, content })).rejects.toThrow('所选画布页中没有')
|
||||
expect(requests).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('rejects visual-only text fallback before issuing a request', async () => {
|
||||
const { runtime, requests } = recordingRuntime()
|
||||
const content = {
|
||||
...canvasEntry.content,
|
||||
pages: canvasEntry.content.pages.map((page) => ({ ...page, objects: [] })),
|
||||
flow: { ops: [{ insert: { image: 'placeholder' } }] }
|
||||
flow: { version: 1 as const, ops: [{ insert: { image: 'placeholder' } }] }
|
||||
}
|
||||
await expect(analyzeMagicNoteEntry(runtime, { ...canvasEntry, content, plainText: ' ' }, options, undefined, undefined, { supportsImageInput: false })).rejects.toThrow('请切换支持图像输入的默认模型')
|
||||
await expect(analyzeMagicNoteEntry(runtime, { ...canvasEntry, content, plainText: ' ' }, options, undefined, undefined, { supportsImageInput: false, canvasPageCount: 2 })).rejects.toThrow('请切换支持图像输入的默认模型')
|
||||
expect(requests).toHaveLength(0)
|
||||
})
|
||||
|
||||
@@ -190,7 +215,7 @@ describe('magic note analyzer', () => {
|
||||
const { runtime, requests } = recordingRuntime(error)
|
||||
await expect(analyzeMagicNoteEntry(runtime, canvasEntry, options, undefined, undefined, { supportsImageInput: true })).rejects.toThrow(error)
|
||||
expect(requests).toHaveLength(1)
|
||||
expect(requests[0]?.images).toHaveLength(2)
|
||||
expect(requests[0]?.images).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('keeps ordinary notes text-only even when the model supports images', async () => {
|
||||
@@ -214,7 +239,7 @@ describe('magic note analyzer', () => {
|
||||
}))
|
||||
}
|
||||
await analyzeMagicNoteDraft(runtime, '', options, undefined, undefined, {
|
||||
supportsImageInput: false, content
|
||||
supportsImageInput: false, content, canvasPageCount: 2
|
||||
})
|
||||
expect(requests[0]?.prompt).toContain('第 2 页:\\nPDF 原生文字 2')
|
||||
expect(requests[0]?.images).toBeUndefined()
|
||||
|
||||
Reference in New Issue
Block a user