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 Foundation
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
enum PromptIcon: String, Codable, CaseIterable {
|
typealias PromptIcon = String
|
||||||
// Document & Text
|
|
||||||
case documentFill = "doc.text.fill"
|
extension PromptIcon {
|
||||||
case textbox = "textbox"
|
static let allCases: [PromptIcon] = [
|
||||||
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 {
|
|
||||||
// Document & Text
|
// Document & Text
|
||||||
case .documentFill: return "Document"
|
"doc.text.fill",
|
||||||
case .textbox: return "Textbox"
|
"textbox",
|
||||||
case .sealedFill: return "Sealed"
|
"checkmark.seal.fill",
|
||||||
|
|
||||||
// Communication
|
// Communication
|
||||||
case .chatFill: return "Chat"
|
"bubble.left.and.bubble.right.fill",
|
||||||
case .messageFill: return "Message"
|
"message.fill",
|
||||||
case .emailFill: return "Email"
|
"envelope.fill",
|
||||||
|
|
||||||
// Professional
|
// Professional
|
||||||
case .meetingFill: return "Meeting"
|
"person.2.fill",
|
||||||
case .presentationFill: return "Presentation"
|
"person.wave.2.fill",
|
||||||
case .briefcaseFill: return "Briefcase"
|
"briefcase.fill",
|
||||||
|
|
||||||
// Technical
|
// Technical
|
||||||
case .codeFill: return "Code"
|
"curlybraces",
|
||||||
case .terminalFill: return "Terminal"
|
"terminal.fill",
|
||||||
case .gearFill: return "Settings"
|
"gearshape.fill",
|
||||||
|
|
||||||
// Content
|
// Content
|
||||||
case .blogFill: return "Blog"
|
"doc.text.image.fill",
|
||||||
case .notesFill: return "Notes"
|
"note",
|
||||||
case .bookFill: return "Book"
|
"book.fill",
|
||||||
case .bookmarkFill: return "Bookmark"
|
"bookmark.fill",
|
||||||
case .pencilFill: return "Edit"
|
"pencil.circle.fill",
|
||||||
|
|
||||||
// Media & Creative
|
// Media & Creative
|
||||||
case .videoFill: return "Video"
|
"video.fill",
|
||||||
case .micFill: return "Audio"
|
"mic.fill",
|
||||||
case .musicFill: return "Music"
|
"music.note",
|
||||||
case .photoFill: return "Photo"
|
"photo.fill",
|
||||||
case .brushFill: return "Design"
|
"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 {
|
struct CustomPrompt: Identifiable, Codable, Equatable {
|
||||||
@ -91,7 +91,7 @@ struct CustomPrompt: Identifiable, Codable, Equatable {
|
|||||||
title: String,
|
title: String,
|
||||||
promptText: String,
|
promptText: String,
|
||||||
isActive: Bool = false,
|
isActive: Bool = false,
|
||||||
icon: PromptIcon = .documentFill,
|
icon: PromptIcon = "doc.text.fill",
|
||||||
description: String? = nil,
|
description: String? = nil,
|
||||||
isPredefined: Bool = false,
|
isPredefined: Bool = false,
|
||||||
triggerWords: [String] = [],
|
triggerWords: [String] = [],
|
||||||
@ -199,7 +199,7 @@ extension CustomPrompt {
|
|||||||
.blur(radius: 2)
|
.blur(radius: 2)
|
||||||
|
|
||||||
// Icon with enhanced effects
|
// Icon with enhanced effects
|
||||||
Image(systemName: icon.rawValue)
|
Image(systemName: icon)
|
||||||
.font(.system(size: 20, weight: .medium))
|
.font(.system(size: 20, weight: .medium))
|
||||||
.foregroundStyle(
|
.foregroundStyle(
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
|
|||||||
@ -19,7 +19,7 @@ enum PredefinedPrompts {
|
|||||||
id: defaultPromptId,
|
id: defaultPromptId,
|
||||||
title: "Default",
|
title: "Default",
|
||||||
promptText: PromptTemplates.all.first { $0.title == "System Default" }?.promptText ?? "",
|
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",
|
description: "Default mode to improved clarity and accuracy of the transcription",
|
||||||
isPredefined: true,
|
isPredefined: true,
|
||||||
useSystemInstructions: true
|
useSystemInstructions: true
|
||||||
@ -29,7 +29,7 @@ enum PredefinedPrompts {
|
|||||||
id: assistantPromptId,
|
id: assistantPromptId,
|
||||||
title: "Assistant",
|
title: "Assistant",
|
||||||
promptText: AIPrompts.assistantMode,
|
promptText: AIPrompts.assistantMode,
|
||||||
icon: .chatFill,
|
icon: "bubble.left.and.bubble.right.fill",
|
||||||
description: "AI assistant that provides direct answers to queries",
|
description: "AI assistant that provides direct answers to queries",
|
||||||
isPredefined: true,
|
isPredefined: true,
|
||||||
useSystemInstructions: false
|
useSystemInstructions: false
|
||||||
|
|||||||
@ -42,8 +42,8 @@ enum PromptTemplates {
|
|||||||
- Output only the cleaned text.
|
- Output only the cleaned text.
|
||||||
- Don't add any information not available in the <TRANSCRIPT> text ever.
|
- 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"
|
description: "Default system prompt"
|
||||||
),
|
),
|
||||||
TemplatePrompt(
|
TemplatePrompt(
|
||||||
id: UUID(),
|
id: UUID(),
|
||||||
@ -60,7 +60,7 @@ enum PromptTemplates {
|
|||||||
- Output only the chat message.
|
- Output only the chat message.
|
||||||
- Don't add any information not available in the <TRANSCRIPT> text ever.
|
- 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"
|
description: "Casual chat-style formatting"
|
||||||
),
|
),
|
||||||
|
|
||||||
@ -76,8 +76,8 @@ enum PromptTemplates {
|
|||||||
- Do not invent new content, but structure it as a proper email format.
|
- 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.
|
- 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"
|
description: "Professional email formatting"
|
||||||
),
|
),
|
||||||
TemplatePrompt(
|
TemplatePrompt(
|
||||||
id: UUID(),
|
id: UUID(),
|
||||||
@ -95,8 +95,8 @@ enum PromptTemplates {
|
|||||||
- Output only the rewritten text.
|
- Output only the rewritten text.
|
||||||
- Don't add any information not available in the <TRANSCRIPT> text ever.
|
- 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."
|
description: "Rewrites with better clarity."
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -415,7 +415,7 @@ class AIEnhancementService: ObservableObject {
|
|||||||
screenCaptureService.lastCapturedText = nil
|
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)
|
let newPrompt = CustomPrompt(title: title, promptText: promptText, icon: icon, description: description, isPredefined: false, triggerWords: triggerWords, useSystemInstructions: useSystemInstructions)
|
||||||
customPrompts.append(newPrompt)
|
customPrompts.append(newPrompt)
|
||||||
if customPrompts.count == 1 {
|
if customPrompts.count == 1 {
|
||||||
|
|||||||
@ -53,7 +53,7 @@ struct MenuBarView: View {
|
|||||||
enhancementService.setActivePrompt(prompt)
|
enhancementService.setActivePrompt(prompt)
|
||||||
} label: {
|
} label: {
|
||||||
HStack {
|
HStack {
|
||||||
Image(systemName: prompt.icon.rawValue)
|
Image(systemName: prompt.icon)
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.accentColor)
|
||||||
Text(prompt.title)
|
Text(prompt.title)
|
||||||
if enhancementService.selectedPromptId == prompt.id {
|
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 triggerWords: [String]
|
||||||
@State private var showingPredefinedPrompts = false
|
@State private var showingPredefinedPrompts = false
|
||||||
@State private var useSystemInstructions: Bool
|
@State private var useSystemInstructions: Bool
|
||||||
|
@State private var showingIconPicker = false
|
||||||
|
|
||||||
private var isEditingPredefinedPrompt: Bool {
|
private var isEditingPredefinedPrompt: Bool {
|
||||||
if case .edit(let prompt) = mode {
|
if case .edit(let prompt) = mode {
|
||||||
@ -41,7 +42,7 @@ struct PromptEditorView: View {
|
|||||||
case .add:
|
case .add:
|
||||||
_title = State(initialValue: "")
|
_title = State(initialValue: "")
|
||||||
_promptText = State(initialValue: "")
|
_promptText = State(initialValue: "")
|
||||||
_selectedIcon = State(initialValue: .documentFill)
|
_selectedIcon = State(initialValue: "doc.text.fill")
|
||||||
_description = State(initialValue: "")
|
_description = State(initialValue: "")
|
||||||
_triggerWords = State(initialValue: [])
|
_triggerWords = State(initialValue: [])
|
||||||
_useSystemInstructions = State(initialValue: true)
|
_useSystemInstructions = State(initialValue: true)
|
||||||
@ -132,29 +133,25 @@ struct PromptEditorView: View {
|
|||||||
.font(.headline)
|
.font(.headline)
|
||||||
.foregroundColor(.secondary)
|
.foregroundColor(.secondary)
|
||||||
|
|
||||||
Menu {
|
// Preview of selected icon - clickable to open popover (square button)
|
||||||
IconMenuContent(selectedIcon: $selectedIcon)
|
Button(action: {
|
||||||
} label: {
|
showingIconPicker = true
|
||||||
HStack {
|
}) {
|
||||||
Image(systemName: selectedIcon.rawValue)
|
Image(systemName: selectedIcon)
|
||||||
.font(.system(size: 16))
|
.font(.system(size: 20))
|
||||||
.foregroundColor(.accentColor)
|
.foregroundColor(.primary)
|
||||||
.frame(width: 24)
|
.frame(width: 48, height: 48)
|
||||||
|
.background(Color(NSColor.controlBackgroundColor))
|
||||||
Text(selectedIcon.title)
|
.cornerRadius(8)
|
||||||
.foregroundColor(.primary)
|
.overlay(
|
||||||
|
RoundedRectangle(cornerRadius: 8)
|
||||||
Spacer()
|
.stroke(Color.secondary.opacity(0.2), lineWidth: 1)
|
||||||
|
)
|
||||||
Image(systemName: "chevron.up.chevron.down")
|
|
||||||
.font(.system(size: 12))
|
|
||||||
.foregroundColor(.secondary)
|
|
||||||
}
|
|
||||||
.padding(8)
|
|
||||||
.background(Color(NSColor.controlBackgroundColor))
|
|
||||||
.cornerRadius(8)
|
|
||||||
}
|
}
|
||||||
.frame(width: 180)
|
.buttonStyle(.plain)
|
||||||
|
}
|
||||||
|
.popover(isPresented: $showingIconPicker, arrowEdge: .bottom) {
|
||||||
|
IconPickerPopover(selectedIcon: $selectedIcon, isPresented: $showingIconPicker)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
@ -218,36 +215,32 @@ struct PromptEditorView: View {
|
|||||||
.padding(.horizontal)
|
.padding(.horizontal)
|
||||||
|
|
||||||
if case .add = mode {
|
if case .add = mode {
|
||||||
// Templates Section with modern styling
|
// Popover keeps templates accessible without taking space in the layout
|
||||||
VStack(alignment: .leading, spacing: 16) {
|
Button("Start with a Predefined Template") {
|
||||||
Text("Start with a Predefined Template")
|
showingPredefinedPrompts.toggle()
|
||||||
.font(.title2)
|
}
|
||||||
.fontWeight(.semibold)
|
.font(.headline)
|
||||||
.foregroundColor(.primary)
|
.padding(.horizontal, 24)
|
||||||
|
.padding(.vertical, 12)
|
||||||
let columns = [
|
.background(
|
||||||
GridItem(.flexible(), spacing: 16),
|
Capsule()
|
||||||
GridItem(.flexible(), spacing: 16)
|
.fill(Color(.windowBackgroundColor).opacity(0.9))
|
||||||
]
|
)
|
||||||
|
.overlay(
|
||||||
LazyVGrid(columns: columns, spacing: 16) {
|
Capsule()
|
||||||
ForEach(PromptTemplates.all) { template in
|
.stroke(Color.secondary.opacity(0.2), lineWidth: 1)
|
||||||
CleanTemplateButton(prompt: template) {
|
)
|
||||||
title = template.title
|
.buttonStyle(.plain)
|
||||||
promptText = template.promptText
|
.padding(.horizontal)
|
||||||
selectedIcon = template.icon
|
.popover(isPresented: $showingPredefinedPrompts, arrowEdge: .bottom) {
|
||||||
description = template.description
|
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
|
// Reusable Trigger Words Editor Component
|
||||||
struct TriggerWordsEditor: View {
|
struct TriggerWordsEditor: View {
|
||||||
@Binding var triggerWords: [String]
|
@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 {
|
struct TriggerWordItemView: View {
|
||||||
let word: String
|
let word: String
|
||||||
@ -503,4 +374,48 @@ struct TriggerWordItemView: View {
|
|||||||
.stroke(Color.secondary.opacity(0.2), lineWidth: 1)
|
.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) {
|
Button(action: action) {
|
||||||
HStack(spacing: 8) {
|
HStack(spacing: 8) {
|
||||||
// Use the icon from the prompt
|
// Use the icon from the prompt
|
||||||
Image(systemName: prompt.icon.rawValue)
|
Image(systemName: prompt.icon)
|
||||||
.font(.system(size: 14))
|
.font(.system(size: 14))
|
||||||
.foregroundColor(isDisabled ? .white.opacity(0.4) : .white.opacity(0.7))
|
.foregroundColor(isDisabled ? .white.opacity(0.4) : .white.opacity(0.7))
|
||||||
|
|
||||||
|
|||||||
@ -167,7 +167,7 @@ struct RecorderPromptButton: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
RecorderToggleButton(
|
RecorderToggleButton(
|
||||||
isEnabled: enhancementService.isEnhancementEnabled,
|
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,
|
color: .blue,
|
||||||
disabled: false
|
disabled: false
|
||||||
) {
|
) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user