| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151 |
- import AppKit
- import Combine
- import Foundation
- @MainActor
- final class GrammarCheckerViewModel: ObservableObject {
- static let maxWords = 1_000
- @Published var text: String = ""
- @Published private(set) var issues: [GrammarIssue] = []
- @Published private(set) var correctedText: String = ""
- @Published private(set) var hasResults = false
- @Published private(set) var isChecking = false
- @Published private(set) var errorMessage: String?
- private let grammarCheckService: any GrammarCheckServicing
- private let historyStorage: HistoryStoring
- private let textExtractionService: any TextExtracting
- init(
- grammarCheckService: any GrammarCheckServicing = GrammarCheckService(),
- historyStorage: HistoryStoring = HistoryStorageService.shared,
- textExtractionService: any TextExtracting = TextExtractionService()
- ) {
- self.grammarCheckService = grammarCheckService
- self.historyStorage = historyStorage
- self.textExtractionService = textExtractionService
- }
- var wordCountLimitLabel: String {
- "\(text.wordCount)/\(Self.maxWords)"
- }
- var issueCountLabel: String {
- let count = issues.count
- return "\(count) Issue\(count == 1 ? "" : "s")"
- }
- var canCheck: Bool {
- !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !isChecking
- }
- func pasteText() {
- guard let clipboard = NSPasteboard.general.string(forType: .string) else { return }
- text = String.importedApplyingWordLimit(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
- resetResults()
- }
- func uploadFile() {
- Task {
- do {
- guard let extracted = try await textExtractionService.pickAndExtractText() else { return }
- text = String.importedApplyingWordLimit(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 })
- resetResults()
- } catch {
- errorMessage = error.localizedDescription
- }
- }
- }
- func notifyWordLimitReached() {
- errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
- }
- func handleTextChange() {
- let (limited, exceeded) = text.applyingWordLimit(maxWords: Self.maxWords)
- if exceeded {
- text = limited
- errorMessage = String.wordLimitExceededMessage(maxWords: Self.maxWords)
- } else if errorMessage == String.wordLimitExceededMessage(maxWords: Self.maxWords) {
- errorMessage = nil
- }
- if hasResults {
- resetResults()
- }
- }
- func clearText() {
- text = ""
- resetResults()
- }
- func resetResults() {
- issues = []
- correctedText = ""
- hasResults = false
- errorMessage = nil
- }
- func checkGrammar() {
- let content = text.trimmingCharacters(in: .whitespacesAndNewlines)
- guard !content.isEmpty else {
- resetResults()
- return
- }
- isChecking = true
- errorMessage = nil
- Task {
- do {
- let result = try await grammarCheckService.checkGrammar(in: content)
- issues = result.issues
- correctedText = result.correctedText
- hasResults = true
- saveToHistory(text: content, issues: result.issues, correctedText: result.correctedText)
- } catch {
- errorMessage = error.localizedDescription
- }
- isChecking = false
- }
- }
- func copyCorrectedText() {
- guard !correctedText.isEmpty else { return }
- NSPasteboard.general.clearContents()
- NSPasteboard.general.setString(correctedText, forType: .string)
- }
- func restore(from entry: HistoryEntry) {
- guard case .grammarChecker(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.grammarChecker(
- GrammarCheckerHistoryData(
- text: text,
- issues: issues.map(\.stored),
- correctedText: correctedText
- )
- )
- let entry = HistoryEntry(
- tool: .grammarChecker,
- title: HistoryHelpers.title(from: text, fallback: "Grammar Check"),
- preview: HistoryHelpers.preview(from: correctedText.isEmpty ? text : correctedText),
- payload: payload
- )
- historyStorage.save(entry)
- }
- }
|