Bläddra i källkod

Present AI Companion notes inside an in-app sheet.

Replace the separate notes window with a styled in-app popup that supports scrolling, copy, and close actions while preserving light/dark adaptive colors.

Co-authored-by: Cursor <cursoragent@cursor.com>
huzaifahayat12 1 månad sedan
förälder
incheckning
9359ce8401
1 ändrade filer med 120 tillägg och 21 borttagningar
  1. 120 21
      meetings_app/ViewController.swift

+ 120 - 21
meetings_app/ViewController.swift

@@ -334,8 +334,10 @@ final class ViewController: NSViewController {
334 334
     private var aiCompanionNotesMeetingIdByView = [ObjectIdentifier: String]()
335 335
     private var aiCompanionNotesStatusLabelByView = [ObjectIdentifier: NSTextField]()
336 336
     private var aiCompanionNotesCurrentRequestId: UUID?
337
-    private var aiCompanionNotesWindow: NSWindow?
337
+    private var aiCompanionNotesPanel: NSPanel?
338 338
     private weak var aiCompanionNotesTextView: NSTextView?
339
+    private weak var aiCompanionNotesCopyButton: NSButton?
340
+    private weak var aiCompanionNotesCloseButton: NSButton?
339 341
     private var aiCompanionNotesTaskByMeetingId = [String: Task<Void, Never>]()
340 342
     private var aiCompanionAudioPlayer: AVPlayer?
341 343
     private var aiCompanionLocalAudioPlayer: AVAudioPlayer?
@@ -3522,58 +3524,148 @@ private extension ViewController {
3522 3524
 
3523 3525
     @MainActor
3524 3526
     private func aiCompanionPresentNotesWindow(meetingTitle: String, initialText: String) {
3525
-        if let window = aiCompanionNotesWindow, let textView = aiCompanionNotesTextView {
3526
-            window.title = "Notes - \(meetingTitle)"
3527
+        guard let hostWindow = view.window else { return }
3528
+        let hostIsDarkMode = hostWindow.effectiveAppearance.bestMatch(from: [.darkAqua, .aqua]) == .darkAqua
3529
+        let panelAppearanceName: NSAppearance.Name = hostIsDarkMode ? .darkAqua : .aqua
3530
+        let outerBackgroundColor: NSColor = hostIsDarkMode ? .windowBackgroundColor : NSColor(calibratedWhite: 0.96, alpha: 1.0)
3531
+        let headerBackgroundColor: NSColor = hostIsDarkMode ? .controlBackgroundColor : NSColor(calibratedWhite: 0.93, alpha: 1.0)
3532
+
3533
+        if let panel = aiCompanionNotesPanel, let textView = aiCompanionNotesTextView {
3534
+            panel.title = "Notes - \(meetingTitle)"
3535
+            panel.appearance = NSAppearance(named: panelAppearanceName)
3536
+            panel.backgroundColor = outerBackgroundColor
3527 3537
             textView.string = initialText
3528
-            window.makeKeyAndOrderFront(nil)
3538
+            if hostWindow.attachedSheet !== panel {
3539
+                hostWindow.beginSheet(panel, completionHandler: nil)
3540
+            }
3541
+            panel.makeKeyAndOrderFront(nil)
3529 3542
             NSApp.activate(ignoringOtherApps: true)
3530 3543
             return
3531 3544
         }
3532 3545
 
3533
-        let windowWidth: CGFloat = 640
3534
-        let windowHeight: CGFloat = 560
3535
-        let window = NSWindow(
3536
-            contentRect: NSRect(x: 0, y: 0, width: windowWidth, height: windowHeight),
3537
-            styleMask: [.titled, .closable, .resizable],
3546
+        let panelWidth: CGFloat = 720
3547
+        let panelHeight: CGFloat = 540
3548
+        let panel = NSPanel(
3549
+            contentRect: NSRect(x: 0, y: 0, width: panelWidth, height: panelHeight),
3550
+            styleMask: [.titled, .closable],
3538 3551
             backing: .buffered,
3539 3552
             defer: false
3540 3553
         )
3541
-        window.isReleasedWhenClosed = false
3542
-        window.title = "Notes - \(meetingTitle)"
3543
-        window.center()
3554
+        panel.isReleasedWhenClosed = false
3555
+        panel.title = "Notes - \(meetingTitle)"
3556
+        panel.isFloatingPanel = false
3557
+        panel.hidesOnDeactivate = false
3558
+        panel.delegate = self
3559
+        panel.appearance = NSAppearance(named: panelAppearanceName)
3560
+        panel.backgroundColor = outerBackgroundColor
3561
+        panel.standardWindowButton(.zoomButton)?.isHidden = true
3562
+        panel.standardWindowButton(.miniaturizeButton)?.isHidden = true
3544 3563
 
3545 3564
         let root = NSView()
3546 3565
         root.translatesAutoresizingMaskIntoConstraints = false
3566
+        root.wantsLayer = true
3567
+        root.layer?.backgroundColor = outerBackgroundColor.cgColor
3568
+
3569
+        let header = NSView()
3570
+        header.translatesAutoresizingMaskIntoConstraints = false
3571
+        header.wantsLayer = true
3572
+        header.layer?.backgroundColor = headerBackgroundColor.cgColor
3573
+        header.layer?.cornerRadius = 10
3574
+
3575
+        let titleLabel = textLabel("Meeting Notes", font: NSFont.systemFont(ofSize: 15, weight: .semibold), color: .labelColor)
3576
+        titleLabel.translatesAutoresizingMaskIntoConstraints = false
3577
+        let subtitleLabel = textLabel(meetingTitle, font: NSFont.systemFont(ofSize: 12, weight: .regular), color: .secondaryLabelColor)
3578
+        subtitleLabel.translatesAutoresizingMaskIntoConstraints = false
3579
+        subtitleLabel.lineBreakMode = .byTruncatingTail
3547 3580
 
3548 3581
         let scroll = NSScrollView()
3549 3582
         scroll.translatesAutoresizingMaskIntoConstraints = false
3550
-        scroll.drawsBackground = false
3583
+        scroll.drawsBackground = true
3584
+        scroll.backgroundColor = NSColor.textBackgroundColor
3551 3585
         scroll.hasVerticalScroller = true
3586
+        scroll.hasHorizontalScroller = false
3587
+        scroll.borderType = .bezelBorder
3552 3588
 
3553 3589
         let textView = NSTextView()
3554 3590
         textView.isEditable = false
3555 3591
         textView.isSelectable = true
3556
-        textView.backgroundColor = .clear
3592
+        textView.backgroundColor = NSColor.textBackgroundColor
3593
+        textView.textColor = NSColor.labelColor
3557 3594
         textView.font = NSFont.systemFont(ofSize: 13, weight: .regular)
3558 3595
         textView.string = initialText
3559 3596
         textView.textContainer?.widthTracksTextView = true
3597
+        textView.textContainerInset = NSSize(width: 10, height: 10)
3598
+
3599
+        let copyButton = NSButton(title: "Copy", target: self, action: #selector(aiCompanionCopyNotesTapped(_:)))
3600
+        copyButton.translatesAutoresizingMaskIntoConstraints = false
3601
+        copyButton.bezelStyle = .rounded
3602
+
3603
+        let closeButton = NSButton(title: "Close", target: self, action: #selector(aiCompanionCloseNotesTapped(_:)))
3604
+        closeButton.translatesAutoresizingMaskIntoConstraints = false
3605
+        closeButton.bezelStyle = .rounded
3560 3606
 
3561 3607
         scroll.documentView = textView
3608
+        header.addSubview(titleLabel)
3609
+        header.addSubview(subtitleLabel)
3610
+        header.addSubview(copyButton)
3611
+        header.addSubview(closeButton)
3612
+        root.addSubview(header)
3562 3613
         root.addSubview(scroll)
3563 3614
 
3564 3615
         NSLayoutConstraint.activate([
3565
-            scroll.leadingAnchor.constraint(equalTo: root.leadingAnchor),
3566
-            scroll.trailingAnchor.constraint(equalTo: root.trailingAnchor),
3567
-            scroll.topAnchor.constraint(equalTo: root.topAnchor),
3568
-            scroll.bottomAnchor.constraint(equalTo: root.bottomAnchor)
3616
+            header.topAnchor.constraint(equalTo: root.topAnchor, constant: 12),
3617
+            header.leadingAnchor.constraint(equalTo: root.leadingAnchor, constant: 16),
3618
+            header.trailingAnchor.constraint(equalTo: root.trailingAnchor, constant: -16),
3619
+
3620
+            titleLabel.topAnchor.constraint(equalTo: header.topAnchor, constant: 12),
3621
+            titleLabel.leadingAnchor.constraint(equalTo: header.leadingAnchor, constant: 12),
3622
+            titleLabel.trailingAnchor.constraint(lessThanOrEqualTo: copyButton.leadingAnchor, constant: -12),
3623
+
3624
+            subtitleLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 4),
3625
+            subtitleLabel.leadingAnchor.constraint(equalTo: header.leadingAnchor, constant: 12),
3626
+            subtitleLabel.trailingAnchor.constraint(lessThanOrEqualTo: copyButton.leadingAnchor, constant: -12),
3627
+            subtitleLabel.bottomAnchor.constraint(equalTo: header.bottomAnchor, constant: -12),
3628
+
3629
+            copyButton.centerYAnchor.constraint(equalTo: header.centerYAnchor),
3630
+            copyButton.trailingAnchor.constraint(equalTo: closeButton.leadingAnchor, constant: -8),
3631
+
3632
+            closeButton.centerYAnchor.constraint(equalTo: header.centerYAnchor),
3633
+            closeButton.trailingAnchor.constraint(equalTo: header.trailingAnchor, constant: -12),
3634
+
3635
+            scroll.topAnchor.constraint(equalTo: header.bottomAnchor, constant: 12),
3636
+            scroll.leadingAnchor.constraint(equalTo: root.leadingAnchor, constant: 16),
3637
+            scroll.trailingAnchor.constraint(equalTo: root.trailingAnchor, constant: -16),
3638
+            scroll.bottomAnchor.constraint(equalTo: root.bottomAnchor, constant: -16)
3569 3639
         ])
3570 3640
 
3571
-        window.contentView = root
3572
-        window.makeKeyAndOrderFront(nil)
3641
+        panel.contentView = root
3642
+        hostWindow.beginSheet(panel, completionHandler: nil)
3643
+        panel.makeKeyAndOrderFront(nil)
3573 3644
         NSApp.activate(ignoringOtherApps: true)
3574 3645
 
3575
-        aiCompanionNotesWindow = window
3646
+        aiCompanionNotesPanel = panel
3576 3647
         aiCompanionNotesTextView = textView
3648
+        aiCompanionNotesCopyButton = copyButton
3649
+        aiCompanionNotesCloseButton = closeButton
3650
+    }
3651
+
3652
+    @objc private func aiCompanionCopyNotesTapped(_ sender: NSButton) {
3653
+        guard let notes = aiCompanionNotesTextView?.string, notes.isEmpty == false else { return }
3654
+        NSPasteboard.general.clearContents()
3655
+        NSPasteboard.general.setString(notes, forType: .string)
3656
+        sender.title = "Copied"
3657
+        DispatchQueue.main.asyncAfter(deadline: .now() + 1.2) { [weak self] in
3658
+            guard self?.aiCompanionNotesCopyButton === sender else { return }
3659
+            sender.title = "Copy"
3660
+        }
3661
+    }
3662
+
3663
+    @objc private func aiCompanionCloseNotesTapped(_ sender: NSButton) {
3664
+        guard let panel = aiCompanionNotesPanel else { return }
3665
+        if let hostWindow = view.window, hostWindow.attachedSheet === panel {
3666
+            hostWindow.endSheet(panel)
3667
+        }
3668
+        panel.orderOut(nil)
3577 3669
     }
3578 3670
 
3579 3671
     private func aiCompanionMeetMeetingCode(from meetURL: URL) -> String? {
@@ -6513,6 +6605,13 @@ extension ViewController: AVAudioPlayerDelegate {
6513 6605
 extension ViewController: NSWindowDelegate {
6514 6606
     func windowWillClose(_ notification: Notification) {
6515 6607
         guard let closingWindow = notification.object as? NSWindow else { return }
6608
+        if closingWindow === aiCompanionNotesPanel {
6609
+            aiCompanionNotesPanel = nil
6610
+            aiCompanionNotesTextView = nil
6611
+            aiCompanionNotesCopyButton = nil
6612
+            aiCompanionNotesCloseButton = nil
6613
+            return
6614
+        }
6516 6615
         if closingWindow === paywallWindow {
6517 6616
             paywallWindow = nil
6518 6617
             paywallUpgradeFlowEnabled = false