import Cocoa // MARK: - Agent 模型编辑弹窗 /// 显示添加/编辑模型的独立窗口,通过回调返回结果 final class AgentModelEditor { static func show(for model: AgentModelConfig?, onSave: @escaping (AgentModelConfig) -> Void) { let isEdit = model != nil let panel = KeyEquivalentPanel( contentRect: NSRect(x: 0, y: 0, width: 420, height: 340), styleMask: [.titled, .closable], backing: .buffered, defer: false ) panel.title = isEdit ? "编辑模型" : "添加模型" panel.isReleasedWhenClosed = false panel.center() guard let content = panel.contentView else { return } let nameField = KeyEquivalentTextField(frame: .zero) nameField.placeholderString = "kimi-k2.5" nameField.stringValue = model?.name ?? "" let urlField = KeyEquivalentTextField(frame: .zero) urlField.placeholderString = "https://api.example.com/v1" urlField.stringValue = model?.url ?? "" let keyField = KeyEquivalentSecureTextField(frame: .zero) keyField.placeholderString = "sk-..." keyField.stringValue = model?.apikey ?? "" let modesField = KeyEquivalentTextField(frame: .zero) modesField.placeholderString = "快速,思考" modesField.stringValue = model?.modes ?? "快速,思考" let multimodalField = KeyEquivalentTextField(frame: .zero) multimodalField.placeholderString = "图片,视频(可留空)" multimodalField.stringValue = model?.multimodal ?? "" let maxOutputField = KeyEquivalentTextField(frame: .zero) maxOutputField.placeholderString = "32000" maxOutputField.stringValue = model.map { String($0.maxOutput) } ?? "32000" let maxContextField = KeyEquivalentTextField(frame: .zero) maxContextField.placeholderString = "256000" maxContextField.stringValue = model.map { String($0.maxContext) } ?? "256000" let fields: [(String, NSTextField)] = [ ("模型名称:", nameField), ("API URL:", urlField), ("API Key:", keyField), ("模式:", modesField), ("多模态:", multimodalField), ("最大输出:", maxOutputField), ("最大上下文:", maxContextField), ] let grid = NSGridView(frame: .zero) grid.translatesAutoresizingMaskIntoConstraints = false grid.columnSpacing = 8 grid.rowSpacing = 10 for (labelText, field) in fields { let label = makeLabel(labelText) field.font = .systemFont(ofSize: 13) field.bezelStyle = .roundedBezel field.translatesAutoresizingMaskIntoConstraints = false grid.addRow(with: [label, field]) } grid.column(at: 0).xPlacement = .trailing grid.column(at: 1).width = 260 let saveBtn = NSButton(title: isEdit ? "保存" : "添加", target: nil, action: nil) saveBtn.bezelStyle = .rounded saveBtn.translatesAutoresizingMaskIntoConstraints = false saveBtn.keyEquivalent = "\r" let cancelBtn = NSButton(title: "取消", target: nil, action: nil) cancelBtn.bezelStyle = .rounded cancelBtn.translatesAutoresizingMaskIntoConstraints = false let buttonStack = NSStackView(views: [cancelBtn, saveBtn]) buttonStack.translatesAutoresizingMaskIntoConstraints = false buttonStack.orientation = .horizontal buttonStack.spacing = 12 buttonStack.distribution = .fillEqually content.addSubview(grid) content.addSubview(buttonStack) NSLayoutConstraint.activate([ grid.topAnchor.constraint(equalTo: content.topAnchor, constant: 20), grid.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 20), grid.trailingAnchor.constraint(equalTo: content.trailingAnchor, constant: -20), buttonStack.topAnchor.constraint(equalTo: grid.bottomAnchor, constant: 16), buttonStack.centerXAnchor.constraint(equalTo: content.centerXAnchor), buttonStack.widthAnchor.constraint(equalToConstant: 200) ]) let helper = ModelEditorHelper( panel: panel, nameField: nameField, urlField: urlField, keyField: keyField, modesField: modesField, multimodalField: multimodalField, maxOutputField: maxOutputField, maxContextField: maxContextField, isEdit: isEdit, onSave: onSave ) saveBtn.target = helper saveBtn.action = #selector(ModelEditorHelper.saveAction(_:)) cancelBtn.target = helper cancelBtn.action = #selector(ModelEditorHelper.cancelAction(_:)) objc_setAssociatedObject(saveBtn, "helper", helper, .OBJC_ASSOCIATION_RETAIN) objc_setAssociatedObject(cancelBtn, "helper", helper, .OBJC_ASSOCIATION_RETAIN) panel.makeKeyAndOrderFront(nil) } private static func makeLabel(_ text: String) -> NSTextField { let label = NSTextField(labelWithString: text) label.font = .systemFont(ofSize: 11, weight: .semibold) label.textColor = .secondaryLabelColor label.alignment = .right label.translatesAutoresizingMaskIntoConstraints = false return label } } // MARK: - Helper private class ModelEditorHelper: NSObject { weak var panel: KeyEquivalentPanel? let nameField: NSTextField let urlField: NSTextField let keyField: KeyEquivalentSecureTextField let modesField: NSTextField let multimodalField: NSTextField let maxOutputField: NSTextField let maxContextField: NSTextField let isEdit: Bool let onSave: (AgentModelConfig) -> Void init(panel: KeyEquivalentPanel, nameField: NSTextField, urlField: NSTextField, keyField: KeyEquivalentSecureTextField, modesField: NSTextField, multimodalField: NSTextField, maxOutputField: NSTextField, maxContextField: NSTextField, isEdit: Bool, onSave: @escaping (AgentModelConfig) -> Void) { self.panel = panel self.nameField = nameField self.urlField = urlField self.keyField = keyField self.modesField = modesField self.multimodalField = multimodalField self.maxOutputField = maxOutputField self.maxContextField = maxContextField self.isEdit = isEdit self.onSave = onSave } @objc func saveAction(_ sender: Any) { let name = nameField.stringValue.trimmingCharacters(in: .whitespaces) let url = urlField.stringValue.trimmingCharacters(in: .whitespaces) let key = keyField.stringValue.trimmingCharacters(in: .whitespaces) let modes = modesField.stringValue.trimmingCharacters(in: .whitespaces) let multimodal = multimodalField.stringValue.trimmingCharacters(in: .whitespaces) let maxOutput = Int(maxOutputField.stringValue) ?? 32000 let maxContext = Int(maxContextField.stringValue) ?? 256000 guard !name.isEmpty, !url.isEmpty, !key.isEmpty, !modes.isEmpty else { let alert = NSAlert() alert.messageText = "请填写模型名称、API URL、API Key 和模式" alert.alertStyle = .warning alert.addButton(withTitle: "确定") alert.runModal() return } let model = AgentModelConfig( url: url, name: name, apikey: key, modes: modes, multimodal: multimodal, maxOutput: maxOutput, maxContext: maxContext ) panel?.close() onSave(model) } @objc func cancelAction(_ sender: Any) { panel?.close() } }