Improve prompt icon picker: grid popover UI, 30+ productivity icons, system colors
This commit is contained in:
parent
417adba2bc
commit
ed9a13c16b
@ -1,78 +1,78 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum PromptIcon: String, Codable, CaseIterable {
|
||||
// Document & Text
|
||||
case documentFill = "doc.text.fill"
|
||||
case textbox = "textbox"
|
||||
case sealedFill = "checkmark.seal.fill"
|
||||
|
||||
// Communication
|
||||
case chatFill = "bubble.left.and.bubble.right.fill"
|
||||
case messageFill = "message.fill"
|
||||
case emailFill = "envelope.fill"
|
||||
|
||||
// Professional
|
||||
case meetingFill = "person.2.fill"
|
||||
case presentationFill = "person.wave.2.fill"
|
||||
case briefcaseFill = "briefcase.fill"
|
||||
|
||||
// Technical
|
||||
case codeFill = "curlybraces"
|
||||
case terminalFill = "terminal.fill"
|
||||
case gearFill = "gearshape.fill"
|
||||
|
||||
// Content
|
||||
case blogFill = "doc.text.image.fill"
|
||||
case notesFill = "note"
|
||||
case bookFill = "book.fill"
|
||||
case bookmarkFill = "bookmark.fill"
|
||||
case pencilFill = "pencil.circle.fill"
|
||||
|
||||
// Media & Creative
|
||||
case videoFill = "video.fill"
|
||||
case micFill = "mic.fill"
|
||||
case musicFill = "music.note"
|
||||
case photoFill = "photo.fill"
|
||||
case brushFill = "paintbrush.fill"
|
||||
|
||||
var title: String {
|
||||
switch self {
|
||||
typealias PromptIcon = String
|
||||
|
||||
extension PromptIcon {
|
||||
static let allCases: [PromptIcon] = [
|
||||
// Document & Text
|
||||
case .documentFill: return "Document"
|
||||
case .textbox: return "Textbox"
|
||||
case .sealedFill: return "Sealed"
|
||||
|
||||
"doc.text.fill",
|
||||
"textbox",
|
||||
"checkmark.seal.fill",
|
||||
|
||||
// Communication
|
||||
case .chatFill: return "Chat"
|
||||
case .messageFill: return "Message"
|
||||
case .emailFill: return "Email"
|
||||
|
||||
"bubble.left.and.bubble.right.fill",
|
||||
"message.fill",
|
||||
"envelope.fill",
|
||||
|
||||
// Professional
|
||||
case .meetingFill: return "Meeting"
|
||||
case .presentationFill: return "Presentation"
|
||||
case .briefcaseFill: return "Briefcase"
|
||||
|
||||
"person.2.fill",
|
||||
"person.wave.2.fill",
|
||||
"briefcase.fill",
|
||||
|
||||
// Technical
|
||||
case .codeFill: return "Code"
|
||||
case .terminalFill: return "Terminal"
|
||||
case .gearFill: return "Settings"
|
||||
|
||||
"curlybraces",
|
||||
"terminal.fill",
|
||||
"gearshape.fill",
|
||||
|
||||
// Content
|
||||
case .blogFill: return "Blog"
|
||||
case .notesFill: return "Notes"
|
||||
case .bookFill: return "Book"
|
||||
case .bookmarkFill: return "Bookmark"
|
||||
case .pencilFill: return "Edit"
|
||||
|
||||
"doc.text.image.fill",
|
||||
"note",
|
||||
"book.fill",
|
||||
"bookmark.fill",
|
||||
"pencil.circle.fill",
|
||||
|
||||
// Media & Creative
|
||||
case .videoFill: return "Video"
|
||||
case .micFill: return "Audio"
|
||||
case .musicFill: return "Music"
|
||||
case .photoFill: return "Photo"
|
||||
case .brushFill: return "Design"
|
||||
}
|
||||
}
|
||||
"video.fill",
|
||||
"mic.fill",
|
||||
"music.note",
|
||||
"photo.fill",
|
||||
"paintbrush.fill",
|
||||
|
||||
// Productivity & Time
|
||||
"clock.fill",
|
||||
"calendar",
|
||||
"list.bullet",
|
||||
"checkmark.circle.fill",
|
||||
"timer",
|
||||
"hourglass",
|
||||
"star.fill",
|
||||
"flag.fill",
|
||||
"tag.fill",
|
||||
"folder.fill",
|
||||
"paperclip",
|
||||
"tray.fill",
|
||||
"chart.bar.fill",
|
||||
"flame.fill",
|
||||
"target",
|
||||
"list.clipboard.fill",
|
||||
"brain.head.profile",
|
||||
"lightbulb.fill",
|
||||
"megaphone.fill",
|
||||
"heart.fill",
|
||||
"map.fill",
|
||||
"house.fill",
|
||||
"camera.fill",
|
||||
"figure.walk",
|
||||
"dumbbell.fill",
|
||||
"cart.fill",
|
||||
"creditcard.fill",
|
||||
"graduationcap.fill",
|
||||
"airplane",
|
||||
"leaf.fill",
|
||||
"hand.raised.fill",
|
||||
"hand.thumbsup.fill"
|
||||
]
|
||||
}
|
||||
|
||||
struct CustomPrompt: Identifiable, Codable, Equatable {
|
||||
@ -91,7 +91,7 @@ struct CustomPrompt: Identifiable, Codable, Equatable {
|
||||
title: String,
|
||||
promptText: String,
|
||||
isActive: Bool = false,
|
||||
icon: PromptIcon = .documentFill,
|
||||
icon: PromptIcon = "doc.text.fill",
|
||||
description: String? = nil,
|
||||
isPredefined: Bool = false,
|
||||
triggerWords: [String] = [],
|
||||
@ -199,7 +199,7 @@ extension CustomPrompt {
|
||||
.blur(radius: 2)
|
||||
|
||||
// Icon with enhanced effects
|
||||
Image(systemName: icon.rawValue)
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 20, weight: .medium))
|
||||
.foregroundStyle(
|
||||
LinearGradient(
|
||||
|
||||
@ -19,7 +19,7 @@ enum PredefinedPrompts {
|
||||
id: defaultPromptId,
|
||||
title: "Default",
|
||||
promptText: PromptTemplates.all.first { $0.title == "System Default" }?.promptText ?? "",
|
||||
icon: .sealedFill,
|
||||
icon: "checkmark.seal.fill",
|
||||
description: "Default mode to improved clarity and accuracy of the transcription",
|
||||
isPredefined: true,
|
||||
useSystemInstructions: true
|
||||
@ -29,7 +29,7 @@ enum PredefinedPrompts {
|
||||
id: assistantPromptId,
|
||||
title: "Assistant",
|
||||
promptText: AIPrompts.assistantMode,
|
||||
icon: .chatFill,
|
||||
icon: "bubble.left.and.bubble.right.fill",
|
||||
description: "AI assistant that provides direct answers to queries",
|
||||
isPredefined: true,
|
||||
useSystemInstructions: false
|
||||
|
||||
@ -42,7 +42,7 @@ enum PromptTemplates {
|
||||
- Output only the cleaned text.
|
||||
- Don't add any information not available in the <TRANSCRIPT> text ever.
|
||||
""",
|
||||
icon: .sealedFill,
|
||||
icon: "checkmark.seal.fill",
|
||||
description: "Default system prompt for improving clarity and accuracy of transcriptions"
|
||||
),
|
||||
TemplatePrompt(
|
||||
@ -60,7 +60,7 @@ enum PromptTemplates {
|
||||
- Output only the chat message.
|
||||
- Don't add any information not available in the <TRANSCRIPT> text ever.
|
||||
""",
|
||||
icon: .chatFill,
|
||||
icon: "bubble.left.and.bubble.right.fill",
|
||||
description: "Casual chat-style formatting"
|
||||
),
|
||||
|
||||
@ -76,7 +76,7 @@ enum PromptTemplates {
|
||||
- Do not invent new content, but structure it as a proper email format.
|
||||
- Don't add any information not available in the <TRANSCRIPT> text ever.
|
||||
""",
|
||||
icon: .emailFill,
|
||||
icon: "envelope.fill",
|
||||
description: "Template for converting casual messages into professional email format"
|
||||
),
|
||||
TemplatePrompt(
|
||||
@ -95,7 +95,7 @@ enum PromptTemplates {
|
||||
- Output only the rewritten text.
|
||||
- Don't add any information not available in the <TRANSCRIPT> text ever.
|
||||
""",
|
||||
icon: .pencilFill,
|
||||
icon: "pencil.circle.fill",
|
||||
description: "Rewrites transcriptions with enhanced clarity, improved sentence structure, and rhythmic flow while preserving original meaning."
|
||||
)
|
||||
]
|
||||
|
||||
@ -415,7 +415,7 @@ class AIEnhancementService: ObservableObject {
|
||||
screenCaptureService.lastCapturedText = nil
|
||||
}
|
||||
|
||||
func addPrompt(title: String, promptText: String, icon: PromptIcon = .documentFill, description: String? = nil, triggerWords: [String] = [], useSystemInstructions: Bool = true) {
|
||||
func addPrompt(title: String, promptText: String, icon: PromptIcon = "doc.text.fill", description: String? = nil, triggerWords: [String] = [], useSystemInstructions: Bool = true) {
|
||||
let newPrompt = CustomPrompt(title: title, promptText: promptText, icon: icon, description: description, isPredefined: false, triggerWords: triggerWords, useSystemInstructions: useSystemInstructions)
|
||||
customPrompts.append(newPrompt)
|
||||
if customPrompts.count == 1 {
|
||||
|
||||
@ -53,7 +53,7 @@ struct MenuBarView: View {
|
||||
enhancementService.setActivePrompt(prompt)
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: prompt.icon.rawValue)
|
||||
Image(systemName: prompt.icon)
|
||||
.foregroundColor(.accentColor)
|
||||
Text(prompt.title)
|
||||
if enhancementService.selectedPromptId == prompt.id {
|
||||
|
||||
@ -27,6 +27,7 @@ struct PromptEditorView: View {
|
||||
@State private var triggerWords: [String]
|
||||
@State private var showingPredefinedPrompts = false
|
||||
@State private var useSystemInstructions: Bool
|
||||
@State private var showingIconPicker = false
|
||||
|
||||
private var isEditingPredefinedPrompt: Bool {
|
||||
if case .edit(let prompt) = mode {
|
||||
@ -41,7 +42,7 @@ struct PromptEditorView: View {
|
||||
case .add:
|
||||
_title = State(initialValue: "")
|
||||
_promptText = State(initialValue: "")
|
||||
_selectedIcon = State(initialValue: .documentFill)
|
||||
_selectedIcon = State(initialValue: "doc.text.fill")
|
||||
_description = State(initialValue: "")
|
||||
_triggerWords = State(initialValue: [])
|
||||
_useSystemInstructions = State(initialValue: true)
|
||||
@ -132,29 +133,25 @@ struct PromptEditorView: View {
|
||||
.font(.headline)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Menu {
|
||||
IconMenuContent(selectedIcon: $selectedIcon)
|
||||
} label: {
|
||||
HStack {
|
||||
Image(systemName: selectedIcon.rawValue)
|
||||
.font(.system(size: 16))
|
||||
.foregroundColor(.accentColor)
|
||||
.frame(width: 24)
|
||||
|
||||
Text(selectedIcon.title)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
Spacer()
|
||||
|
||||
Image(systemName: "chevron.up.chevron.down")
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.padding(8)
|
||||
.background(Color(NSColor.controlBackgroundColor))
|
||||
.cornerRadius(8)
|
||||
// Preview of selected icon - clickable to open popover (square button)
|
||||
Button(action: {
|
||||
showingIconPicker = true
|
||||
}) {
|
||||
Image(systemName: selectedIcon)
|
||||
.font(.system(size: 20))
|
||||
.foregroundColor(.primary)
|
||||
.frame(width: 48, height: 48)
|
||||
.background(Color(NSColor.controlBackgroundColor))
|
||||
.cornerRadius(8)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(Color.secondary.opacity(0.2), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
.frame(width: 180)
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
.popover(isPresented: $showingIconPicker, arrowEdge: .bottom) {
|
||||
IconPickerPopover(selectedIcon: $selectedIcon, isPresented: $showingIconPicker)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
@ -299,7 +296,7 @@ struct CleanTemplateButton: View {
|
||||
.fill(Color.accentColor.opacity(0.15))
|
||||
.frame(width: 44, height: 44)
|
||||
|
||||
Image(systemName: prompt.icon.rawValue)
|
||||
Image(systemName: prompt.icon)
|
||||
.font(.system(size: 20, weight: .semibold))
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
@ -342,7 +339,7 @@ struct TemplateButton: View {
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
HStack(alignment: .center, spacing: 12) {
|
||||
Image(systemName: prompt.icon.rawValue)
|
||||
Image(systemName: prompt.icon)
|
||||
.font(.system(size: 20, weight: .medium))
|
||||
.foregroundColor(.accentColor)
|
||||
.frame(width: 28, height: 28)
|
||||
@ -425,44 +422,6 @@ struct TriggerWordsEditor: View {
|
||||
}
|
||||
}
|
||||
|
||||
// Icon menu content for better organization
|
||||
struct IconMenuContent: View {
|
||||
@Binding var selectedIcon: PromptIcon
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
IconMenuSection(title: "Document & Text", icons: [.documentFill, .textbox, .sealedFill], selectedIcon: $selectedIcon)
|
||||
IconMenuSection(title: "Communication", icons: [.chatFill, .messageFill, .emailFill], selectedIcon: $selectedIcon)
|
||||
IconMenuSection(title: "Professional", icons: [.meetingFill, .presentationFill, .briefcaseFill], selectedIcon: $selectedIcon)
|
||||
IconMenuSection(title: "Technical", icons: [.codeFill, .terminalFill, .gearFill], selectedIcon: $selectedIcon)
|
||||
IconMenuSection(title: "Content", icons: [.blogFill, .notesFill, .bookFill, .bookmarkFill, .pencilFill], selectedIcon: $selectedIcon)
|
||||
IconMenuSection(title: "Media & Creative", icons: [.videoFill, .micFill, .musicFill, .photoFill, .brushFill], selectedIcon: $selectedIcon)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Icon menu section for better organization
|
||||
struct IconMenuSection: View {
|
||||
let title: String
|
||||
let icons: [PromptIcon]
|
||||
@Binding var selectedIcon: PromptIcon
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
Text(title)
|
||||
.font(.caption)
|
||||
.foregroundColor(.secondary)
|
||||
ForEach(icons, id: \.self) { icon in
|
||||
Button(action: { selectedIcon = icon }) {
|
||||
Label(icon.title, systemImage: icon.rawValue)
|
||||
}
|
||||
}
|
||||
if title != "Media & Creative" {
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct TriggerWordItemView: View {
|
||||
let word: String
|
||||
@ -503,4 +462,48 @@ struct TriggerWordItemView: View {
|
||||
.stroke(Color.secondary.opacity(0.2), lineWidth: 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Icon Picker Popover - shows icons in a grid format without category labels
|
||||
struct IconPickerPopover: View {
|
||||
@Binding var selectedIcon: PromptIcon
|
||||
@Binding var isPresented: Bool
|
||||
|
||||
var body: some View {
|
||||
let columns = [
|
||||
GridItem(.adaptive(minimum: 45, maximum: 52), spacing: 14)
|
||||
]
|
||||
|
||||
ScrollView {
|
||||
LazyVGrid(columns: columns, spacing: 14) {
|
||||
ForEach(PromptIcon.allCases, id: \.self) { icon in
|
||||
Button(action: {
|
||||
withAnimation(.spring(response: 0.2, dampingFraction: 0.7)) {
|
||||
selectedIcon = icon
|
||||
isPresented = false
|
||||
}
|
||||
}) {
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(selectedIcon == icon ? Color(NSColor.windowBackgroundColor) : Color(NSColor.controlBackgroundColor))
|
||||
.frame(width: 52, height: 52)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.stroke(selectedIcon == icon ? Color(NSColor.separatorColor) : Color.secondary.opacity(0.2), lineWidth: selectedIcon == icon ? 2 : 1)
|
||||
)
|
||||
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: 24, weight: .medium))
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
.scaleEffect(selectedIcon == icon ? 1.1 : 1.0)
|
||||
.animation(.spring(response: 0.2, dampingFraction: 0.7), value: selectedIcon == icon)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
.padding(20)
|
||||
}
|
||||
.frame(width: 400, height: 400)
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,7 +70,7 @@ struct EnhancementPromptRow: View {
|
||||
Button(action: action) {
|
||||
HStack(spacing: 8) {
|
||||
// Use the icon from the prompt
|
||||
Image(systemName: prompt.icon.rawValue)
|
||||
Image(systemName: prompt.icon)
|
||||
.font(.system(size: 14))
|
||||
.foregroundColor(isDisabled ? .white.opacity(0.4) : .white.opacity(0.7))
|
||||
|
||||
|
||||
@ -167,7 +167,7 @@ struct RecorderPromptButton: View {
|
||||
var body: some View {
|
||||
RecorderToggleButton(
|
||||
isEnabled: enhancementService.isEnhancementEnabled,
|
||||
icon: enhancementService.activePrompt?.icon.rawValue ?? enhancementService.allPrompts.first(where: { $0.id == PredefinedPrompts.defaultPromptId })?.icon.rawValue ?? "checkmark.seal.fill",
|
||||
icon: enhancementService.activePrompt?.icon ?? enhancementService.allPrompts.first(where: { $0.id == PredefinedPrompts.defaultPromptId })?.icon ?? "checkmark.seal.fill",
|
||||
color: .blue,
|
||||
disabled: false
|
||||
) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user