Sfoglia il codice sorgente

Add history recording for summarizer, paraphrasing, and punctuation checker.

These tools now save results to history and support reopening saved entries from the History screen.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 1 mese fa
parent
commit
df3a4f4011

+ 25 - 0
gramora/Models/HistoryModels.swift

@@ -2,16 +2,22 @@ import Foundation
 
 enum HistoryTool: String, Codable, CaseIterable {
     case grammarChecker
+    case summarizer
+    case paraphrasing
     case spellingChecker
     case languageTranslator
+    case punctuationChecker
     case aiEmailWriter
     case journalFinder
 
     var navigationDestination: NavigationDestination {
         switch self {
         case .grammarChecker: .grammarChecker
+        case .summarizer: .summarizer
+        case .paraphrasing: .paraphrasing
         case .spellingChecker: .spellingChecker
         case .languageTranslator: .languageTranslator
+        case .punctuationChecker: .punctuationChecker
         case .aiEmailWriter: .aiEmailWriter
         case .journalFinder: .journalFinder
         }
@@ -53,8 +59,11 @@ struct HistoryEntry: Identifiable, Codable, Equatable {
 
 enum HistoryPayload: Codable, Equatable {
     case grammarChecker(GrammarCheckerHistoryData)
+    case summarizer(SummarizerHistoryData)
+    case paraphrasing(ParaphrasingHistoryData)
     case spellingChecker(SpellCheckerHistoryData)
     case languageTranslator(TranslationHistoryData)
+    case punctuationChecker(PunctuationCheckerHistoryData)
     case emailWriter(EmailWriterHistoryData)
     case journalFinder(JournalFinderHistoryData)
 }
@@ -65,6 +74,22 @@ struct GrammarCheckerHistoryData: Codable, Equatable {
     let correctedText: String
 }
 
+struct SummarizerHistoryData: Codable, Equatable {
+    let text: String
+    let summary: String
+}
+
+struct ParaphrasingHistoryData: Codable, Equatable {
+    let text: String
+    let paraphrasedText: String
+}
+
+struct PunctuationCheckerHistoryData: Codable, Equatable {
+    let text: String
+    let issues: [StoredGrammarIssue]
+    let correctedText: String
+}
+
 struct StoredGrammarIssue: Codable, Equatable, Identifiable {
     let id: UUID
     let original: String

+ 29 - 1
gramora/ViewModels/ParaphrasingViewModel.swift

@@ -12,13 +12,16 @@ final class ParaphrasingViewModel: ObservableObject {
     @Published private(set) var errorMessage: String?
 
     private let paraphrasingService: any ParaphrasingServicing
+    private let historyStorage: HistoryStoring
     private let textExtractionService: any TextExtracting
 
     init(
         paraphrasingService: any ParaphrasingServicing = ParaphrasingService(),
+        historyStorage: HistoryStoring = HistoryStorageService.shared,
         textExtractionService: any TextExtracting = TextExtractionService()
     ) {
         self.paraphrasingService = paraphrasingService
+        self.historyStorage = historyStorage
         self.textExtractionService = textExtractionService
     }
 
@@ -89,7 +92,9 @@ final class ParaphrasingViewModel: ObservableObject {
 
         Task {
             do {
-                paraphrasedText = try await paraphrasingService.paraphrase(content)
+                let result = try await paraphrasingService.paraphrase(content)
+                paraphrasedText = result
+                saveToHistory(text: content, paraphrasedText: result)
             } catch {
                 errorMessage = error.localizedDescription
             }
@@ -98,6 +103,29 @@ final class ParaphrasingViewModel: ObservableObject {
         }
     }
 
+    func restore(from entry: HistoryEntry) {
+        guard case .paraphrasing(let data) = entry.payload else { return }
+
+        text = data.text
+        paraphrasedText = data.paraphrasedText
+        errorMessage = nil
+    }
+
+    private func saveToHistory(text: String, paraphrasedText: String) {
+        let payload = HistoryPayload.paraphrasing(
+            ParaphrasingHistoryData(text: text, paraphrasedText: paraphrasedText)
+        )
+
+        let entry = HistoryEntry(
+            tool: .paraphrasing,
+            title: HistoryHelpers.title(from: text, fallback: "Paraphrasing"),
+            preview: HistoryHelpers.preview(from: paraphrasedText),
+            payload: payload
+        )
+
+        historyStorage.save(entry)
+    }
+
     func copyParaphrasedText() {
         guard !paraphrasedText.isEmpty else { return }
         NSPasteboard.general.clearContents()

+ 35 - 0
gramora/ViewModels/PunctuationCheckerViewModel.swift

@@ -14,13 +14,16 @@ final class PunctuationCheckerViewModel: ObservableObject {
     @Published private(set) var errorMessage: String?
 
     private let punctuationCheckService: any PunctuationCheckServicing
+    private let historyStorage: HistoryStoring
     private let textExtractionService: any TextExtracting
 
     init(
         punctuationCheckService: any PunctuationCheckServicing = PunctuationCheckService(),
+        historyStorage: HistoryStoring = HistoryStorageService.shared,
         textExtractionService: any TextExtracting = TextExtractionService()
     ) {
         self.punctuationCheckService = punctuationCheckService
+        self.historyStorage = historyStorage
         self.textExtractionService = textExtractionService
     }
 
@@ -95,6 +98,7 @@ final class PunctuationCheckerViewModel: ObservableObject {
                 correctedText = result.correctedText
                 issues = result.issues
                 hasResults = true
+                saveToHistory(text: content, issues: result.issues, correctedText: result.correctedText)
             } catch {
                 errorMessage = error.localizedDescription
             }
@@ -103,6 +107,37 @@ final class PunctuationCheckerViewModel: ObservableObject {
         }
     }
 
+    func restore(from entry: HistoryEntry) {
+        guard case .punctuationChecker(let data) = entry.payload else { return }
+
+        text = data.text
+        issues = data.issues.map {
+            GrammarIssue(original: $0.original, suggestion: $0.suggestion, explanation: $0.explanation)
+        }
+        correctedText = data.correctedText
+        hasResults = true
+        errorMessage = nil
+    }
+
+    private func saveToHistory(text: String, issues: [GrammarIssue], correctedText: String) {
+        let payload = HistoryPayload.punctuationChecker(
+            PunctuationCheckerHistoryData(
+                text: text,
+                issues: issues.map(\.stored),
+                correctedText: correctedText
+            )
+        )
+
+        let entry = HistoryEntry(
+            tool: .punctuationChecker,
+            title: HistoryHelpers.title(from: text, fallback: "Punctuation Check"),
+            preview: HistoryHelpers.preview(from: correctedText.isEmpty ? text : correctedText),
+            payload: payload
+        )
+
+        historyStorage.save(entry)
+    }
+
     func copyCorrectedText() {
         guard !correctedText.isEmpty else { return }
         NSPasteboard.general.clearContents()

+ 29 - 1
gramora/ViewModels/SummarizerViewModel.swift

@@ -12,13 +12,16 @@ final class SummarizerViewModel: ObservableObject {
     @Published private(set) var errorMessage: String?
 
     private let summarizerService: any SummarizerServicing
+    private let historyStorage: HistoryStoring
     private let textExtractionService: any TextExtracting
 
     init(
         summarizerService: any SummarizerServicing = SummarizerService(),
+        historyStorage: HistoryStoring = HistoryStorageService.shared,
         textExtractionService: any TextExtracting = TextExtractionService()
     ) {
         self.summarizerService = summarizerService
+        self.historyStorage = historyStorage
         self.textExtractionService = textExtractionService
     }
 
@@ -89,7 +92,9 @@ final class SummarizerViewModel: ObservableObject {
 
         Task {
             do {
-                summary = try await summarizerService.summarize(content)
+                let result = try await summarizerService.summarize(content)
+                summary = result
+                saveToHistory(text: content, summary: result)
             } catch {
                 errorMessage = error.localizedDescription
             }
@@ -98,6 +103,29 @@ final class SummarizerViewModel: ObservableObject {
         }
     }
 
+    func restore(from entry: HistoryEntry) {
+        guard case .summarizer(let data) = entry.payload else { return }
+
+        text = data.text
+        summary = data.summary
+        errorMessage = nil
+    }
+
+    private func saveToHistory(text: String, summary: String) {
+        let payload = HistoryPayload.summarizer(
+            SummarizerHistoryData(text: text, summary: summary)
+        )
+
+        let entry = HistoryEntry(
+            tool: .summarizer,
+            title: HistoryHelpers.title(from: text, fallback: "Summary"),
+            preview: HistoryHelpers.preview(from: summary),
+            payload: payload
+        )
+
+        historyStorage.save(entry)
+    }
+
     func copySummary() {
         guard !summary.isEmpty else { return }
         NSPasteboard.general.clearContents()

+ 12 - 3
gramora/Views/MainView.swift

@@ -76,16 +76,25 @@ struct MainView: View {
                 onRestoreHandled: viewModel.clearPendingHistoryEntry
             )
         case .summarizer:
-            SummarizerView()
+            SummarizerView(
+                restoreEntry: historyEntry(for: .summarizer),
+                onRestoreHandled: viewModel.clearPendingHistoryEntry
+            )
         case .paraphrasing:
-            ParaphrasingView()
+            ParaphrasingView(
+                restoreEntry: historyEntry(for: .paraphrasing),
+                onRestoreHandled: viewModel.clearPendingHistoryEntry
+            )
         case .languageTranslator:
             LanguageTranslatorView(
                 restoreEntry: historyEntry(for: .languageTranslator),
                 onRestoreHandled: viewModel.clearPendingHistoryEntry
             )
         case .punctuationChecker:
-            PunctuationCheckerView()
+            PunctuationCheckerView(
+                restoreEntry: historyEntry(for: .punctuationChecker),
+                onRestoreHandled: viewModel.clearPendingHistoryEntry
+            )
         case .wordsCount:
             WordsCountView()
         case .aiEmailWriter:

+ 18 - 0
gramora/Views/ParaphrasingView.swift

@@ -4,6 +4,14 @@ struct ParaphrasingView: View {
     @StateObject private var viewModel = ParaphrasingViewModel()
     @Environment(\.onSettingsTapped) private var onSettingsTapped
 
+    let restoreEntry: HistoryEntry?
+    let onRestoreHandled: () -> Void
+
+    init(restoreEntry: HistoryEntry? = nil, onRestoreHandled: @escaping () -> Void = {}) {
+        self.restoreEntry = restoreEntry
+        self.onRestoreHandled = onRestoreHandled
+    }
+
     var body: some View {
         GeometryReader { proxy in
             let dynamicHorizontalPadding = min(max(proxy.size.width * 0.045, AppTheme.contentPadding), 52)
@@ -62,6 +70,16 @@ struct ParaphrasingView: View {
         .onChange(of: viewModel.text) { _ in
             viewModel.handleTextChange()
         }
+        .onAppear(perform: restoreIfNeeded)
+        .onChange(of: restoreEntry?.id) { _ in
+            restoreIfNeeded()
+        }
+    }
+
+    private func restoreIfNeeded() {
+        guard let restoreEntry else { return }
+        viewModel.restore(from: restoreEntry)
+        onRestoreHandled()
     }
 
     private func loadingLayout(

+ 18 - 0
gramora/Views/PunctuationCheckerView.swift

@@ -4,6 +4,14 @@ struct PunctuationCheckerView: View {
     @StateObject private var viewModel = PunctuationCheckerViewModel()
     @Environment(\.onSettingsTapped) private var onSettingsTapped
 
+    let restoreEntry: HistoryEntry?
+    let onRestoreHandled: () -> Void
+
+    init(restoreEntry: HistoryEntry? = nil, onRestoreHandled: @escaping () -> Void = {}) {
+        self.restoreEntry = restoreEntry
+        self.onRestoreHandled = onRestoreHandled
+    }
+
     var body: some View {
         GeometryReader { proxy in
             let dynamicHorizontalPadding = min(max(proxy.size.width * 0.045, AppTheme.contentPadding), 52)
@@ -62,6 +70,16 @@ struct PunctuationCheckerView: View {
         .onChange(of: viewModel.text) { _ in
             viewModel.handleTextChange()
         }
+        .onAppear(perform: restoreIfNeeded)
+        .onChange(of: restoreEntry?.id) { _ in
+            restoreIfNeeded()
+        }
+    }
+
+    private func restoreIfNeeded() {
+        guard let restoreEntry else { return }
+        viewModel.restore(from: restoreEntry)
+        onRestoreHandled()
     }
 
     private func loadingLayout(

+ 18 - 0
gramora/Views/SummarizerView.swift

@@ -4,6 +4,14 @@ struct SummarizerView: View {
     @StateObject private var viewModel = SummarizerViewModel()
     @Environment(\.onSettingsTapped) private var onSettingsTapped
 
+    let restoreEntry: HistoryEntry?
+    let onRestoreHandled: () -> Void
+
+    init(restoreEntry: HistoryEntry? = nil, onRestoreHandled: @escaping () -> Void = {}) {
+        self.restoreEntry = restoreEntry
+        self.onRestoreHandled = onRestoreHandled
+    }
+
     var body: some View {
         GeometryReader { proxy in
             let dynamicHorizontalPadding = min(max(proxy.size.width * 0.045, AppTheme.contentPadding), 52)
@@ -62,6 +70,16 @@ struct SummarizerView: View {
         .onChange(of: viewModel.text) { _ in
             viewModel.handleTextChange()
         }
+        .onAppear(perform: restoreIfNeeded)
+        .onChange(of: restoreEntry?.id) { _ in
+            restoreIfNeeded()
+        }
+    }
+
+    private func restoreIfNeeded() {
+        guard let restoreEntry else { return }
+        viewModel.restore(from: restoreEntry)
+        onRestoreHandled()
     }
 
     private func loadingLayout(