Refactor the custom prompt view code.
This commit is contained in:
parent
3b0c09553b
commit
7e2c5b4b16
@ -1,4 +1,5 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
enum PromptIcon: String, Codable, CaseIterable {
|
||||
// Document & Text
|
||||
@ -103,4 +104,248 @@ struct CustomPrompt: Identifiable, Codable, Equatable {
|
||||
self.isPredefined = isPredefined
|
||||
self.triggerWord = triggerWord
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
VStack(spacing: 8) {
|
||||
ZStack {
|
||||
// Dynamic background with blur effect
|
||||
RoundedRectangle(cornerRadius: 14)
|
||||
.fill(
|
||||
LinearGradient(
|
||||
gradient: isSelected ?
|
||||
Gradient(colors: [
|
||||
Color.accentColor.opacity(0.9),
|
||||
Color.accentColor.opacity(0.7)
|
||||
]) :
|
||||
Gradient(colors: [
|
||||
Color(NSColor.controlBackgroundColor).opacity(0.95),
|
||||
Color(NSColor.controlBackgroundColor).opacity(0.85)
|
||||
]),
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 14)
|
||||
.stroke(
|
||||
LinearGradient(
|
||||
gradient: Gradient(colors: [
|
||||
isSelected ?
|
||||
Color.white.opacity(0.3) : Color.white.opacity(0.15),
|
||||
isSelected ?
|
||||
Color.white.opacity(0.1) : Color.white.opacity(0.05)
|
||||
]),
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
),
|
||||
lineWidth: 1
|
||||
)
|
||||
)
|
||||
.shadow(
|
||||
color: isSelected ?
|
||||
Color.accentColor.opacity(0.4) : Color.black.opacity(0.1),
|
||||
radius: isSelected ? 10 : 6,
|
||||
x: 0,
|
||||
y: 3
|
||||
)
|
||||
|
||||
// Decorative background elements
|
||||
Circle()
|
||||
.fill(
|
||||
RadialGradient(
|
||||
gradient: Gradient(colors: [
|
||||
isSelected ?
|
||||
Color.white.opacity(0.15) : Color.white.opacity(0.08),
|
||||
Color.clear
|
||||
]),
|
||||
center: .center,
|
||||
startRadius: 1,
|
||||
endRadius: 25
|
||||
)
|
||||
)
|
||||
.frame(width: 50, height: 50)
|
||||
.offset(x: -15, y: -15)
|
||||
.blur(radius: 2)
|
||||
|
||||
// Icon with enhanced effects
|
||||
Image(systemName: icon.rawValue)
|
||||
.font(.system(size: 20, weight: .medium))
|
||||
.foregroundStyle(
|
||||
LinearGradient(
|
||||
colors: isSelected ?
|
||||
[Color.white, Color.white.opacity(0.9)] :
|
||||
[Color.primary.opacity(0.9), Color.primary.opacity(0.7)],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
)
|
||||
.shadow(
|
||||
color: isSelected ?
|
||||
Color.white.opacity(0.5) : Color.clear,
|
||||
radius: 4
|
||||
)
|
||||
.shadow(
|
||||
color: isSelected ?
|
||||
Color.accentColor.opacity(0.5) : Color.clear,
|
||||
radius: 3
|
||||
)
|
||||
}
|
||||
.frame(width: 48, height: 48)
|
||||
|
||||
// Enhanced title styling
|
||||
VStack(spacing: 2) {
|
||||
Text(title)
|
||||
.font(.system(size: 11, weight: .medium))
|
||||
.foregroundColor(isSelected ?
|
||||
.primary : .secondary)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: 70)
|
||||
|
||||
// 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
|
||||
HStack(spacing: 2) {
|
||||
Image(systemName: "mic.fill")
|
||||
.font(.system(size: 7))
|
||||
.foregroundColor(isSelected ? .accentColor.opacity(0.9) : .secondary.opacity(0.7))
|
||||
|
||||
Text("\"\(triggerWord)...\"")
|
||||
.font(.system(size: 8, weight: .regular))
|
||||
.foregroundColor(isSelected ? .primary.opacity(0.8) : .secondary.opacity(0.7))
|
||||
.lineLimit(1)
|
||||
}
|
||||
.frame(maxWidth: 70)
|
||||
}
|
||||
}
|
||||
.frame(height: 16) // Fixed height for all modes, with or without trigger words
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 4)
|
||||
.padding(.vertical, 6)
|
||||
.contentShape(Rectangle())
|
||||
.scaleEffect(isSelected ? 1.05 : 1.0)
|
||||
.onTapGesture(perform: onTap)
|
||||
.contextMenu {
|
||||
if !isPredefined && (onEdit != nil || onDelete != nil) {
|
||||
if let onEdit = onEdit {
|
||||
Button {
|
||||
onEdit(self)
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
}
|
||||
|
||||
if let onDelete = onDelete {
|
||||
Button(role: .destructive) {
|
||||
onDelete(self)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Static method to create an "Add New" button with the same styling as the prompt icons
|
||||
static func addNewButton(action: @escaping () -> Void) -> some View {
|
||||
VStack(spacing: 8) {
|
||||
ZStack {
|
||||
// Dynamic background with blur effect - same styling as promptIcon
|
||||
RoundedRectangle(cornerRadius: 14)
|
||||
.fill(
|
||||
LinearGradient(
|
||||
gradient: Gradient(colors: [
|
||||
Color(NSColor.controlBackgroundColor).opacity(0.95),
|
||||
Color(NSColor.controlBackgroundColor).opacity(0.85)
|
||||
]),
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 14)
|
||||
.stroke(
|
||||
LinearGradient(
|
||||
gradient: Gradient(colors: [
|
||||
Color.white.opacity(0.15),
|
||||
Color.white.opacity(0.05)
|
||||
]),
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
),
|
||||
lineWidth: 1
|
||||
)
|
||||
)
|
||||
.shadow(
|
||||
color: Color.black.opacity(0.1),
|
||||
radius: 6,
|
||||
x: 0,
|
||||
y: 3
|
||||
)
|
||||
|
||||
// Decorative background elements (same as in promptIcon)
|
||||
Circle()
|
||||
.fill(
|
||||
RadialGradient(
|
||||
gradient: Gradient(colors: [
|
||||
Color.white.opacity(0.08),
|
||||
Color.clear
|
||||
]),
|
||||
center: .center,
|
||||
startRadius: 1,
|
||||
endRadius: 25
|
||||
)
|
||||
)
|
||||
.frame(width: 50, height: 50)
|
||||
.offset(x: -15, y: -15)
|
||||
.blur(radius: 2)
|
||||
|
||||
// Plus icon with same styling as the normal icons
|
||||
Image(systemName: "plus.circle.fill")
|
||||
.font(.system(size: 20, weight: .medium))
|
||||
.foregroundStyle(
|
||||
LinearGradient(
|
||||
colors: [Color.accentColor.opacity(0.9), Color.accentColor.opacity(0.7)],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
)
|
||||
}
|
||||
.frame(width: 48, height: 48)
|
||||
|
||||
// Text label with matching styling
|
||||
VStack(spacing: 2) {
|
||||
Text("Add New")
|
||||
.font(.system(size: 11, weight: .medium))
|
||||
.foregroundColor(.secondary)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: 70)
|
||||
|
||||
// Empty space matching the trigger word area height
|
||||
Spacer()
|
||||
.frame(height: 16)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 4)
|
||||
.padding(.vertical, 6)
|
||||
.contentShape(Rectangle())
|
||||
.onTapGesture(perform: action)
|
||||
}
|
||||
}
|
||||
@ -557,26 +557,14 @@ struct ConfigurationView: View {
|
||||
}
|
||||
|
||||
|
||||
// Enhancement Modes Section (reused from EnhancementSettingsView)
|
||||
// Enhancement Prompts Section (reused from EnhancementSettingsView)
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack {
|
||||
Text("Enhancement Modes")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
Button(action: { isEditingPrompt = true }) {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.system(size: 26, weight: .medium))
|
||||
.foregroundStyle(Color.accentColor)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.contentShape(Circle())
|
||||
.help("Add new mode")
|
||||
}
|
||||
Text("Enhancement Prompts")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.primary)
|
||||
|
||||
if enhancementService.allPrompts.isEmpty {
|
||||
Text("No modes available")
|
||||
Text("No prompts available")
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
} else {
|
||||
@ -593,6 +581,12 @@ struct ConfigurationView: View {
|
||||
onDelete: { enhancementService.deletePrompt($0) }
|
||||
)
|
||||
}
|
||||
|
||||
// Plus icon using the same styling as prompt icons
|
||||
CustomPrompt.addNewButton {
|
||||
isEditingPrompt = true
|
||||
}
|
||||
.help("Add new prompt")
|
||||
}
|
||||
.padding(.vertical, 12)
|
||||
.padding(.horizontal, 16)
|
||||
|
||||
@ -1,162 +1,5 @@
|
||||
import SwiftUI
|
||||
|
||||
extension CustomPrompt {
|
||||
func promptIcon(isSelected: Bool, onTap: @escaping () -> Void, onEdit: ((CustomPrompt) -> Void)? = nil, onDelete: ((CustomPrompt) -> Void)? = nil, assistantTriggerWord: String? = nil) -> some View {
|
||||
VStack(spacing: 8) {
|
||||
ZStack {
|
||||
// Dynamic background with blur effect
|
||||
RoundedRectangle(cornerRadius: 14)
|
||||
.fill(
|
||||
LinearGradient(
|
||||
gradient: isSelected ?
|
||||
Gradient(colors: [
|
||||
Color.accentColor.opacity(0.9),
|
||||
Color.accentColor.opacity(0.7)
|
||||
]) :
|
||||
Gradient(colors: [
|
||||
Color(NSColor.controlBackgroundColor).opacity(0.95),
|
||||
Color(NSColor.controlBackgroundColor).opacity(0.85)
|
||||
]),
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 14)
|
||||
.stroke(
|
||||
LinearGradient(
|
||||
gradient: Gradient(colors: [
|
||||
isSelected ?
|
||||
Color.white.opacity(0.3) : Color.white.opacity(0.15),
|
||||
isSelected ?
|
||||
Color.white.opacity(0.1) : Color.white.opacity(0.05)
|
||||
]),
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
),
|
||||
lineWidth: 1
|
||||
)
|
||||
)
|
||||
.shadow(
|
||||
color: isSelected ?
|
||||
Color.accentColor.opacity(0.4) : Color.black.opacity(0.1),
|
||||
radius: isSelected ? 10 : 6,
|
||||
x: 0,
|
||||
y: 3
|
||||
)
|
||||
|
||||
// Decorative background elements
|
||||
Circle()
|
||||
.fill(
|
||||
RadialGradient(
|
||||
gradient: Gradient(colors: [
|
||||
isSelected ?
|
||||
Color.white.opacity(0.15) : Color.white.opacity(0.08),
|
||||
Color.clear
|
||||
]),
|
||||
center: .center,
|
||||
startRadius: 1,
|
||||
endRadius: 25
|
||||
)
|
||||
)
|
||||
.frame(width: 50, height: 50)
|
||||
.offset(x: -15, y: -15)
|
||||
.blur(radius: 2)
|
||||
|
||||
// Icon with enhanced effects
|
||||
Image(systemName: icon.rawValue)
|
||||
.font(.system(size: 20, weight: .medium))
|
||||
.foregroundStyle(
|
||||
LinearGradient(
|
||||
colors: isSelected ?
|
||||
[Color.white, Color.white.opacity(0.9)] :
|
||||
[Color.primary.opacity(0.9), Color.primary.opacity(0.7)],
|
||||
startPoint: .topLeading,
|
||||
endPoint: .bottomTrailing
|
||||
)
|
||||
)
|
||||
.shadow(
|
||||
color: isSelected ?
|
||||
Color.white.opacity(0.5) : Color.clear,
|
||||
radius: 4
|
||||
)
|
||||
.shadow(
|
||||
color: isSelected ?
|
||||
Color.accentColor.opacity(0.5) : Color.clear,
|
||||
radius: 3
|
||||
)
|
||||
}
|
||||
.frame(width: 48, height: 48)
|
||||
|
||||
// Enhanced title styling
|
||||
VStack(spacing: 2) {
|
||||
Text(title)
|
||||
.font(.system(size: 11, weight: .medium))
|
||||
.foregroundColor(isSelected ?
|
||||
.primary : .secondary)
|
||||
.lineLimit(1)
|
||||
.frame(maxWidth: 70)
|
||||
|
||||
// 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
|
||||
HStack(spacing: 2) {
|
||||
Image(systemName: "mic.fill")
|
||||
.font(.system(size: 7))
|
||||
.foregroundColor(isSelected ? .accentColor.opacity(0.9) : .secondary.opacity(0.7))
|
||||
|
||||
Text("\"\(triggerWord)...\"")
|
||||
.font(.system(size: 8, weight: .regular))
|
||||
.foregroundColor(isSelected ? .primary.opacity(0.8) : .secondary.opacity(0.7))
|
||||
.lineLimit(1)
|
||||
}
|
||||
.frame(maxWidth: 70)
|
||||
}
|
||||
}
|
||||
.frame(height: 16) // Fixed height for all modes, with or without trigger words
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 4)
|
||||
.padding(.vertical, 6)
|
||||
.contentShape(Rectangle())
|
||||
.scaleEffect(isSelected ? 1.05 : 1.0)
|
||||
.onTapGesture(perform: onTap)
|
||||
.contextMenu {
|
||||
if !isPredefined && (onEdit != nil || onDelete != nil) {
|
||||
if let onEdit = onEdit {
|
||||
Button {
|
||||
onEdit(self)
|
||||
} label: {
|
||||
Label("Edit", systemImage: "pencil")
|
||||
}
|
||||
}
|
||||
|
||||
if let onDelete = onDelete {
|
||||
Button(role: .destructive) {
|
||||
onDelete(self)
|
||||
} label: {
|
||||
Label("Delete", systemImage: "trash")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct EnhancementSettingsView: View {
|
||||
@EnvironmentObject private var enhancementService: AIEnhancementService
|
||||
@State private var isEditingPrompt = false
|
||||
@ -243,29 +86,13 @@ struct EnhancementSettingsView: View {
|
||||
|
||||
// 3. Enhancement Modes & Assistant Section
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
Text("Enhancement Modes & Assistant")
|
||||
Text("Enhancement Prompt")
|
||||
.font(.headline)
|
||||
|
||||
// Modes Section
|
||||
// Prompts Section
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack {
|
||||
Text("Enhancement Modes")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.primary)
|
||||
Spacer()
|
||||
Button(action: { isEditingPrompt = true }) {
|
||||
Image(systemName: "plus.circle.fill")
|
||||
.symbolRenderingMode(.hierarchical)
|
||||
.font(.system(size: 26, weight: .medium))
|
||||
.foregroundStyle(Color.accentColor)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.contentShape(Circle())
|
||||
.help("Add new mode")
|
||||
}
|
||||
|
||||
if enhancementService.allPrompts.isEmpty {
|
||||
Text("No modes available")
|
||||
Text("No prompts available")
|
||||
.foregroundColor(.secondary)
|
||||
.font(.caption)
|
||||
} else {
|
||||
@ -285,6 +112,12 @@ struct EnhancementSettingsView: View {
|
||||
assistantTriggerWord: enhancementService.assistantTriggerWord
|
||||
)
|
||||
}
|
||||
|
||||
// Plus icon using the same styling as prompt icons
|
||||
CustomPrompt.addNewButton {
|
||||
isEditingPrompt = true
|
||||
}
|
||||
.help("Add new prompt")
|
||||
}
|
||||
.padding(.vertical, 12)
|
||||
.padding(.horizontal, 16)
|
||||
@ -296,7 +129,7 @@ struct EnhancementSettingsView: View {
|
||||
// Assistant Mode Section
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
HStack {
|
||||
Text("Assistant Mode")
|
||||
Text("Assistant Prompt")
|
||||
.font(.subheadline)
|
||||
Image(systemName: "sparkles")
|
||||
.foregroundColor(.accentColor)
|
||||
|
||||
@ -49,7 +49,7 @@ struct PromptEditorView: View {
|
||||
VStack(spacing: 0) {
|
||||
// Header with modern styling
|
||||
HStack {
|
||||
Text(mode == .add ? "New Mode" : "Edit Mode")
|
||||
Text(mode == .add ? "New Prompt" : "Edit Prompt")
|
||||
.font(.title2)
|
||||
.fontWeight(.bold)
|
||||
Spacer()
|
||||
@ -133,7 +133,7 @@ struct PromptEditorView: View {
|
||||
.font(.headline)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Text("Add a brief description of what this mode does")
|
||||
Text("Add a brief description of what this prompt does")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
@ -149,7 +149,7 @@ struct PromptEditorView: View {
|
||||
.font(.headline)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Text("Add a custom word to activate this mode by voice (optional)")
|
||||
Text("Add a custom word to activate this prompt by voice (optional)")
|
||||
.font(.subheadline)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
@ -161,7 +161,7 @@ struct PromptEditorView: View {
|
||||
|
||||
// Prompt Text Section with improved styling
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text("Mode Instructions")
|
||||
Text("Prompt Instructions")
|
||||
.font(.headline)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user