167 lines
6.0 KiB
Swift
167 lines
6.0 KiB
Swift
import Foundation
|
|
import MacToolsPluginKit
|
|
|
|
struct TranslatorPanelPresentation: Equatable {
|
|
struct Tip: Equatable {
|
|
var title: String
|
|
var message: String
|
|
var actionTitle: String
|
|
var systemImage: String
|
|
}
|
|
|
|
struct ProviderRow: Equatable, Identifiable {
|
|
var id: String
|
|
var title: String
|
|
var symbolName: String
|
|
var bodyText: String
|
|
var isExpanded: Bool
|
|
var isLoading: Bool
|
|
var isError: Bool
|
|
var canCopy: Bool
|
|
}
|
|
|
|
var sourceText: String
|
|
var usesSourceCaretPlaceholder: Bool
|
|
var sourceLanguageTitle: String
|
|
var targetLanguageTitle: String
|
|
var tip: Tip?
|
|
var providerRows: [ProviderRow]
|
|
|
|
init(
|
|
snapshot: TranslatorPanelSnapshot,
|
|
localization: PluginLocalization = PluginLocalization(bundle: .main)
|
|
) {
|
|
let trimmedSource = snapshot.sourceText?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
sourceText = trimmedSource
|
|
usesSourceCaretPlaceholder = trimmedSource.isEmpty
|
|
sourceLanguageTitle = snapshot.languageSelection?.sourceDisplayName(localization: localization)
|
|
?? localization.string("language.automatic", defaultValue: "自动检测")
|
|
targetLanguageTitle = snapshot.languageSelection?.target.displayName(localization: localization)
|
|
?? localization.string("language.automaticTarget", defaultValue: "自动选择")
|
|
tip = Self.tip(for: snapshot, localization: localization)
|
|
|
|
let results = Self.providerResults(for: snapshot, localization: localization)
|
|
let expandedProviderID = Self.expandedProviderID(in: results)
|
|
providerRows = results.map { result in
|
|
let text = Self.providerBodyText(result, localization: localization)
|
|
let translatedText = result.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
return ProviderRow(
|
|
id: result.id,
|
|
title: result.providerTitle,
|
|
symbolName: Self.symbolName(forProviderTitle: result.providerTitle),
|
|
bodyText: text,
|
|
isExpanded: result.id == expandedProviderID,
|
|
isLoading: result.phase == .translating || result.phase == .waiting,
|
|
isError: result.phase == .error,
|
|
canCopy: !translatedText.isEmpty
|
|
)
|
|
}
|
|
}
|
|
|
|
private static func providerResults(
|
|
for snapshot: TranslatorPanelSnapshot,
|
|
localization: PluginLocalization
|
|
) -> [TranslatorProviderResult] {
|
|
if !snapshot.providerResults.isEmpty {
|
|
return snapshot.providerResults
|
|
}
|
|
|
|
let providerTitle = snapshot.translation?.providerTitle
|
|
?? localization.string("openAIClient.providerTitle", defaultValue: "OpenAI 翻译")
|
|
return [
|
|
TranslatorProviderResult(
|
|
id: "placeholder",
|
|
providerTitle: providerTitle,
|
|
phase: placeholderProviderPhase(for: snapshot.phase),
|
|
translation: snapshot.translation,
|
|
errorMessage: snapshot.errorMessage
|
|
),
|
|
]
|
|
}
|
|
|
|
private static func placeholderProviderPhase(for phase: TranslatorPanelPhase) -> TranslatorProviderResultPhase {
|
|
switch phase {
|
|
case .translating:
|
|
return .translating
|
|
case .success:
|
|
return .success
|
|
case .error:
|
|
return .error
|
|
case .idle, .capturing:
|
|
return .waiting
|
|
}
|
|
}
|
|
|
|
private static func expandedProviderID(in results: [TranslatorProviderResult]) -> String? {
|
|
results.first { result in
|
|
let text = result.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
return !text.isEmpty || result.phase == .error
|
|
}?.id
|
|
}
|
|
|
|
private static func tip(
|
|
for snapshot: TranslatorPanelSnapshot,
|
|
localization: PluginLocalization
|
|
) -> Tip? {
|
|
guard case let .error(error) = snapshot.phase else {
|
|
return nil
|
|
}
|
|
|
|
let title = localization.string("panel.tip.title", defaultValue: "提示")
|
|
let actionTitle = localization.string("panel.tip.actionTitle", defaultValue: "如何解决")
|
|
let message: String
|
|
switch error {
|
|
case .missingSelection:
|
|
message = localization.string(
|
|
"panel.tip.missingSelection",
|
|
defaultValue: "划词翻译没有获取到文本"
|
|
)
|
|
default:
|
|
message = snapshot.errorMessage ?? error.message(localization: localization)
|
|
}
|
|
|
|
return Tip(
|
|
title: title,
|
|
message: message,
|
|
actionTitle: actionTitle,
|
|
systemImage: "lightbulb"
|
|
)
|
|
}
|
|
|
|
private static func providerBodyText(
|
|
_ result: TranslatorProviderResult,
|
|
localization: PluginLocalization
|
|
) -> String {
|
|
let text = result.text?.trimmingCharacters(in: .whitespacesAndNewlines) ?? ""
|
|
if !text.isEmpty {
|
|
return text
|
|
}
|
|
|
|
switch result.phase {
|
|
case .translating:
|
|
return localization.string("panel.resultPlaceholder.translating", defaultValue: "正在翻译...")
|
|
case .error:
|
|
return result.errorMessage
|
|
?? localization.string("openAIClient.error.requestFailed", defaultValue: "请求失败,请稍后重试")
|
|
case .waiting:
|
|
return localization.string("panel.resultPlaceholder.idle", defaultValue: "等待翻译")
|
|
case .success:
|
|
return localization.string("panel.resultPlaceholder.emptyResponse", defaultValue: "响应为空")
|
|
}
|
|
}
|
|
|
|
private static func symbolName(forProviderTitle title: String) -> String {
|
|
let normalized = title.lowercased()
|
|
if normalized.contains("deepseek") {
|
|
return "sparkle.magnifyingglass"
|
|
}
|
|
if normalized.contains("fireworks") {
|
|
return "sparkles"
|
|
}
|
|
if normalized.contains("openai") {
|
|
return "circle.hexagongrid"
|
|
}
|
|
return "bubble.left.and.text.bubble.right"
|
|
}
|
|
}
|