Files
openclaw/apps/macos/Sources/OpenClaw/QuickChatModelMenu.swift
Peter Steinberger 45f6543c9a feat(macos): Quick Chat power features — voice dictation, paste-to-app, model/reasoning control (#110994)
* feat(macos): add Quick Chat power features

* docs(macos): document Quick Chat power features

* chore(i18n): refresh native locale artifacts for Quick Chat power features

* fix(macos): update DEBUG test helper sendProvider to the reasoning-threaded arity
2026-07-18 17:21:03 -07:00

110 lines
4.1 KiB
Swift

import AppKit
import OpenClawChatUI
@MainActor
private final class QuickChatModelMenuTarget: NSObject {
let onSelectModel: (String) -> Void
let onSelectThinking: (String?) -> Void
init(
onSelectModel: @escaping (String) -> Void,
onSelectThinking: @escaping (String?) -> Void)
{
self.onSelectModel = onSelectModel
self.onSelectThinking = onSelectThinking
}
@objc func selectModel(_ sender: NSMenuItem) {
guard let selectionID = sender.representedObject as? String else { return }
self.onSelectModel(selectionID)
}
@objc func selectThinking(_ sender: NSMenuItem) {
guard let level = sender.representedObject as? String else { return }
self.onSelectThinking(level)
}
@objc func selectAutomaticThinking(_: NSMenuItem) {
self.onSelectThinking(nil)
}
}
@MainActor
enum QuickChatModelMenuPresenter {
static func present(model: QuickChatModel, panel: NSPanel, contentView: NSView) {
let target = QuickChatModelMenuTarget(
onSelectModel: { [weak model] selectionID in
model?.selectModel(selectionID)
},
onSelectThinking: { [weak model] level in
model?.selectThinkingLevel(level)
})
let menu = NSMenu()
let modelHeader = NSMenuItem(
title: String(localized: "Model"),
action: nil,
keyEquivalent: "")
modelHeader.isEnabled = false
menu.addItem(modelHeader)
let defaultItem = NSMenuItem(
title: String(localized: "Session default"),
action: #selector(QuickChatModelMenuTarget.selectModel(_:)),
keyEquivalent: "")
defaultItem.target = target
defaultItem.representedObject = OpenClawChatViewModel.defaultModelSelectionID
defaultItem.state = model.selectedModelSelectionID == OpenClawChatViewModel.defaultModelSelectionID
? .on
: .off
menu.addItem(defaultItem)
for section in model.modelPickerSections.providers {
let providerItem = NSMenuItem(title: section.displayName, action: nil, keyEquivalent: "")
let submenu = NSMenu()
for choice in section.models {
let item = NSMenuItem(
title: choice.name,
action: #selector(QuickChatModelMenuTarget.selectModel(_:)),
keyEquivalent: "")
item.target = target
item.representedObject = choice.selectionID
item.state = model.displayedModelSelectionID == choice.selectionID ? .on : .off
submenu.addItem(item)
}
providerItem.submenu = submenu
menu.addItem(providerItem)
}
menu.addItem(.separator())
let reasoningHeader = NSMenuItem(
title: String(localized: "Reasoning"),
action: nil,
keyEquivalent: "")
reasoningHeader.isEnabled = false
menu.addItem(reasoningHeader)
let automaticItem = NSMenuItem(
title: String(localized: "Auto"),
action: #selector(QuickChatModelMenuTarget.selectAutomaticThinking(_:)),
keyEquivalent: "")
automaticItem.target = target
automaticItem.state = model.selectedThinkingLevel == nil ? .on : .off
menu.addItem(automaticItem)
for option in model.thinkingOptions {
let item = NSMenuItem(
title: option.label,
action: #selector(QuickChatModelMenuTarget.selectThinking(_:)),
keyEquivalent: "")
item.target = target
item.representedObject = option.id
item.state = model.selectedThinkingLevel == option.id ? .on : .off
menu.addItem(item)
}
let windowPoint = panel.convertPoint(fromScreen: NSEvent.mouseLocation)
let contentPoint = contentView.convert(windowPoint, from: nil)
withExtendedLifetime(target) {
_ = menu.popUp(positioning: nil, at: contentPoint, in: contentView)
}
}
}