Removed separate handling of AI assistant trigger word

This commit is contained in:
Beingpax 2025-05-29 11:27:26 +05:45
parent 7781ecc19b
commit 8a1870934d
8 changed files with 281 additions and 306 deletions

View File

@ -108,7 +108,7 @@ struct CustomPrompt: Identifiable, Codable, Equatable {
// MARK: - UI Extensions
extension CustomPrompt {
func promptIcon(isSelected: Bool, onTap: @escaping () -> Void, onEdit: ((CustomPrompt) -> Void)? = nil, onDelete: ((CustomPrompt) -> Void)? = nil, assistantTriggerWord: String? = nil) -> some View {
func promptIcon(isSelected: Bool, onTap: @escaping () -> Void, onEdit: ((CustomPrompt) -> Void)? = nil, onDelete: ((CustomPrompt) -> Void)? = nil) -> some View {
VStack(spacing: 8) {
ZStack {
// Dynamic background with blur effect
@ -206,21 +206,7 @@ extension CustomPrompt {
// Trigger word section with consistent height
ZStack(alignment: .center) {
if id == PredefinedPrompts.assistantPromptId, let assistantTriggerWord = assistantTriggerWord, !assistantTriggerWord.isEmpty {
// Show the global assistant trigger word for the Assistant Mode
HStack(spacing: 2) {
Image(systemName: "mic.fill")
.font(.system(size: 7))
.foregroundColor(isSelected ? .accentColor.opacity(0.9) : .secondary.opacity(0.7))
Text("\"\(assistantTriggerWord)...\"")
.font(.system(size: 8, weight: .regular))
.foregroundColor(isSelected ? .primary.opacity(0.8) : .secondary.opacity(0.7))
.lineLimit(1)
}
.frame(maxWidth: 70)
} else if let triggerWord = triggerWord, !triggerWord.isEmpty {
// Show custom trigger words for Enhancement Modes
if let triggerWord = triggerWord, !triggerWord.isEmpty {
HStack(spacing: 2) {
Image(systemName: "mic.fill")
.font(.system(size: 7))
@ -234,7 +220,7 @@ extension CustomPrompt {
.frame(maxWidth: 70)
}
}
.frame(height: 16) // Fixed height for all modes, with or without trigger words
.frame(height: 16)
}
}
.padding(.horizontal, 4)
@ -243,7 +229,7 @@ extension CustomPrompt {
.scaleEffect(isSelected ? 1.05 : 1.0)
.onTapGesture(perform: onTap)
.contextMenu {
if !isPredefined && (onEdit != nil || onDelete != nil) {
if onEdit != nil || onDelete != nil {
if let onEdit = onEdit {
Button {
onEdit(self)
@ -252,7 +238,7 @@ extension CustomPrompt {
}
}
if let onDelete = onDelete {
if let onDelete = onDelete, !isPredefined {
Button(role: .destructive) {
onDelete(self)
} label: {

View File

@ -5,7 +5,7 @@ enum PredefinedPrompts {
private static let predefinedPromptsKey = "PredefinedPrompts"
// Static UUIDs for predefined prompts
private static let defaultPromptId = UUID(uuidString: "00000000-0000-0000-0000-000000000001")!
static let defaultPromptId = UUID(uuidString: "00000000-0000-0000-0000-000000000001")!
static let assistantPromptId = UUID(uuidString: "00000000-0000-0000-0000-000000000002")!
static var all: [CustomPrompt] {

View File

@ -564,20 +564,20 @@ struct ConfigurationView: View {
.foregroundColor(.primary)
PromptSelectionGrid(
prompts: enhancementService.allPrompts,
selectedPromptId: selectedPromptId,
onPromptTap: { prompt in
onPromptSelected: { prompt in
selectedPromptId = prompt.id
},
onPromptEdit: { prompt in
onEditPrompt: { prompt in
selectedPromptForEdit = prompt
},
onPromptDelete: { prompt in
onDeletePrompt: { prompt in
enhancementService.deletePrompt(prompt)
},
onAddNew: {
onAddNewPrompt: {
isEditingPrompt = true
},
assistantTriggerWord: enhancementService.assistantTriggerWord
}
)
}

View File

@ -42,15 +42,9 @@ class AIEnhancementService: ObservableObject {
}
}
@Published var assistantTriggerWord: String {
didSet {
UserDefaults.standard.set(assistantTriggerWord, forKey: "assistantTriggerWord")
}
}
@Published var customPrompts: [CustomPrompt] {
didSet {
if let encoded = try? JSONEncoder().encode(customPrompts.filter { !$0.isPredefined }) {
if let encoded = try? JSONEncoder().encode(customPrompts) {
UserDefaults.standard.set(encoded, forKey: "customPrompts")
}
}
@ -67,7 +61,7 @@ class AIEnhancementService: ObservableObject {
}
var allPrompts: [CustomPrompt] {
PredefinedPrompts.createDefaultPrompts() + customPrompts.filter { !$0.isPredefined }
return customPrompts
}
private let aiService: AIService
@ -87,7 +81,6 @@ class AIEnhancementService: ObservableObject {
self.isEnhancementEnabled = UserDefaults.standard.bool(forKey: "isAIEnhancementEnabled")
self.useClipboardContext = UserDefaults.standard.bool(forKey: "useClipboardContext")
self.useScreenCaptureContext = UserDefaults.standard.bool(forKey: "useScreenCaptureContext")
self.assistantTriggerWord = UserDefaults.standard.string(forKey: "assistantTriggerWord") ?? "hey"
if let savedPromptsData = UserDefaults.standard.data(forKey: "customPrompts"),
let decodedPrompts = try? JSONDecoder().decode([CustomPrompt].self, from: savedPromptsData) {
@ -110,6 +103,8 @@ class AIEnhancementService: ObservableObject {
name: .aiProviderKeyChanged,
object: nil
)
initializePredefinedPrompts()
}
deinit {
@ -166,20 +161,17 @@ class AIEnhancementService: ObservableObject {
""
}
switch mode {
case .transcriptionEnhancement:
if let activePrompt = activePrompt,
activePrompt.id == PredefinedPrompts.assistantPromptId {
return AIPrompts.assistantMode + contextSection
}
var systemMessage = String(format: AIPrompts.customPromptTemplate, activePrompt!.promptText)
systemMessage += contextSection
return systemMessage
case .aiAssistant:
guard let activePrompt = activePrompt else {
return AIPrompts.assistantMode + contextSection
}
if activePrompt.id == PredefinedPrompts.assistantPromptId {
return activePrompt.promptText + contextSection
}
var systemMessage = String(format: AIPrompts.customPromptTemplate, activePrompt.promptText)
systemMessage += contextSection
return systemMessage
}
private func makeRequest(text: String, mode: EnhancementPrompt, retryCount: Int = 0) async throws -> String {
@ -418,12 +410,7 @@ class AIEnhancementService: ObservableObject {
func enhance(_ text: String) async throws -> String {
logger.notice("🚀 Starting AI enhancement for text (\(text.count) characters)")
let enhancementPrompt: EnhancementPrompt = {
if let activePrompt = activePrompt, activePrompt.id == PredefinedPrompts.assistantPromptId {
return .aiAssistant
}
return .transcriptionEnhancement
}()
let enhancementPrompt: EnhancementPrompt = .transcriptionEnhancement
var retryCount = 0
while retryCount < maxRetries {
@ -477,16 +464,12 @@ class AIEnhancementService: ObservableObject {
}
func updatePrompt(_ prompt: CustomPrompt) {
if prompt.isPredefined { return }
if let index = customPrompts.firstIndex(where: { $0.id == prompt.id }) {
customPrompts[index] = prompt
}
}
func deletePrompt(_ prompt: CustomPrompt) {
if prompt.isPredefined { return }
customPrompts.removeAll { $0.id == prompt.id }
if selectedPromptId == prompt.id {
selectedPromptId = allPrompts.first?.id
@ -511,6 +494,31 @@ class AIEnhancementService: ObservableObject {
private func getRetryDelay(for retryCount: Int) -> TimeInterval {
return retryCount == 1 ? 1.0 : 2.0
}
private func initializePredefinedPrompts() {
let predefinedTemplates = PredefinedPrompts.createDefaultPrompts()
for template in predefinedTemplates {
if let existingIndex = customPrompts.firstIndex(where: { $0.id == template.id }) {
// Update existing predefined prompt: only update prompt text, preserve trigger word
var updatedPrompt = customPrompts[existingIndex]
updatedPrompt = CustomPrompt(
id: updatedPrompt.id,
title: template.title,
promptText: template.promptText, // Update from template
isActive: updatedPrompt.isActive,
icon: template.icon,
description: template.description,
isPredefined: true,
triggerWord: updatedPrompt.triggerWord // Preserve user's trigger word
)
customPrompts[existingIndex] = updatedPrompt
} else {
// Add new predefined prompt (no default trigger word)
customPrompts.append(template)
}
}
}
}
enum EnhancementError: Error {

View File

@ -20,21 +20,10 @@ class PromptDetectionService {
let originalEnhancementState = enhancementService.isEnhancementEnabled
let originalPromptId = enhancementService.selectedPromptId
if let result = checkAssistantTrigger(text: text, triggerWord: enhancementService.assistantTriggerWord) {
return PromptDetectionResult(
shouldEnableAI: true,
selectedPromptId: PredefinedPrompts.assistantPromptId,
processedText: result,
detectedTriggerWord: enhancementService.assistantTriggerWord,
originalEnhancementState: originalEnhancementState,
originalPromptId: originalPromptId
)
}
for prompt in enhancementService.allPrompts {
if let triggerWord = prompt.triggerWord?.trimmingCharacters(in: .whitespacesAndNewlines),
!triggerWord.isEmpty,
let result = checkCustomTrigger(text: text, triggerWord: triggerWord) {
let result = removeTriggerWord(from: text, triggerWord: triggerWord) {
return PromptDetectionResult(
shouldEnableAI: true,
@ -87,14 +76,6 @@ class PromptDetectionService {
}
}
private func checkAssistantTrigger(text: String, triggerWord: String) -> String? {
return removeTriggerWord(from: text, triggerWord: triggerWord)
}
private func checkCustomTrigger(text: String, triggerWord: String) -> String? {
return removeTriggerWord(from: text, triggerWord: triggerWord)
}
private func removeTriggerWord(from text: String, triggerWord: String) -> String? {
let trimmedText = text.trimmingCharacters(in: .whitespacesAndNewlines)
let lowerText = trimmedText.lowercased()

View File

@ -4,32 +4,63 @@ import SwiftUI
struct PromptSelectionGrid: View {
@EnvironmentObject private var enhancementService: AIEnhancementService
let prompts: [CustomPrompt]
let selectedPromptId: UUID?
let onPromptTap: (CustomPrompt) -> Void
let onPromptEdit: (CustomPrompt) -> Void
let onPromptDelete: (CustomPrompt) -> Void
let onAddNew: () -> Void
let assistantTriggerWord: String?
let onPromptSelected: (CustomPrompt) -> Void
let onEditPrompt: ((CustomPrompt) -> Void)?
let onDeletePrompt: ((CustomPrompt) -> Void)?
let onAddNewPrompt: (() -> Void)?
init(
prompts: [CustomPrompt],
selectedPromptId: UUID?,
onPromptTap: @escaping (CustomPrompt) -> Void,
onPromptEdit: @escaping (CustomPrompt) -> Void = { _ in },
onPromptDelete: @escaping (CustomPrompt) -> Void = { _ in },
onAddNew: @escaping () -> Void,
assistantTriggerWord: String? = nil
onPromptSelected: @escaping (CustomPrompt) -> Void,
onEditPrompt: ((CustomPrompt) -> Void)? = nil,
onDeletePrompt: ((CustomPrompt) -> Void)? = nil,
onAddNewPrompt: (() -> Void)? = nil
) {
self.prompts = prompts
self.selectedPromptId = selectedPromptId
self.onPromptTap = onPromptTap
self.onPromptEdit = onPromptEdit
self.onPromptDelete = onPromptDelete
self.onAddNew = onAddNew
self.assistantTriggerWord = assistantTriggerWord
self.onPromptSelected = onPromptSelected
self.onEditPrompt = onEditPrompt
self.onDeletePrompt = onDeletePrompt
self.onAddNewPrompt = onAddNewPrompt
}
private var sortedPrompts: [CustomPrompt] {
prompts.sorted { prompt1, prompt2 in
// Predefined prompts come first
if prompt1.isPredefined && !prompt2.isPredefined {
return true
}
if !prompt1.isPredefined && prompt2.isPredefined {
return false
}
// Among predefined prompts: Default first, then Assistant
if prompt1.isPredefined && prompt2.isPredefined {
if prompt1.id == PredefinedPrompts.defaultPromptId {
return true
}
if prompt2.id == PredefinedPrompts.defaultPromptId {
return false
}
if prompt1.id == PredefinedPrompts.assistantPromptId {
return true
}
if prompt2.id == PredefinedPrompts.assistantPromptId {
return false
}
}
// Custom prompts: sort alphabetically by title
return prompt1.title.localizedCaseInsensitiveCompare(prompt2.title) == .orderedAscending
}
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
if enhancementService.allPrompts.isEmpty {
if sortedPrompts.isEmpty {
Text("No prompts available")
.foregroundColor(.secondary)
.font(.caption)
@ -38,29 +69,42 @@ struct PromptSelectionGrid: View {
GridItem(.adaptive(minimum: 80, maximum: 100), spacing: 36)
]
LazyVGrid(columns: columns, spacing: 24) {
ForEach(enhancementService.allPrompts) { prompt in
LazyVGrid(columns: columns, spacing: 16) {
ForEach(sortedPrompts) { prompt in
prompt.promptIcon(
isSelected: selectedPromptId == prompt.id,
onTap: {
withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
onPromptTap(prompt)
onPromptSelected(prompt)
}
},
onEdit: onPromptEdit,
onDelete: onPromptDelete,
assistantTriggerWord: assistantTriggerWord
onEdit: onEditPrompt,
onDelete: onDeletePrompt
)
}
// Plus icon using the same styling as prompt icons
CustomPrompt.addNewButton {
onAddNew()
if let onAddNewPrompt = onAddNewPrompt {
CustomPrompt.addNewButton {
onAddNewPrompt()
}
.help("Add new prompt")
}
.help("Add new prompt")
}
.padding(.vertical, 12)
.padding(.horizontal, 16)
// Helpful tip for users
HStack {
Image(systemName: "info.circle")
.font(.caption)
.foregroundColor(.secondary)
Text("Right-click on prompts to edit or delete")
.font(.caption)
.foregroundColor(.secondary)
}
.padding(.top, 8)
.padding(.horizontal, 16)
}
}
}

View File

@ -5,8 +5,6 @@ struct EnhancementSettingsView: View {
@State private var isEditingPrompt = false
@State private var isSettingsExpanded = true
@State private var selectedPromptForEdit: CustomPrompt?
@State private var isEditingTriggerWord = false
@State private var tempTriggerWord = ""
var body: some View {
ScrollView {
@ -92,88 +90,22 @@ struct EnhancementSettingsView: View {
// Prompts Section
VStack(alignment: .leading, spacing: 12) {
PromptSelectionGrid(
prompts: enhancementService.allPrompts,
selectedPromptId: enhancementService.selectedPromptId,
onPromptTap: { prompt in
onPromptSelected: { prompt in
enhancementService.setActivePrompt(prompt)
},
onPromptEdit: { prompt in
onEditPrompt: { prompt in
selectedPromptForEdit = prompt
},
onPromptDelete: { prompt in
onDeletePrompt: { prompt in
enhancementService.deletePrompt(prompt)
},
onAddNew: {
onAddNewPrompt: {
isEditingPrompt = true
},
assistantTriggerWord: enhancementService.assistantTriggerWord
}
)
}
Divider()
// Assistant Mode Section
VStack(alignment: .leading, spacing: 12) {
HStack {
Text("Assistant Prompt")
.font(.subheadline)
Image(systemName: "sparkles")
.foregroundColor(.accentColor)
}
Text("Configure how to trigger the AI assistant mode")
.font(.caption)
.foregroundColor(.secondary)
VStack(alignment: .leading, spacing: 12) {
HStack {
Text("Current Trigger:")
.font(.subheadline)
Text("\"\(enhancementService.assistantTriggerWord)\"")
.font(.system(.subheadline, design: .monospaced))
.foregroundColor(.accentColor)
}
if isEditingTriggerWord {
VStack(alignment: .leading, spacing: 8) {
HStack {
TextField("New trigger word", text: $tempTriggerWord)
.textFieldStyle(.roundedBorder)
.frame(maxWidth: 200)
Button("Save") {
enhancementService.assistantTriggerWord = tempTriggerWord
isEditingTriggerWord = false
}
.buttonStyle(.borderedProminent)
.disabled(tempTriggerWord.isEmpty)
Button("Cancel") {
isEditingTriggerWord = false
tempTriggerWord = enhancementService.assistantTriggerWord
}
.buttonStyle(.bordered)
}
Text("Default: \"hey\"")
.font(.caption)
.foregroundColor(.secondary)
}
} else {
Button("Change Trigger Word") {
tempTriggerWord = enhancementService.assistantTriggerWord
isEditingTriggerWord = true
}
.buttonStyle(.bordered)
}
}
Text("Start with \"\(enhancementService.assistantTriggerWord), \" to use AI assistant mode")
.font(.caption)
.foregroundColor(.secondary)
Text("Instead of enhancing the text, VoiceInk will respond like a conversational AI assistant")
.font(.caption)
.foregroundColor(.secondary)
}
}
.padding()
.background(Color(.windowBackgroundColor).opacity(0.4))

View File

@ -27,6 +27,13 @@ struct PromptEditorView: View {
@State private var triggerWord: String
@State private var showingPredefinedPrompts = false
private var isEditingPredefinedPrompt: Bool {
if case .edit(let prompt) = mode {
return prompt.isPredefined
}
return false
}
init(mode: Mode) {
self.mode = mode
switch mode {
@ -49,7 +56,7 @@ struct PromptEditorView: View {
VStack(spacing: 0) {
// Header with modern styling
HStack {
Text(mode == .add ? "New Prompt" : "Edit Prompt")
Text(isEditingPredefinedPrompt ? "Edit Trigger Word" : (mode == .add ? "New Prompt" : "Edit Prompt"))
.font(.title2)
.fontWeight(.bold)
Spacer()
@ -68,7 +75,7 @@ struct PromptEditorView: View {
.fontWeight(.medium)
}
.buttonStyle(.borderedProminent)
.disabled(title.isEmpty || promptText.isEmpty)
.disabled(isEditingPredefinedPrompt ? false : (title.isEmpty || promptText.isEmpty))
.keyboardShortcut(.return, modifiers: .command)
}
}
@ -80,141 +87,157 @@ struct PromptEditorView: View {
ScrollView {
VStack(spacing: 24) {
// Title and Icon Section with improved layout
HStack(spacing: 20) {
// Title Field
VStack(alignment: .leading, spacing: 8) {
Text("Title")
.font(.headline)
.foregroundColor(.secondary)
TextField("Enter a short, descriptive title", text: $title)
.textFieldStyle(.roundedBorder)
.font(.body)
}
.frame(maxWidth: .infinity)
// Icon Selector with preview
VStack(alignment: .leading, spacing: 8) {
Text("Icon")
.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)
}
.frame(width: 180)
}
}
.padding(.horizontal)
.padding(.top, 8)
// Description Field
VStack(alignment: .leading, spacing: 8) {
Text("Description")
.font(.headline)
.foregroundColor(.secondary)
Text("Add a brief description of what this prompt does")
.font(.subheadline)
.foregroundColor(.secondary)
TextField("Enter a description", text: $description)
.textFieldStyle(.roundedBorder)
.font(.body)
}
.padding(.horizontal)
// Trigger Word Field
VStack(alignment: .leading, spacing: 8) {
Text("Trigger Word")
.font(.headline)
.foregroundColor(.secondary)
Text("Add a custom word to activate this prompt by voice (optional)")
.font(.subheadline)
.foregroundColor(.secondary)
TextField("Enter a trigger word", text: $triggerWord)
.textFieldStyle(.roundedBorder)
.font(.body)
}
.padding(.horizontal)
// Prompt Text Section with improved styling
VStack(alignment: .leading, spacing: 8) {
Text("Prompt Instructions")
.font(.headline)
.foregroundColor(.secondary)
Text("Define how AI should enhance your transcriptions")
.font(.subheadline)
.foregroundColor(.secondary)
TextEditor(text: $promptText)
.font(.system(.body, design: .monospaced))
.frame(minHeight: 200)
.padding(12)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color(NSColor.textBackgroundColor))
)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.secondary.opacity(0.2), lineWidth: 1)
)
}
.padding(.horizontal)
if case .add = mode {
// Templates Section with modern styling
if isEditingPredefinedPrompt {
// Simplified view for predefined prompts - only trigger word editing
VStack(alignment: .leading, spacing: 16) {
Text("Start with a Predefined Template")
Text("Editing: \(title)")
.font(.title2)
.fontWeight(.semibold)
.foregroundColor(.primary)
.padding(.horizontal)
.padding(.top, 8)
let columns = [
GridItem(.flexible(), spacing: 16),
GridItem(.flexible(), spacing: 16)
]
Text("You can only customize the trigger word for system prompts.")
.font(.subheadline)
.foregroundColor(.secondary)
.padding(.horizontal)
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
// Trigger Word Field with same styling as custom prompts
VStack(alignment: .leading, spacing: 8) {
Text("Trigger Word")
.font(.headline)
.foregroundColor(.secondary)
TextField("Enter a trigger word (optional)", text: $triggerWord)
.textFieldStyle(.roundedBorder)
.font(.body)
}
.padding(.horizontal)
}
.padding(.vertical, 20)
} else {
// Full editing interface for custom prompts
// Title and Icon Section with improved layout
HStack(spacing: 20) {
// Title Field
VStack(alignment: .leading, spacing: 8) {
Text("Title")
.font(.headline)
.foregroundColor(.secondary)
TextField("Enter a short, descriptive title", text: $title)
.textFieldStyle(.roundedBorder)
.font(.body)
}
.frame(maxWidth: .infinity)
// Icon Selector with preview
VStack(alignment: .leading, spacing: 8) {
Text("Icon")
.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)
}
.frame(width: 180)
}
}
.padding(.horizontal)
.padding(.vertical, 16)
.background(
RoundedRectangle(cornerRadius: 16)
.fill(Color(.windowBackgroundColor).opacity(0.6))
)
.padding(.top, 8)
// Description Field
VStack(alignment: .leading, spacing: 8) {
Text("Description")
.font(.headline)
.foregroundColor(.secondary)
Text("Add a brief description of what this prompt does")
.font(.subheadline)
.foregroundColor(.secondary)
TextField("Enter a description", text: $description)
.textFieldStyle(.roundedBorder)
.font(.body)
}
.padding(.horizontal)
// Prompt Text Section with improved styling
VStack(alignment: .leading, spacing: 8) {
Text("Prompt Instructions")
.font(.headline)
.foregroundColor(.secondary)
Text("Define how AI should enhance your transcriptions")
.font(.subheadline)
.foregroundColor(.secondary)
TextEditor(text: $promptText)
.font(.system(.body, design: .monospaced))
.frame(minHeight: 200)
.padding(12)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(Color(NSColor.textBackgroundColor))
)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.secondary.opacity(0.2), lineWidth: 1)
)
}
.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
}
}
}
}
.padding(.horizontal)
.padding(.vertical, 16)
.background(
RoundedRectangle(cornerRadius: 16)
.fill(Color(.windowBackgroundColor).opacity(0.6))
)
.padding(.horizontal)
}
}
}
.padding(.vertical, 20)
@ -236,11 +259,12 @@ struct PromptEditorView: View {
case .edit(let prompt):
let updatedPrompt = CustomPrompt(
id: prompt.id,
title: title,
promptText: promptText,
title: prompt.isPredefined ? prompt.title : title,
promptText: prompt.isPredefined ? prompt.promptText : promptText,
isActive: prompt.isActive,
icon: selectedIcon,
description: description.isEmpty ? nil : description,
icon: prompt.isPredefined ? prompt.icon : selectedIcon,
description: prompt.isPredefined ? prompt.description : (description.isEmpty ? nil : description),
isPredefined: prompt.isPredefined,
triggerWord: triggerWord.isEmpty ? nil : triggerWord
)
enhancementService.updatePrompt(updatedPrompt)