From ed9a13c16b434411f4e71da56c9f8d5124fa7e77 Mon Sep 17 00:00:00 2001 From: Beingpax Date: Fri, 31 Oct 2025 12:10:57 +0545 Subject: [PATCH 1/3] Improve prompt icon picker: grid popover UI, 30+ productivity icons, system colors --- VoiceInk/Models/CustomPrompt.swift | 136 +++++++++--------- VoiceInk/Models/PredefinedPrompts.swift | 4 +- VoiceInk/Models/PromptTemplates.swift | 8 +- VoiceInk/Services/AIEnhancementService.swift | 2 +- VoiceInk/Views/MenuBarView.swift | 2 +- VoiceInk/Views/PromptEditorView.swift | 131 ++++++++--------- .../Recorder/EnhancementPromptPopover.swift | 2 +- .../Views/Recorder/RecorderComponents.swift | 2 +- 8 files changed, 145 insertions(+), 142 deletions(-) diff --git a/VoiceInk/Models/CustomPrompt.swift b/VoiceInk/Models/CustomPrompt.swift index 6c7e226..2260457 100644 --- a/VoiceInk/Models/CustomPrompt.swift +++ b/VoiceInk/Models/CustomPrompt.swift @@ -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( diff --git a/VoiceInk/Models/PredefinedPrompts.swift b/VoiceInk/Models/PredefinedPrompts.swift index 69a86a2..15718c1 100644 --- a/VoiceInk/Models/PredefinedPrompts.swift +++ b/VoiceInk/Models/PredefinedPrompts.swift @@ -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 diff --git a/VoiceInk/Models/PromptTemplates.swift b/VoiceInk/Models/PromptTemplates.swift index ec0e04c..47eb348 100644 --- a/VoiceInk/Models/PromptTemplates.swift +++ b/VoiceInk/Models/PromptTemplates.swift @@ -42,7 +42,7 @@ enum PromptTemplates { - Output only the cleaned text. - Don't add any information not available in the 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 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 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 text ever. """, - icon: .pencilFill, + icon: "pencil.circle.fill", description: "Rewrites transcriptions with enhanced clarity, improved sentence structure, and rhythmic flow while preserving original meaning." ) ] diff --git a/VoiceInk/Services/AIEnhancementService.swift b/VoiceInk/Services/AIEnhancementService.swift index 0ab70f6..d5042c5 100644 --- a/VoiceInk/Services/AIEnhancementService.swift +++ b/VoiceInk/Services/AIEnhancementService.swift @@ -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 { diff --git a/VoiceInk/Views/MenuBarView.swift b/VoiceInk/Views/MenuBarView.swift index 8067481..bf7543d 100644 --- a/VoiceInk/Views/MenuBarView.swift +++ b/VoiceInk/Views/MenuBarView.swift @@ -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 { diff --git a/VoiceInk/Views/PromptEditorView.swift b/VoiceInk/Views/PromptEditorView.swift index 31498fc..46c497e 100644 --- a/VoiceInk/Views/PromptEditorView.swift +++ b/VoiceInk/Views/PromptEditorView.swift @@ -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) } } -} \ No newline at end of file +} + +// 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) + } +} diff --git a/VoiceInk/Views/Recorder/EnhancementPromptPopover.swift b/VoiceInk/Views/Recorder/EnhancementPromptPopover.swift index 7ff4ae0..df51ee9 100644 --- a/VoiceInk/Views/Recorder/EnhancementPromptPopover.swift +++ b/VoiceInk/Views/Recorder/EnhancementPromptPopover.swift @@ -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)) diff --git a/VoiceInk/Views/Recorder/RecorderComponents.swift b/VoiceInk/Views/Recorder/RecorderComponents.swift index 6137cc1..b78e335 100644 --- a/VoiceInk/Views/Recorder/RecorderComponents.swift +++ b/VoiceInk/Views/Recorder/RecorderComponents.swift @@ -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 ) { From 6d3fac76ef48b12d8c239c9a1bddc6027946d1f7 Mon Sep 17 00:00:00 2001 From: Beingpax Date: Fri, 31 Oct 2025 15:12:12 +0545 Subject: [PATCH 2/3] Redesign predefinedprompttemplates view --- VoiceInk/Models/PromptTemplates.swift | 6 +- VoiceInk/Views/PredefinedPromptsView.swift | 90 ++++++++++++++ VoiceInk/Views/PromptEditorView.swift | 136 ++++----------------- 3 files changed, 117 insertions(+), 115 deletions(-) create mode 100644 VoiceInk/Views/PredefinedPromptsView.swift diff --git a/VoiceInk/Models/PromptTemplates.swift b/VoiceInk/Models/PromptTemplates.swift index 47eb348..7b6b086 100644 --- a/VoiceInk/Models/PromptTemplates.swift +++ b/VoiceInk/Models/PromptTemplates.swift @@ -43,7 +43,7 @@ enum PromptTemplates { - Don't add any information not available in the text ever. """, icon: "checkmark.seal.fill", - description: "Default system prompt for improving clarity and accuracy of transcriptions" + description: "Default system prompt" ), TemplatePrompt( id: UUID(), @@ -77,7 +77,7 @@ enum PromptTemplates { - Don't add any information not available in the text ever. """, icon: "envelope.fill", - description: "Template for converting casual messages into professional email format" + description: "Professional email formatting" ), TemplatePrompt( id: UUID(), @@ -96,7 +96,7 @@ enum PromptTemplates { - Don't add any information not available in the text ever. """, icon: "pencil.circle.fill", - description: "Rewrites transcriptions with enhanced clarity, improved sentence structure, and rhythmic flow while preserving original meaning." + description: "Rewrites with better clarity." ) ] } diff --git a/VoiceInk/Views/PredefinedPromptsView.swift b/VoiceInk/Views/PredefinedPromptsView.swift new file mode 100644 index 0000000..bfe85fe --- /dev/null +++ b/VoiceInk/Views/PredefinedPromptsView.swift @@ -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) { 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) + } +} diff --git a/VoiceInk/Views/PromptEditorView.swift b/VoiceInk/Views/PromptEditorView.swift index 46c497e..0a005d6 100644 --- a/VoiceInk/Views/PromptEditorView.swift +++ b/VoiceInk/Views/PromptEditorView.swift @@ -215,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) } } } @@ -282,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) - .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) - .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] From 648c36f12ac15d1311ee0f329b94714dc7768415 Mon Sep 17 00:00:00 2001 From: Prakash Joshi Pax <101010368+Beingpax@users.noreply.github.com> Date: Fri, 31 Oct 2025 16:41:33 +0545 Subject: [PATCH 3/3] Update VoiceInk/Views/PredefinedPromptsView.swift Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> --- VoiceInk/Views/PredefinedPromptsView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VoiceInk/Views/PredefinedPromptsView.swift b/VoiceInk/Views/PredefinedPromptsView.swift index bfe85fe..c243197 100644 --- a/VoiceInk/Views/PredefinedPromptsView.swift +++ b/VoiceInk/Views/PredefinedPromptsView.swift @@ -8,7 +8,7 @@ struct PredefinedPromptsView: View { var body: some View { ScrollView { LazyVGrid(columns: columns, spacing: 16) { - ForEach(PromptTemplates.all) { template in + ForEach(PromptTemplates.all, id: \.title) { template in PredefinedTemplateButton(prompt: template) { onSelect(template) }