Merge pull request #352 from Beingpax/improve-enhancement-prompt-view
Improve enhancement prompt view
This commit is contained in:
commit
b02a22cb95
@ -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,8 +42,8 @@ enum PromptTemplates {
|
||||
- Output only the cleaned text.
|
||||
- Don't add any information not available in the <TRANSCRIPT> text ever.
|
||||
""",
|
||||
icon: .sealedFill,
|
||||
description: "Default system prompt for improving clarity and accuracy of transcriptions"
|
||||
icon: "checkmark.seal.fill",
|
||||
description: "Default system prompt"
|
||||
),
|
||||
TemplatePrompt(
|
||||
id: UUID(),
|
||||
@ -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,8 +76,8 @@ 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,
|
||||
description: "Template for converting casual messages into professional email format"
|
||||
icon: "envelope.fill",
|
||||
description: "Professional email formatting"
|
||||
),
|
||||
TemplatePrompt(
|
||||
id: UUID(),
|
||||
@ -95,8 +95,8 @@ enum PromptTemplates {
|
||||
- Output only the rewritten text.
|
||||
- Don't add any information not available in the <TRANSCRIPT> text ever.
|
||||
""",
|
||||
icon: .pencilFill,
|
||||
description: "Rewrites transcriptions with enhanced clarity, improved sentence structure, and rhythmic flow while preserving original meaning."
|
||||
icon: "pencil.circle.fill",
|
||||
description: "Rewrites with better clarity."
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
90
VoiceInk/Views/PredefinedPromptsView.swift
Normal file
90
VoiceInk/Views/PredefinedPromptsView.swift
Normal file
@ -0,0 +1,90 @@
|
||||
import SwiftUI
|
||||
|
||||
struct PredefinedPromptsView: View {
|
||||
let onSelect: (TemplatePrompt) -> Void
|
||||
|
||||
private let columns: [GridItem] = Array(repeating: GridItem(.flexible(), spacing: 18), count: 2)
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
LazyVGrid(columns: columns, spacing: 16) {
|
||||
ForEach(PromptTemplates.all, id: \.title) { template in
|
||||
PredefinedTemplateButton(prompt: template) {
|
||||
onSelect(template)
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.vertical, 20)
|
||||
}
|
||||
.frame(minWidth: 410, idealWidth: 520, maxWidth: 570, maxHeight: 440)
|
||||
}
|
||||
}
|
||||
|
||||
struct PredefinedTemplateButton: View {
|
||||
let prompt: TemplatePrompt
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack(alignment: .center, spacing: 12) {
|
||||
RoundedRectangle(cornerRadius: 10, style: .continuous)
|
||||
.fill(Color(NSColor.unemphasizedSelectedTextBackgroundColor))
|
||||
.frame(width: 42, height: 42)
|
||||
.overlay(
|
||||
Image(systemName: prompt.icon)
|
||||
.font(.system(size: 19, weight: .medium))
|
||||
.foregroundColor(Color(NSColor.labelColor))
|
||||
)
|
||||
|
||||
Text(prompt.title)
|
||||
.font(.system(size: 15, weight: .semibold))
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
|
||||
Text(prompt.description)
|
||||
.font(.system(size: 12))
|
||||
.foregroundColor(Color(NSColor.secondaryLabelColor))
|
||||
.lineLimit(1)
|
||||
.truncationMode(.tail)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .topLeading)
|
||||
.padding(.horizontal, 18)
|
||||
.padding(.vertical, 12)
|
||||
.background(cardBackground)
|
||||
.overlay(cardStroke)
|
||||
.contentShape(RoundedRectangle(cornerRadius: 16, style: .continuous))
|
||||
.shadow(color: cardShadowColor, radius: 6, x: 0, y: 4)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
|
||||
private var cardBackground: some View {
|
||||
RoundedRectangle(cornerRadius: 16, style: .continuous)
|
||||
.fill(Color(NSColor.controlBackgroundColor))
|
||||
}
|
||||
|
||||
private var cardStroke: some View {
|
||||
RoundedRectangle(cornerRadius: 16, style: .continuous)
|
||||
.stroke(
|
||||
LinearGradient(
|
||||
colors: [
|
||||
Color(NSColor.separatorColor).opacity(0.35),
|
||||
Color(NSColor.separatorColor).opacity(0.15)
|
||||
],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
),
|
||||
lineWidth: 1
|
||||
)
|
||||
}
|
||||
|
||||
private var cardShadowColor: Color {
|
||||
Color(NSColor.shadowColor).opacity(0.25)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
@ -218,36 +215,32 @@ struct PromptEditorView: View {
|
||||
.padding(.horizontal)
|
||||
|
||||
if case .add = mode {
|
||||
// Templates Section with modern styling
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("Start with a Predefined Template")
|
||||
.font(.title2)
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
let columns = [
|
||||
GridItem(.flexible(), spacing: 16),
|
||||
GridItem(.flexible(), spacing: 16)
|
||||
]
|
||||
|
||||
LazyVGrid(columns: columns, spacing: 16) {
|
||||
ForEach(PromptTemplates.all) { template in
|
||||
CleanTemplateButton(prompt: template) {
|
||||
title = template.title
|
||||
promptText = template.promptText
|
||||
selectedIcon = template.icon
|
||||
description = template.description
|
||||
}
|
||||
}
|
||||
// Popover keeps templates accessible without taking space in the layout
|
||||
Button("Start with a Predefined Template") {
|
||||
showingPredefinedPrompts.toggle()
|
||||
}
|
||||
.font(.headline)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.vertical, 12)
|
||||
.background(
|
||||
Capsule()
|
||||
.fill(Color(.windowBackgroundColor).opacity(0.9))
|
||||
)
|
||||
.overlay(
|
||||
Capsule()
|
||||
.stroke(Color.secondary.opacity(0.2), lineWidth: 1)
|
||||
)
|
||||
.buttonStyle(.plain)
|
||||
.padding(.horizontal)
|
||||
.popover(isPresented: $showingPredefinedPrompts, arrowEdge: .bottom) {
|
||||
PredefinedPromptsView { template in
|
||||
title = template.title
|
||||
promptText = template.promptText
|
||||
selectedIcon = template.icon
|
||||
description = template.description
|
||||
showingPredefinedPrompts = false
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
.padding(.vertical, 16)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.fill(Color(.windowBackgroundColor).opacity(0.6))
|
||||
)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -285,90 +278,6 @@ struct PromptEditorView: View {
|
||||
}
|
||||
}
|
||||
|
||||
// Clean template button with minimal styling
|
||||
struct CleanTemplateButton: View {
|
||||
let prompt: TemplatePrompt
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
HStack(alignment: .top, spacing: 12) {
|
||||
// Clean icon design
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(Color.accentColor.opacity(0.15))
|
||||
.frame(width: 44, height: 44)
|
||||
|
||||
Image(systemName: prompt.icon.rawValue)
|
||||
.font(.system(size: 20, weight: .semibold))
|
||||
.foregroundColor(.accentColor)
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(prompt.title)
|
||||
.font(.system(size: 16, weight: .semibold))
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
|
||||
Text(prompt.description)
|
||||
.font(.system(size: 13))
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(2)
|
||||
.multilineTextAlignment(.leading)
|
||||
}
|
||||
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
.padding(16)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.fill(Color(.controlBackgroundColor))
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 12)
|
||||
.stroke(Color.secondary.opacity(0.2), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
|
||||
// Keep the old TemplateButton for backward compatibility if needed elsewhere
|
||||
struct TemplateButton: View {
|
||||
let prompt: TemplatePrompt
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
HStack(alignment: .center, spacing: 12) {
|
||||
Image(systemName: prompt.icon.rawValue)
|
||||
.font(.system(size: 20, weight: .medium))
|
||||
.foregroundColor(.accentColor)
|
||||
.frame(width: 28, height: 28)
|
||||
.background(Color.accentColor.opacity(0.12))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 6))
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
Text(prompt.title)
|
||||
.font(.system(size: 15, weight: .semibold))
|
||||
.foregroundColor(.primary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
.padding(12)
|
||||
.frame(height: 60)
|
||||
.background(Color(NSColor.controlBackgroundColor))
|
||||
.cornerRadius(10)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(Color.secondary.opacity(0.18), lineWidth: 1)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
}
|
||||
}
|
||||
|
||||
// Reusable Trigger Words Editor Component
|
||||
struct TriggerWordsEditor: View {
|
||||
@Binding var triggerWords: [String]
|
||||
@ -425,44 +334,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 +374,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