Update plugin ZTools 提供商 v1.0.1#305
Conversation
- feat: ZTools OCR + 翻译提供商插件(截图识别 / 代码翻译 / manage) - chore: 调整插件命名空间 - chore: 调整资源url - feat: 微信 OCR 原生模块支持 macOS(libwxocr.dylib) - feat: 截图识别结果改为独立窗口展示(左图右文 + 拖动缩放)
There was a problem hiding this comment.
Code Review
This pull request refactors the screenshot OCR feature to display results in a new, dedicated borderless child window instead of the main window. The main window now coordinates the capture and recognition process, opens the child window, injects the results, and exits, while the child window handles interactive image viewing and text selection. The review comments provide valuable feedback to improve robustness, including resetting the UI state when a capture is canceled, adding defensive checks to prevent executing JavaScript on a destroyed window, handling potential promise rejections from clipboard operations, and ensuring the image scale factor remains positive on extreme window resizes.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| if (!imgBase64) { | ||
| phase.value = 'idle' | ||
| errorToast('已取消截图') | ||
| // 取消后退出插件,回到宿主 | ||
| try { | ||
| window.ztools.outPlugin() | ||
| } catch (_) { | ||
| /* ignore */ | ||
| } | ||
| return | ||
| } |
There was a problem hiding this comment.
当用户取消截图时,phase.value 没有被重置为 'idle'。如果 window.ztools.outPlugin() 调用失败或未能成功关闭/隐藏插件窗口,UI 界面将会一直卡在 'capturing'(截图进行中)的状态。建议在退出前将 phase.value 重置为 'idle' 以保证状态机的健壮性。
if (!imgBase64) {
phase.value = 'idle'
errorToast('已取消截图')
// 取消后退出插件,回到宿主
try {
window.ztools.outPlugin()
} catch (_) {
/* ignore */
}
return
}
| function injectData( | ||
| win: BrowserWindow.WindowInstance, | ||
| data: { image: string; lines: OcrLine[]; isDark: boolean; logo?: string } | ||
| ): void { | ||
| try { | ||
| // 序列化为安全的 JSON,避免引号/换行破坏 JS 字符串 | ||
| const payload = JSON.stringify(data) | ||
| const code = `window.__loadScreenOcrResult && window.__loadScreenOcrResult(${payload});` | ||
| win.webContents.executeJavaScript(code) | ||
| } catch (_) { | ||
| /* ignore:兜底注入会再试 */ | ||
| } | ||
| } |
There was a problem hiding this comment.
在调用 win.webContents.executeJavaScript 之前,建议先检查窗口是否已被销毁(win.isDestroyed())。如果用户在 800ms 内快速关闭了结果窗口,延迟执行的 injectData 可能会尝试操作已销毁的窗口,从而引发异常。进行防御性检查可以提高代码的稳定性。
function injectData(
win: BrowserWindow.WindowInstance,
data: { image: string; lines: OcrLine[]; isDark: boolean; logo?: string }
): void {
try {
if (!win || win.isDestroyed()) return
// 序列化为安全的 JSON,避免引号/换行破坏 JS 字符串
const payload = JSON.stringify(data)
const code = `window.__loadScreenOcrResult && window.__loadScreenOcrResult(${payload});`
win.webContents?.executeJavaScript(code)
} catch (_) {
/* ignore:兜底注入会再试 */
}
}
| function copyText(text: string) { | ||
| if (!text) return | ||
| try { | ||
| navigator.clipboard?.writeText(text) | ||
| } catch (_) { | ||
| /* 子窗口可能无 clipboard 权限,静默失败 */ | ||
| } | ||
| copied.value = text | ||
| if (copiedTimer) window.clearTimeout(copiedTimer) | ||
| copiedTimer = window.setTimeout(() => (copied.value = ''), 1200) | ||
| } |
There was a problem hiding this comment.
navigator.clipboard.writeText 返回的是一个 Promise。由于它是异步执行的,外部的 try-catch 块无法捕获其异步的 Promise Rejection(例如当文档未聚焦或权限被拒绝时,会抛出 DOMException: Document is not focused)。这会导致控制台出现未捕获的 Promise 拒绝错误。建议显式使用 .catch() 来处理,并先判断 navigator.clipboard 是否存在。
function copyText(text: string) {
if (!text) return
if (navigator.clipboard) {
navigator.clipboard.writeText(text).catch(() => {
/* 子窗口可能无 clipboard 权限,静默失败 */
})
}
copied.value = text
if (copiedTimer) window.clearTimeout(copiedTimer)
copiedTimer = window.setTimeout(() => (copied.value = ''), 1200)
}
| function computeFitScale(): number { | ||
| const vp = viewportRef.value | ||
| const vw = vp ? vp.clientWidth : window.innerWidth | ||
| const vh = vp ? vp.clientHeight : window.innerHeight | ||
| if (!naturalWidth.value || !naturalHeight.value || !vw || !vh) return 1 | ||
| // 留 16px 内边距 | ||
| const aw = vw - 16 | ||
| const ah = vh - 16 | ||
| return Math.min(aw / naturalWidth.value, ah / naturalHeight.value, 1) | ||
| } |
There was a problem hiding this comment.
在 computeFitScale 中,如果用户将窗口缩放到极小(宽度或高度小于 16px),aw 或 ah 可能会计算为负数,从而导致返回一个负的缩放比例(scale),这会引起图片渲染异常或翻转。建议使用 Math.max(1, ...) 确保可用宽高始终为正数。
function computeFitScale(): number {
const vp = viewportRef.value
const vw = vp ? vp.clientWidth : window.innerWidth
const vh = vp ? vp.clientHeight : window.innerHeight
if (!naturalWidth.value || !naturalHeight.value || !vw || !vh) return 1
// 留 16px 内边距,确保不为负数
const aw = Math.max(1, vw - 16)
const ah = Math.max(1, vh - 16)
return Math.min(aw / naturalWidth.value, ah / naturalHeight.value, 1)
}
插件信息
本次变更
截图 / 演示
自检清单
plugins/f-provider/目录此 PR 由 ztools-plugin-cli 自动管理:每次
ztools publish在分支上追加一个 commit,PR 链接保持不变。