Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions Recap/UseCases/Settings/Components/Reusable/CustomTextEditor.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import SwiftUI

struct CustomTextEditor: View {
let title: String
@Binding var text: String
let placeholder: String
let height: CGFloat

@State private var isEditing = false
@FocusState private var isFocused: Bool

init(
title: String,
text: Binding<String>,
placeholder: String = "",
height: CGFloat = 100
) {
self.title = title
self._text = text
self.placeholder = placeholder
self.height = height
}

var body: some View {
VStack(alignment: .leading, spacing: 6) {
Text(title)
.font(.system(size: 11, weight: .medium))
.foregroundColor(UIConstants.Colors.textSecondary)

ZStack(alignment: .topLeading) {
RoundedRectangle(cornerRadius: 8)
.fill(Color(hex: "2A2A2A").opacity(0.3))
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(
LinearGradient(
gradient: Gradient(stops: [
.init(color: Color(hex: "979797").opacity(isFocused ? 0.4 : 0.2), location: 0),
.init(color: Color(hex: "979797").opacity(isFocused ? 0.3 : 0.1), location: 1)
]),
startPoint: .top,
endPoint: .bottom
),
lineWidth: 0.8
)
)
.frame(height: height)

if text.isEmpty && !isFocused {
Text(placeholder)
.font(.system(size: 12, weight: .medium))
.foregroundColor(UIConstants.Colors.textSecondary.opacity(0.6))
.padding(.horizontal, 12)
.padding(.vertical, 8)
.allowsHitTesting(false)
}

TextEditor(text: $text)
.font(.system(size: 12, weight: .medium))
.foregroundColor(UIConstants.Colors.textPrimary)
.background(Color.clear)
.scrollContentBackground(.hidden)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.focused($isFocused)
.onChange(of: isFocused) { _, focused in
withAnimation(.easeInOut(duration: 0.2)) {
isEditing = focused
}
}
}
}
}
}

#Preview {
VStack(spacing: 20) {
CustomTextEditor(
title: "Custom Prompt",
text: .constant(""),
placeholder: "Enter your custom prompt template here...",
height: 120
)

CustomTextEditor(
title: "With Content",
text: .constant(UserPreferencesInfo.defaultPromptTemplate),
placeholder: "Enter text...",
height: 80
)
}
.frame(width: 400, height: 300)
.padding(20)
.background(Color.black)
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,41 @@ struct GeneralSettingsView<ViewModel: GeneralSettingsViewModelType>: View {
}
}

SettingsCard(title: "Custom Prompt") {
VStack(alignment: .leading, spacing: 12) {
CustomTextEditor(
title: "Prompt Template",
text: Binding(
get: { viewModel.customPromptTemplate },
set: { newTemplate in
viewModel.customPromptTemplate = newTemplate
Comment thread
dennisimoo marked this conversation as resolved.
Outdated
Comment thread
dennisimoo marked this conversation as resolved.
Outdated
Task {
await viewModel.updateCustomPromptTemplate(newTemplate)
}
}
),
placeholder: "Enter your custom prompt template here...",
height: 120
)

HStack {
Text("Customize how AI summarizes your meeting transcripts")
.font(.system(size: 11, weight: .regular))
.foregroundColor(UIConstants.Colors.textSecondary)

Spacer()

Button("Reset to Default") {
Task {
await viewModel.resetToDefaultPrompt()
}
}
.font(.system(size: 11, weight: .medium))
.foregroundColor(Color.blue)
}
}
}

}
.padding(.horizontal, 20)
.padding(.vertical, 20)
Expand Down Expand Up @@ -127,6 +162,7 @@ struct GeneralSettingsView<ViewModel: GeneralSettingsViewModelType>: View {
}

private final class PreviewGeneralSettingsViewModel: ObservableObject, GeneralSettingsViewModelType {
@Published var customPromptTemplate: String = UserPreferencesInfo.defaultPromptTemplate
@Published var availableModels: [LLMModelInfo] = [
LLMModelInfo(name: "llama3.2", provider: "ollama"),
LLMModelInfo(name: "codellama", provider: "ollama")
Expand Down Expand Up @@ -170,4 +206,12 @@ private final class PreviewGeneralSettingsViewModel: ObservableObject, GeneralSe
func toggleAutoStopRecording(_ enabled: Bool) async {
isAutoStopRecording = enabled
}

func updateCustomPromptTemplate(_ template: String) async {
customPromptTemplate = template
}

func resetToDefaultPrompt() async {
customPromptTemplate = UserPreferencesInfo.defaultPromptTemplate
}
}
9 changes: 9 additions & 0 deletions Recap/UseCases/Settings/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ struct SettingsView<GeneralViewModel: GeneralSettingsViewModelType>: View {

private final class PreviewGeneralSettingsViewModel: ObservableObject, GeneralSettingsViewModelType {
var activeWarnings: [WarningItem] = []
@Published var customPromptTemplate: String = UserPreferencesInfo.defaultPromptTemplate

@Published var availableModels: [LLMModelInfo] = [
LLMModelInfo(name: "llama3.2", provider: "ollama"),
Expand Down Expand Up @@ -187,4 +188,12 @@ private final class PreviewGeneralSettingsViewModel: ObservableObject, GeneralSe
func toggleAutoStopRecording(_ enabled: Bool) async {
isAutoStopRecording = enabled
}

func updateCustomPromptTemplate(_ template: String) async {
customPromptTemplate = template
}

func resetToDefaultPrompt() async {
customPromptTemplate = UserPreferencesInfo.defaultPromptTemplate
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ final class GeneralSettingsViewModel: ObservableObject, GeneralSettingsViewModel
@Published private(set) var selectedProvider: LLMProvider = .default
@Published private(set) var autoDetectMeetings: Bool = false
@Published private(set) var isAutoStopRecording: Bool = false
@Published var customPromptTemplate: String = ""
Comment thread
dennisimoo marked this conversation as resolved.
Outdated
@Published private(set) var isLoading = false
@Published private(set) var errorMessage: String?
@Published private(set) var showToast = false
Expand Down Expand Up @@ -58,10 +59,12 @@ final class GeneralSettingsViewModel: ObservableObject, GeneralSettingsViewModel
selectedProvider = preferences.selectedProvider
autoDetectMeetings = preferences.autoDetectMeetings
isAutoStopRecording = preferences.autoStopRecording
customPromptTemplate = preferences.summaryPromptTemplate ?? UserPreferencesInfo.defaultPromptTemplate
} catch {
selectedProvider = .default
autoDetectMeetings = false
isAutoStopRecording = false
customPromptTemplate = UserPreferencesInfo.defaultPromptTemplate
}
await loadModels()
}
Expand Down Expand Up @@ -167,4 +170,22 @@ final class GeneralSettingsViewModel: ObservableObject, GeneralSettingsViewModel
isAutoStopRecording = !enabled
}
}

func updateCustomPromptTemplate(_ template: String) async {
errorMessage = nil
let trimmedTemplate = template.trimmingCharacters(in: .whitespacesAndNewlines)
customPromptTemplate = trimmedTemplate

do {
let templateToSave = trimmedTemplate.isEmpty ? nil : trimmedTemplate
try await userPreferencesRepository.updateSummaryPromptTemplate(templateToSave)
} catch {
errorMessage = error.localizedDescription
}
}

func resetToDefaultPrompt() async {
await updateCustomPromptTemplate("")
customPromptTemplate = UserPreferencesInfo.defaultPromptTemplate
Comment thread
dennisimoo marked this conversation as resolved.
Outdated
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ protocol GeneralSettingsViewModelType: ObservableObject {
var showToast: Bool { get }
var toastMessage: String { get }
var activeWarnings: [WarningItem] { get }
var customPromptTemplate: String { get set }

func loadModels() async
func selectModel(_ model: LLMModelInfo) async
func selectProvider(_ provider: LLMProvider) async
func toggleAutoDetectMeetings(_ enabled: Bool) async
func toggleAutoStopRecording(_ enabled: Bool) async
func updateCustomPromptTemplate(_ template: String) async
func resetToDefaultPrompt() async
}