Skip to content

refactor: 清理预览弹窗翻译功能并优化设置界面#10

Merged
hubo1989 merged 1 commit intomainfrom
fixui-color-an-so-on
Feb 10, 2026
Merged

refactor: 清理预览弹窗翻译功能并优化设置界面#10
hubo1989 merged 1 commit intomainfrom
fixui-color-an-so-on

Conversation

@hubo1989
Copy link
Owner

@hubo1989 hubo1989 commented Feb 10, 2026

Summary

本次 PR 对 ScreenTranslate 进行了一系列功能清理和界面优化:

功能变更

  • 移除预览弹窗翻译功能:截图标注弹窗不再提供翻译相关操作,专注于截图和标注功能
  • 清理废弃设置项:移除了设置界面中不再使用的 OCR 引擎、翻译引擎、翻译模式配置

界面优化

  • 设置界面颜色调整:使用 macOS 26 标准颜色(windowBackgroundColorcontrolBackgroundColor),移除渐变效果
  • 菜单栏图标:为所有菜单项添加了 SF Symbols 图标,提升视觉一致性

功能增强

  • VLM API 测试:在 VLM 配置界面添加连通性测试按钮,支持 OpenAI、Claude、Ollama
  • 快捷键修复:修复 Command+Shift+A 无法设置为区域截图快捷键的问题(A 键虚拟键码为 0x00)

Bug 修复

  • 修复 ImmersiveTranslationView 中的 "Modifying state during view update" 警告

测试建议

  1. 验证设置界面显示正常,颜色符合 macOS 26 规范
  2. 验证菜单栏菜单项都显示了对应图标
  3. 验证 VLM API 测试功能工作正常
  4. 验证 Command+Shift+A 可以正常设置为快捷键
  5. 验证预览弹窗只显示 OCR 按钮,没有翻译相关按钮

🤖 Generated with Claude Code

Summary by CodeRabbit

发行说明

  • 新功能

    • 在设置中添加VLM API连接测试功能,支持多个AI提供商的连接验证
  • 改进

    • 增强OCR文本提取,改善长文本响应处理
    • 菜单项现已显示图标
    • 优化设置界面和应用布局
    • 改进错误处理和诊断信息
  • 移除

    • 移除翻译结果显示覆盖层
    • 移除翻译相关的保存和复制功能

- 移除预览弹窗中的翻译按钮及相关实现(显示/隐藏翻译覆盖层、保存/复制带译文图片)
- 简化 PreviewViewModel,移除翻译相关状态和方法
- 更新设置界面颜色以符合 macOS 26 规范
- 移除设置中废弃的 OCR 引擎/翻译引擎/翻译模式配置
- 添加 VLM API 连通性测试功能
- 修复 Command+Shift+A 快捷键无法设置的问题(A 键虚拟键码为 0)
- 为菜单栏菜单项添加 SF Symbols 图标
- 修复 ImmersiveTranslationView 状态修改警告

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Feb 10, 2026

📝 Walkthrough

高层概述

该PR移除了翻译相关功能,包括翻译UI、导出和保存工作流,同时为VLM API连接测试添加了新功能。简化了预览界面,优化了菜单栏图标,并增强了OpenAI VLM提供商以支持多轮对话。

变更

分类 / 文件 摘要
双语结果和预览UI
ScreenTranslate/Features/BilingualResult/BilingualResultView.swift, ScreenTranslate/Features/Preview/PreviewActionButtons.swift, ScreenTranslate/Features/Preview/PreviewAnnotatedImageView.swift, ScreenTranslate/Features/Preview/PreviewContentView.swift, ScreenTranslate/Features/Preview/PreviewResultsPanel.swift, ScreenTranslate/Features/Preview/ImmersiveTranslationView.swift
移除翻译覆盖层和翻译结果UI,简化预览界面布局;将ImmersiveTranslationView从注释图像视图中删除;将PreviewResultsPanel的可见性条件从hasOCRResults或hasTranslationResults改为仅hasOCRResults;替换OCR和翻译按钮为单一OCR按钮。
预览ViewModel逻辑
ScreenTranslate/Features/Preview/PreviewViewModel.swift, ScreenTranslate/Features/Preview/PreviewViewModel+Export.swift, ScreenTranslate/Features/Preview/PreviewViewModel+OCR.swift
删除translation、isPerformingTranslation、hasTranslationResults等翻译相关状态属性;移除saveWithTranslations()、copyWithTranslations()和executeTranslation()等翻译导出/处理方法;修改calculateLayout()返回元组(blocks, CGFloat)而非仅blocks。
菜单栏与设置UI
ScreenTranslate/Features/MenuBar/MenuBarController.swift, ScreenTranslate/Features/Settings/EngineSettingsTab.swift, ScreenTranslate/Features/Settings/SettingsView.swift, ScreenTranslate/Features/Settings/SettingsWindowController.swift, ScreenTranslate/Features/Settings/SettingsViewModel.swift
为菜单项添加SF Symbol图标支持;移除OCREnginePicker和TranslationEnginePicker;添加VLM API连接测试功能(testVLMAPI方法及相关UI状态);简化SettingsView背景样式和窗口外观。
VLM服务与提示
ScreenTranslate/Services/OpenAIVLMProvider.swift, ScreenTranslate/Services/VLMProvider.swift
增强OpenAI VLM提供商以支持多轮对话和延续处理,包含JSON修复和部分内容解析;简化系统提示和规则,采用更紧凑的JSON输出格式。
翻译流和模型
ScreenTranslate/Features/TranslationFlow/TranslationFlowController.swift, ScreenTranslate/Models/KeyboardShortcut.swift, ScreenTranslate/Resources/DesignSystem.swift
增强错误日志和诊断信息;放松KeyboardShortcut有效性检查(仅需required modifiers);简化macOS 26背景设计系统实现。
本地化资源
ScreenTranslate/Resources/en.lproj/Localizable.strings, ScreenTranslate/Resources/zh-Hans.lproj/Localizable.strings
移除6个预览翻译相关的工具提示键;添加settings.vlm.test.button本地化键。

序列图

sequenceDiagram
    participant User as 用户
    participant UI as SettingsView
    participant VM as SettingsViewModel
    participant Provider as VLMProvider<br/>(OpenAI/Claude/<br/>Ollama)
    participant Result as UI State

    User->>UI: 点击"测试连接"按钮
    UI->>VM: testVLMAPI()
    Note over VM: isTestingVLM = true
    VM->>Provider: 执行特定提供商<br/>连接测试
    
    alt 连接成功
        Provider-->>VM: 成功响应
        VM->>Result: vlmTestSuccess = true<br/>vlmTestResult = "连接成功"
    else 认证失败
        Provider-->>VM: 401错误
        VM->>Result: vlmTestSuccess = false<br/>vlmTestResult = "认证失败"
    else 网络错误
        Provider-->>VM: 网络超时
        VM->>Result: vlmTestSuccess = false<br/>vlmTestResult = "网络错误"
    end
    
    Note over VM: isTestingVLM = false
    VM-->>UI: 更新UI
    UI->>User: 显示测试结果
Loading

估计代码审查工作量

🎯 4 (复杂) | ⏱️ ~60 分钟

可能相关的PR

诗文

🐰 翻译功能已消退,测试连接闪烁灯,
VLM对话多轮谈,提示词更精简。
菜单栏换新装,图标闪耀细琢磨,
简约之美入眼帘,码农欢声又笑声!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 标题准确概括了该 PR 的核心变更:从预览弹窗清理翻译功能和优化设置界面两个主要方面。标题简洁明了,反映了 PR 的主要目的。
Docstring Coverage ✅ Passed Docstring coverage is 85.71% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fixui-color-an-so-on

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@ScreenTranslate/Features/BilingualResult/BilingualResultView.swift`:
- Around line 10-28: 当前实现同时在 scaleEffect(viewModel.scale) 和 frame(width:
CGFloat(viewModel.imageWidth) * viewModel.scale, height:
CGFloat(viewModel.imageHeight) * viewModel.scale) 上应用缩放,导致等效缩放为
scale^2;请只保留一种缩放方式——例如保留 scaleEffect(viewModel.scale) 并把 frame 改回不乘 scale(使用
viewModel.imageWidth / imageHeight 原始尺寸),或相反地移除 scaleEffect 并只在 frame 上乘以
viewModel.scale;在 ScrollView/Image 上调整 frame/scaleEffect 以只应用一次缩放,保持
onScrollWheelZoom、zoomIn 和 zoomOut 不变以便继续处理交互。

In `@ScreenTranslate/Features/Preview/PreviewActionButtons.swift`:
- Around line 115-128: The OCR icon-only button (ocrButton) lacks an
accessibility label so screen readers announce the system image name; add a
localized accessibility label to the Button (or its label Image/ProgressView)
such as .accessibilityLabel(String(localized: "preview.accessibility.ocr")) so
the role is clear, keeping existing .disabled and .help behavior and ensuring
the label is used whether viewModel.isPerformingOCR shows ProgressView or
Image(systemName:).

In `@ScreenTranslate/Services/OpenAIVLMProvider.swift`:
- Around line 170-175: The current extractContentAndStatus(from:) implementation
prints the raw response (rawJSON) which can leak sensitive content; change this
to avoid logging full responses in production by replacing the unconditional
print with either a conditional debug-only log (e.g., wrap in `#if` DEBUG) or use
a centralized Logger that respects privacy levels and only logs a
redacted/trimmed preview (no more than a few characters or a hash) and include
context (e.g., "[OpenAI] response preview"). Update the
extractContentAndStatus(from:) function to remove the unconditional
print(rawJSON) and use the chosen debug/Logger approach so production builds
never emit raw response bodies.
- Around line 122-131: The parseVLMContent call is missing the isTruncated flag
so the repair path (attemptToRepairJSON) never runs; update parseVLMContent to
accept a second parameter (e.g., isTruncated: Bool) and change the call here
from parseVLMContent(content) to parseVLMContent(content, isTruncated:
isTruncated) (and update the parseVLMContent signature and any callers
accordingly) so that when isTruncated is true the function can invoke
attemptToRepairJSON and return the repaired result.
- Around line 159-163: The continuation user prompt appended via
OpenAIChatMessage in conversationMessages currently asks for a "JSON array"
which conflicts with the initial prompt's expected object shape
{"segments":[...]}; update the prompt content text to explicitly request the
same complete JSON object format (for example: "Return ONLY the complete JSON
object with remaining segments in the format: {\"segments\":[...]}") so the
model returns an object with a segments property instead of a bare array.
🧹 Nitpick comments (5)
ScreenTranslate/Resources/DesignSystem.swift (1)

72-79: 建议改用系统分隔线色以适配深色/高对比模式。

Color.black.opacity(0.05) 在深色或高对比模式下可能过暗或不可见。建议用 Color(.separatorColor)(或同类语义色)让系统自动适配。

♻️ 建议修改
             .overlay(
                 RoundedRectangle(cornerRadius: cornerRadius)
-                    .stroke(Color.black.opacity(0.05), lineWidth: 0.5)
+                    .stroke(Color(.separatorColor), lineWidth: 0.5)
             )
ScreenTranslate/Features/TranslationFlow/TranslationFlowController.swift (1)

294-301: 建议将 Provider/Model 标签本地化。

"Provider:" 和 "Model:" 字符串未使用本地化,与应用其他部分的国际化风格不一致。虽然这是调试辅助信息,但为保持一致性,建议使用 String(localized:)

♻️ 建议的修改
         case .analysisFailure:
             let settings = AppSettings.shared
-            errorDetails += "\n\nProvider: \(settings.vlmProvider.localizedName)"
-            errorDetails += "\nModel: \(settings.vlmModelName)"
+            errorDetails += "\n\n" + String(localized: "translationFlow.error.provider") + ": \(settings.vlmProvider.localizedName)"
+            errorDetails += "\n" + String(localized: "translationFlow.error.model") + ": \(settings.vlmModelName)"
ScreenTranslate/Features/MenuBar/MenuBarController.swift (1)

172-175: 建议为图标添加无障碍描述(可选优化)。

当前图标的 accessibilityDescription 设为 nil。虽然菜单项本身有标题可供屏幕阅读器使用,但为图标添加描述可以进一步提升无障碍体验。

♻️ 可选的无障碍优化
         if let imageName = imageName,
-           let image = NSImage(systemSymbolName: imageName, accessibilityDescription: nil) {
+           let image = NSImage(systemSymbolName: imageName, accessibilityDescription: comment) {
             item.image = image
         }

这样可以复用 comment 参数作为图标的无障碍描述。

ScreenTranslate/Features/Preview/ImmersiveTranslationView.swift (1)

36-39: 函数签名重构合理

元组返回模式清晰地封装了布局计算结果。建议考虑将底部间距 20 提取为命名常量以提高可读性。

private let bottomPadding: CGFloat = 20
// ...
let requiredHeight = max(imageHeight, maxYExtension + bottomPadding)

Also applies to: 92-94

ScreenTranslate/Features/Settings/SettingsViewModel.swift (1)

572-720: 配置变更后建议清空上次测试结果

当前测试结果会在配置变更后继续显示,可能造成“已验证但实际上配置已变”的误解。建议在配置变更时重置测试状态。

♻️ 可选改进示例
 var vlmProvider: VLMProviderType {
     get { settings.vlmProvider }
     set {
         settings.vlmProvider = newValue
+        resetVLMTestState()
         if vlmBaseURL.isEmpty || vlmBaseURL == settings.vlmProvider.defaultBaseURL {
             vlmBaseURL = newValue.defaultBaseURL
         }
         if vlmModelName.isEmpty || vlmModelName == settings.vlmProvider.defaultModelName {
             vlmModelName = newValue.defaultModelName
         }
     }
 }

 var vlmAPIKey: String {
     get { settings.vlmAPIKey }
-    set { settings.vlmAPIKey = newValue }
+    set { settings.vlmAPIKey = newValue; resetVLMTestState() }
 }

 var vlmBaseURL: String {
     get { settings.vlmBaseURL }
-    set { settings.vlmBaseURL = newValue }
+    set { settings.vlmBaseURL = newValue; resetVLMTestState() }
 }

 var vlmModelName: String {
     get { settings.vlmModelName }
-    set { settings.vlmModelName = newValue }
+    set { settings.vlmModelName = newValue; resetVLMTestState() }
 }

+private func resetVLMTestState() {
+    guard !isTestingVLM else { return }
+    vlmTestResult = nil
+    vlmTestSuccess = false
+}

Comment on lines +10 to +28
ScrollView([.horizontal, .vertical], showsIndicators: true) {
Image(decorative: viewModel.image, scale: 1.0)
.resizable()
.aspectRatio(contentMode: .fit)
.scaleEffect(viewModel.scale)
.frame(
width: CGFloat(viewModel.imageWidth) * viewModel.scale,
height: CGFloat(viewModel.imageHeight) * viewModel.scale
)
.onScrollWheelZoom { delta in
if delta > 0 {
viewModel.zoomIn()
} else {
viewModel.zoomOut()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(nsColor: .windowBackgroundColor))

if viewModel.isLoading {
loadingOverlay
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(nsColor: .windowBackgroundColor))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

避免缩放被应用两次导致缩放比例失真
Line 14 的 scaleEffect(viewModel.scale) 与 Line 15-18 的 frame 同时按 viewModel.scale 放大,等效缩放为 scale^2,会导致缩放百分比显示不准且缩放步进过快。建议保留一种缩放方式即可。

🔧 建议修正
                 Image(decorative: viewModel.image, scale: 1.0)
                     .resizable()
                     .aspectRatio(contentMode: .fit)
-                    .scaleEffect(viewModel.scale)
                     .frame(
                         width: CGFloat(viewModel.imageWidth) * viewModel.scale,
                         height: CGFloat(viewModel.imageHeight) * viewModel.scale
                     )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ScrollView([.horizontal, .vertical], showsIndicators: true) {
Image(decorative: viewModel.image, scale: 1.0)
.resizable()
.aspectRatio(contentMode: .fit)
.scaleEffect(viewModel.scale)
.frame(
width: CGFloat(viewModel.imageWidth) * viewModel.scale,
height: CGFloat(viewModel.imageHeight) * viewModel.scale
)
.onScrollWheelZoom { delta in
if delta > 0 {
viewModel.zoomIn()
} else {
viewModel.zoomOut()
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(nsColor: .windowBackgroundColor))
if viewModel.isLoading {
loadingOverlay
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(nsColor: .windowBackgroundColor))
ScrollView([.horizontal, .vertical], showsIndicators: true) {
Image(decorative: viewModel.image, scale: 1.0)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(
width: CGFloat(viewModel.imageWidth) * viewModel.scale,
height: CGFloat(viewModel.imageHeight) * viewModel.scale
)
.onScrollWheelZoom { delta in
if delta > 0 {
viewModel.zoomIn()
} else {
viewModel.zoomOut()
}
}
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(Color(nsColor: .windowBackgroundColor))
🤖 Prompt for AI Agents
In `@ScreenTranslate/Features/BilingualResult/BilingualResultView.swift` around
lines 10 - 28, 当前实现同时在 scaleEffect(viewModel.scale) 和 frame(width:
CGFloat(viewModel.imageWidth) * viewModel.scale, height:
CGFloat(viewModel.imageHeight) * viewModel.scale) 上应用缩放,导致等效缩放为
scale^2;请只保留一种缩放方式——例如保留 scaleEffect(viewModel.scale) 并把 frame 改回不乘 scale(使用
viewModel.imageWidth / imageHeight 原始尺寸),或相反地移除 scaleEffect 并只在 frame 上乘以
viewModel.scale;在 ScrollView/Image 上调整 frame/scaleEffect 以只应用一次缩放,保持
onScrollWheelZoom、zoomIn 和 zoomOut 不变以便继续处理交互。

Comment on lines +115 to +128
private var ocrButton: some View {
Button {
viewModel.performOCR()
} label: {
if viewModel.isPerformingOCR {
ProgressView()
.controlSize(.small)
.frame(width: 16, height: 16)
} else {
Image(systemName: "text.viewfinder")
}
.disabled(!viewModel.hasTranslationResults || viewModel.isCopyingWithTranslations)
.help(String(localized: "preview.tooltip.copy.with.translations"))
.accessibilityLabel(Text(
viewModel.isCopyingWithTranslations ? "Copying translated image" : "Copy image with translations"
))
}
.disabled(viewModel.isPerformingOCR)
.help(String(localized: "preview.tooltip.ocr"))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

为 OCR 按钮补充无障碍标签。
当前按钮仅有图标,屏幕阅读器会读出系统图标名,用户难以理解功能。

♿ 建议补充无障碍标签
     private var ocrButton: some View {
         Button {
             viewModel.performOCR()
         } label: {
             if viewModel.isPerformingOCR {
                 ProgressView()
                     .controlSize(.small)
                     .frame(width: 16, height: 16)
             } else {
                 Image(systemName: "text.viewfinder")
             }
         }
         .disabled(viewModel.isPerformingOCR)
         .help(String(localized: "preview.tooltip.ocr"))
+        .accessibilityLabel(Text("preview.tooltip.ocr"))
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private var ocrButton: some View {
Button {
viewModel.performOCR()
} label: {
if viewModel.isPerformingOCR {
ProgressView()
.controlSize(.small)
.frame(width: 16, height: 16)
} else {
Image(systemName: "text.viewfinder")
}
.disabled(!viewModel.hasTranslationResults || viewModel.isCopyingWithTranslations)
.help(String(localized: "preview.tooltip.copy.with.translations"))
.accessibilityLabel(Text(
viewModel.isCopyingWithTranslations ? "Copying translated image" : "Copy image with translations"
))
}
.disabled(viewModel.isPerformingOCR)
.help(String(localized: "preview.tooltip.ocr"))
private var ocrButton: some View {
Button {
viewModel.performOCR()
} label: {
if viewModel.isPerformingOCR {
ProgressView()
.controlSize(.small)
.frame(width: 16, height: 16)
} else {
Image(systemName: "text.viewfinder")
}
}
.disabled(viewModel.isPerformingOCR)
.help(String(localized: "preview.tooltip.ocr"))
.accessibilityLabel(String(localized: "preview.tooltip.ocr"))
}
🤖 Prompt for AI Agents
In `@ScreenTranslate/Features/Preview/PreviewActionButtons.swift` around lines 115
- 128, The OCR icon-only button (ocrButton) lacks an accessibility label so
screen readers announce the system image name; add a localized accessibility
label to the Button (or its label Image/ProgressView) such as
.accessibilityLabel(String(localized: "preview.accessibility.ocr")) so the role
is clear, keeping existing .disabled and .help behavior and ensuring the label
is used whether viewModel.isPerformingOCR shows ProgressView or
Image(systemName:).

Comment on lines +122 to +131
// Try to parse this response
do {
let response = try parseVLMContent(content)
allSegments.append(contentsOf: response.segments)
print("[OpenAI] Parsed \(response.segments.count) segments from this response")

if !isTruncated {
// Complete - return merged result
print("[OpenAI] Complete response received, total \(allSegments.count) segments")
return VLMAnalysisResponse(segments: allSegments)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

截断响应未传入 wasTruncated,修复路径不会生效。
此处未将 isTruncated 传入解析函数,导致 attemptToRepairJSON 分支无法触发。

🔧 建议传入 isTruncated
-                let response = try parseVLMContent(content)
+                let response = try parseVLMContent(content, wasTruncated: isTruncated)
🤖 Prompt for AI Agents
In `@ScreenTranslate/Services/OpenAIVLMProvider.swift` around lines 122 - 131, The
parseVLMContent call is missing the isTruncated flag so the repair path
(attemptToRepairJSON) never runs; update parseVLMContent to accept a second
parameter (e.g., isTruncated: Bool) and change the call here from
parseVLMContent(content) to parseVLMContent(content, isTruncated: isTruncated)
(and update the parseVLMContent signature and any callers accordingly) so that
when isTruncated is true the function can invoke attemptToRepairJSON and return
the repaired result.

Comment on lines +159 to +163
// Request continuation - ask for complete output this time
conversationMessages.append(OpenAIChatMessage(
role: "user",
content: .text("Continue from where you left off. Return ONLY the complete JSON array of remaining segments. Do not repeat segments already returned.")
))
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's find and examine the VLMAnalysisResponse structure
rg -n "VLMAnalysisResponse" --type=swift -B 2 -A 10

Repository: hubo1989/ScreenTranslate

Length of output: 24889


🏁 Script executed:

# Check for VLMPromptTemplate references
rg -n "VLMPromptTemplate" --type=swift -B 2 -A 5

Repository: hubo1989/ScreenTranslate

Length of output: 4043


🏁 Script executed:

# Examine the context around lines 159-163 in OpenAIVLMProvider.swift
fd "OpenAIVLMProvider.swift" --type=f

Repository: hubo1989/ScreenTranslate

Length of output: 116


🏁 Script executed:

# Get the VLMPromptTemplate.userPrompt definition
rg -n "userPrompt" ScreenTranslate/Services/VLMProvider.swift -A 30

Repository: hubo1989/ScreenTranslate

Length of output: 1546


🏁 Script executed:

# Get the exact context around lines 159-163 in OpenAIVLMProvider.swift
sed -n '155,170p' ScreenTranslate/Services/OpenAIVLMProvider.swift

Repository: hubo1989/ScreenTranslate

Length of output: 764


续写提示与初始提示返回格式不一致,会导致解析失败。

初始提示(第118行)正确要求完整JSON对象格式:{"segments":[...]}。但续写提示(第159-163行)错误地要求返回"JSON array",会导致模型返回纯数组格式[...],而解析器期望的是具有segments属性的对象结构。修正续写提示文本,让它要求与初始提示相同的格式,例如:"Return ONLY the complete JSON object with remaining segments in the format: {"segments":[...]}"

🤖 Prompt for AI Agents
In `@ScreenTranslate/Services/OpenAIVLMProvider.swift` around lines 159 - 163, The
continuation user prompt appended via OpenAIChatMessage in conversationMessages
currently asks for a "JSON array" which conflicts with the initial prompt's
expected object shape {"segments":[...]}; update the prompt content text to
explicitly request the same complete JSON object format (for example: "Return
ONLY the complete JSON object with remaining segments in the format:
{\"segments\":[...]}") so the model returns an object with a segments property
instead of a bare array.

Comment on lines +170 to +175
/// Extracts content text and truncation status from OpenAI response
private func extractContentAndStatus(from data: Data) throws -> (content: String, isTruncated: Bool, finishReason: String?) {
// Log raw response first for debugging
if let rawJSON = String(data: data, encoding: .utf8) {
print("[OpenAI] Raw response (\(data.count) bytes): \(rawJSON.prefix(500))...")
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

避免在生产日志中输出完整响应内容。
当前直接打印原始响应,可能泄露用户图像内容/识别文本,属于隐私风险。建议仅在 DEBUG 或通过隐私等级日志输出。

🛡️ 建议仅在 DEBUG 输出或改用 Logger
-        if let rawJSON = String(data: data, encoding: .utf8) {
-            print("[OpenAI] Raw response (\(data.count) bytes): \(rawJSON.prefix(500))...")
-        }
+        `#if` DEBUG
+        if let rawJSON = String(data: data, encoding: .utf8) {
+            print("[OpenAI] Raw response (\(data.count) bytes): \(rawJSON.prefix(500))...")
+        }
+        `#endif`
🤖 Prompt for AI Agents
In `@ScreenTranslate/Services/OpenAIVLMProvider.swift` around lines 170 - 175, The
current extractContentAndStatus(from:) implementation prints the raw response
(rawJSON) which can leak sensitive content; change this to avoid logging full
responses in production by replacing the unconditional print with either a
conditional debug-only log (e.g., wrap in `#if` DEBUG) or use a centralized Logger
that respects privacy levels and only logs a redacted/trimmed preview (no more
than a few characters or a hash) and include context (e.g., "[OpenAI] response
preview"). Update the extractContentAndStatus(from:) function to remove the
unconditional print(rawJSON) and use the chosen debug/Logger approach so
production builds never emit raw response bodies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant