## Agent 核心功能 - **流式输出刘海弹窗**:实时展示模型思考内容,支持自动滚动 - **工具调用进度**:最多可见3条 + 可滚动查看全部历史 + 防抖并行滚动 - **打字/语音双输入**:Agent 模式可选手动打字或语音输入 - **快速/思考模式切换**:支持 thinking mode 开关 - **多轮对话连续性**:同一会话内持续对话,新建对话按钮 ⊕ 开启新会话 - **工作时间显示**:工作完成状态栏显示耗时,如「工作完成 · 00:42」 ## Bug 修复 - **SIGTERM 优雅退出**:bridge.js 处理 SIGTERM,避免 code 15 误报异常 - **弹窗复用不重建**:输入内容继续对话时弹窗内部刷新,不再闪出闪进 - **关闭按钮立刻生效**:ignoreMouse=true 绕过 DynamicNotchKit 延迟 - **设置窗口 Agent UI 构建时机**:从延迟构建改为 init 中直接构建 - **模型编辑器**:修复 grid column nil 崩溃 + 改为独立 KeyEquivalentPanel 窗口支持 Cmd+A/C/V/X - **双击 Control 三向轮换**:语音输入 ↔ AI助手 ↔ 智能体 - **Fn 长按冲突处理**:非 Agent 模式下自动关闭 Agent 刘海弹窗 - **Save 后刷新下拉**:保存配置后默认模型下拉框即时同步 ## UI 交互优化 - **工具列表无灰色背景**:工具执行期间不再有底色变化 - **工作时隐藏新建对话按钮**:thinking/running 期间 ⊕ 自动隐藏 - **工具调用去 timeout 显示**:运行指令行不再显示 (timeout=30s) ## 文件拆分(单文件≤500行) - AgentNotchView → AgentProgressTracker + AppKitTextField + AgentNotchView - SettingsWindow → AgentModelEditor + KeyEquivalentSecureTextField + KeyEquivalentPanel + SettingsWindow ## 底层改动 - DynamicNotchKit: 新增 contentVersion + notifyContentChanged 机制 - NotchView: 动画绑定 contentVersion 支持弹窗内容变化弹性动画
85 lines
2.8 KiB
Swift
85 lines
2.8 KiB
Swift
import SwiftUI
|
||
import AppKit
|
||
|
||
// MARK: - AppKit NSTextField 桥接(支持 Cmd+A/C/V/X)
|
||
|
||
struct AppKitTextField: NSViewRepresentable {
|
||
@Binding var text: String
|
||
var placeholder: String
|
||
var isEditable: Bool
|
||
var onSubmit: () -> Void
|
||
|
||
func makeNSView(context: Context) -> KeyEquivalentTextField {
|
||
let textField = KeyEquivalentTextField()
|
||
textField.isBordered = false
|
||
textField.isBezeled = false
|
||
textField.drawsBackground = false
|
||
textField.focusRingType = .none
|
||
textField.font = .systemFont(ofSize: 12)
|
||
textField.textColor = .white
|
||
textField.placeholderString = placeholder
|
||
textField.isEditable = isEditable
|
||
textField.delegate = context.coordinator
|
||
textField.cell?.wraps = false
|
||
textField.cell?.isScrollable = true
|
||
textField.lineBreakMode = .byClipping
|
||
return textField
|
||
}
|
||
|
||
func updateNSView(_ nsView: KeyEquivalentTextField, context: Context) {
|
||
if nsView.stringValue != text {
|
||
nsView.stringValue = text
|
||
}
|
||
nsView.placeholderString = placeholder
|
||
nsView.isEditable = isEditable
|
||
}
|
||
|
||
func makeCoordinator() -> Coordinator {
|
||
Coordinator(self)
|
||
}
|
||
|
||
class Coordinator: NSObject, NSTextFieldDelegate {
|
||
let parent: AppKitTextField
|
||
|
||
init(_ parent: AppKitTextField) {
|
||
self.parent = parent
|
||
}
|
||
|
||
func controlTextDidChange(_ obj: Notification) {
|
||
guard let textField = obj.object as? NSTextField else { return }
|
||
parent.text = textField.stringValue
|
||
}
|
||
|
||
func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
|
||
if commandSelector == #selector(NSResponder.insertNewline(_:)) {
|
||
parent.onSubmit()
|
||
return true
|
||
}
|
||
return false
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - Cmd+A/C/V/X 支持
|
||
|
||
final class KeyEquivalentTextField: NSTextField {
|
||
override func performKeyEquivalent(with event: NSEvent) -> Bool {
|
||
guard event.modifierFlags.contains(.command),
|
||
let chars = event.charactersIgnoringModifiers?.lowercased() else {
|
||
return super.performKeyEquivalent(with: event)
|
||
}
|
||
switch chars {
|
||
case "a":
|
||
return NSApp.sendAction(#selector(NSText.selectAll(_:)), to: currentEditor() ?? self, from: self)
|
||
case "c":
|
||
return NSApp.sendAction(#selector(NSText.copy(_:)), to: currentEditor() ?? self, from: self)
|
||
case "v":
|
||
return NSApp.sendAction(#selector(NSText.paste(_:)), to: currentEditor() ?? self, from: self)
|
||
case "x":
|
||
return NSApp.sendAction(#selector(NSText.cut(_:)), to: currentEditor() ?? self, from: self)
|
||
default:
|
||
return super.performKeyEquivalent(with: event)
|
||
}
|
||
}
|
||
}
|