// // PermissionBanner.swift // 划词侠 plugin // // Two banners share this file: // • Accessibility-permission warning (when sourceMode = .selection) // • Missing-config (state E) full-card with copy-path helper // import SwiftUI struct AccessibilityBanner: View { var onOpenSettings: () -> Void var body: some View { HStack(alignment: .top, spacing: 8) { Image(systemName: "exclamationmark.triangle.fill") .foregroundColor(ClipperTheme.amber) .font(.system(size: 12)) .padding(.top, 1) VStack(alignment: .leading, spacing: 4) { Text("选中文本需要辅助功能权限") .font(.system(size: 11.5, weight: .semibold)) .foregroundColor(ClipperTheme.fg85) Text("前往 系统设置 > 隐私与安全性 > 辅助功能,勾选 Mio Island 后回来重试。当前已自动回退到粘贴板。") .font(.system(size: 10.5)) .foregroundColor(ClipperTheme.fg55) .fixedSize(horizontal: false, vertical: true) Button(action: onOpenSettings) { Text("打开 系统设置") .font(.system(size: 11, weight: .semibold)) .foregroundColor(ClipperTheme.amber) } .buttonStyle(.plain) .padding(.top, 2) } Spacer(minLength: 0) } .padding(10) .background( RoundedRectangle(cornerRadius: 10) .fill(ClipperTheme.amber.opacity(0.08)) .overlay( RoundedRectangle(cornerRadius: 10) .stroke(ClipperTheme.amber.opacity(0.25), lineWidth: 0.5) ) ) } } struct MissingConfigCard: View { @ObservedObject var store: ClipperStore @State private var copied = false var body: some View { VStack(alignment: .leading, spacing: 12) { HStack(spacing: 8) { Image(systemName: "key.horizontal") .font(.system(size: 14, weight: .semibold)) .foregroundColor(ClipperTheme.amber) Text("先填一个 DeepSeek API Key") .font(.system(size: 13, weight: .semibold)) .foregroundColor(ClipperTheme.fgPrimary) } VStack(alignment: .leading, spacing: 6) { Text("打开下面的配置文件,把 deepseek_api_key 改成你的真实 key,然后点 重新加载。") .font(.system(size: 11.5)) .foregroundColor(ClipperTheme.fg70) .fixedSize(horizontal: false, vertical: true) Text(ClipperConfig.configPath) .font(.system(size: 10.5, design: .monospaced)) .foregroundColor(ClipperTheme.fg55) .padding(.horizontal, 10) .padding(.vertical, 8) .frame(maxWidth: .infinity, alignment: .leading) .background( RoundedRectangle(cornerRadius: 8) .fill(ClipperTheme.overlay04) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(ClipperTheme.overlay08, lineWidth: 0.5) ) ) .textSelection(.enabled) } HStack(spacing: 8) { Button { store.copyToPasteboard(ClipperConfig.configPath) copied = true Task { try? await Task.sleep(nanoseconds: 1_200_000_000) await MainActor.run { copied = false } } } label: { HStack(spacing: 6) { Image(systemName: copied ? "checkmark" : "doc.on.doc") .font(.system(size: 11, weight: .semibold)) Text(copied ? "路径已复制" : "复制路径") .font(.system(size: 12, weight: .semibold)) } .foregroundColor(copied ? ClipperTheme.lime : ClipperTheme.fg70) .frame(maxWidth: .infinity) .frame(height: 30) .background( Capsule().fill(ClipperTheme.overlay06) .overlay(Capsule().stroke(ClipperTheme.overlay08, lineWidth: 0.5)) ) } .buttonStyle(.plain) Button { NSWorkspace.shared.open(URL(fileURLWithPath: ClipperConfig.configDir)) } label: { HStack(spacing: 6) { Image(systemName: "folder") .font(.system(size: 11, weight: .semibold)) Text("打开文件夹") .font(.system(size: 12, weight: .semibold)) } .foregroundColor(ClipperTheme.fg70) .frame(maxWidth: .infinity) .frame(height: 30) .background( Capsule().fill(ClipperTheme.overlay06) .overlay(Capsule().stroke(ClipperTheme.overlay08, lineWidth: 0.5)) ) } .buttonStyle(.plain) Button { store.reloadConfig() } label: { HStack(spacing: 6) { Image(systemName: "arrow.clockwise") .font(.system(size: 11, weight: .semibold)) Text("重新加载") .font(.system(size: 12, weight: .semibold)) } .foregroundColor(Color(red: 0x0B/255, green: 0x0B/255, blue: 0x0B/255)) .frame(maxWidth: .infinity) .frame(height: 30) .background(Capsule().fill(ClipperTheme.lime)) } .buttonStyle(.plain) } Text("没有 key?去 platform.deepseek.com 注册一个,新用户有免费额度。") .font(.system(size: 10.5)) .foregroundColor(ClipperTheme.fg45) } .padding(14) .frame(maxWidth: .infinity, alignment: .leading) .background( RoundedRectangle(cornerRadius: 14) .fill(ClipperTheme.overlay04) .overlay( RoundedRectangle(cornerRadius: 14) .stroke(ClipperTheme.overlay08, lineWidth: 0.5) ) ) } }