- 新增 AssistOutputMode 配置(inject/notch) - Assist 设置界面添加输出方式弹出菜单 - 新建 NotchDisplayManager:DynamicNotchKit 弹窗,支持滚动、复制、15s 自动消失 - DynamicNotchKit 固化为本地依赖,修复假刘海高度问题(0→内容贴近顶部)
97 lines
2.7 KiB
Swift
97 lines
2.7 KiB
Swift
import Cocoa
|
||
import SwiftUI
|
||
import DynamicNotchKit
|
||
|
||
/// 管理 AI 回复的刘海弹窗展示
|
||
final class NotchDisplayManager {
|
||
|
||
static let shared = NotchDisplayManager()
|
||
|
||
private var currentNotch: DynamicNotch<NotchContentView>?
|
||
private var isActive = false
|
||
|
||
private init() {}
|
||
|
||
/// 在刘海/浮动区域展示 AI 回复
|
||
func show(text: String) {
|
||
currentNotch?.hide()
|
||
|
||
let content = NotchContentView(
|
||
text: text,
|
||
onCopy: {
|
||
let pasteboard = NSPasteboard.general
|
||
pasteboard.clearContents()
|
||
pasteboard.setString(text, forType: .string)
|
||
}
|
||
)
|
||
|
||
currentNotch = DynamicNotch(style: .notch) {
|
||
content
|
||
}
|
||
isActive = true
|
||
|
||
// 显示 15 秒;用户鼠标悬停时自动暂停倒计时
|
||
currentNotch?.show(for: 15)
|
||
}
|
||
|
||
func hide() {
|
||
currentNotch?.hide()
|
||
currentNotch = nil
|
||
isActive = false
|
||
}
|
||
}
|
||
|
||
// MARK: - 弹窗内容视图
|
||
|
||
struct NotchContentView: View {
|
||
let text: String
|
||
var onCopy: () -> Void
|
||
|
||
private let maxHeight: CGFloat = 220
|
||
|
||
var body: some View {
|
||
VStack(alignment: .leading, spacing: 8) {
|
||
// 标题栏
|
||
HStack(spacing: 0) {
|
||
HStack(spacing: 4) {
|
||
Image(systemName: "sparkles")
|
||
.font(.system(size: 10))
|
||
Text("AI 助手回复")
|
||
.font(.system(size: 11, weight: .medium))
|
||
}
|
||
.foregroundColor(.white.opacity(0.5))
|
||
|
||
Spacer()
|
||
|
||
Button(action: onCopy) {
|
||
HStack(spacing: 3) {
|
||
Image(systemName: "doc.on.doc")
|
||
.font(.system(size: 9))
|
||
Text("复制")
|
||
.font(.system(size: 10))
|
||
}
|
||
.foregroundColor(.white.opacity(0.8))
|
||
.padding(.horizontal, 8)
|
||
.padding(.vertical, 3)
|
||
.background(.white.opacity(0.12))
|
||
.clipShape(Capsule())
|
||
}
|
||
.buttonStyle(.plain)
|
||
}
|
||
|
||
// 文本区域
|
||
ScrollView(.vertical, showsIndicators: false) {
|
||
Text(text)
|
||
.font(.system(size: 13))
|
||
.foregroundColor(.white.opacity(0.85))
|
||
.lineSpacing(4)
|
||
.frame(maxWidth: .infinity, alignment: .leading)
|
||
}
|
||
.frame(maxHeight: maxHeight)
|
||
}
|
||
.padding(.horizontal, 14)
|
||
.padding(.vertical, 10)
|
||
.frame(width: 320)
|
||
}
|
||
}
|