Explorar o código

Add Summarizer and Paraphrasing pages matching the grammar checker layout.

Co-authored-by: Cursor <cursoragent@cursor.com>
AhtashamShahzad1 hai 1 mes
pai
achega
79fa1314d7

+ 29 - 0
gramora/ViewModels/ParaphrasingViewModel.swift

@@ -0,0 +1,29 @@
+import AppKit
+import Combine
+import Foundation
+
+@MainActor
+final class ParaphrasingViewModel: ObservableObject {
+    static let maxCharacters = 10_000
+
+    @Published var text: String = ""
+
+    var characterCount: Int { text.count }
+
+    var characterCountLabel: String {
+        "\(characterCount)/\(Self.maxCharacters)"
+    }
+
+    func pasteText() {
+        guard let clipboard = NSPasteboard.general.string(forType: .string) else { return }
+        text = String(clipboard.prefix(Self.maxCharacters))
+    }
+
+    func clearText() {
+        text = ""
+    }
+
+    func paraphrase() {
+        // Paraphrasing service integration point
+    }
+}

+ 29 - 0
gramora/ViewModels/SummarizerViewModel.swift

@@ -0,0 +1,29 @@
+import AppKit
+import Combine
+import Foundation
+
+@MainActor
+final class SummarizerViewModel: ObservableObject {
+    static let maxCharacters = 10_000
+
+    @Published var text: String = ""
+
+    var characterCount: Int { text.count }
+
+    var characterCountLabel: String {
+        "\(characterCount)/\(Self.maxCharacters)"
+    }
+
+    func pasteText() {
+        guard let clipboard = NSPasteboard.general.string(forType: .string) else { return }
+        text = String(clipboard.prefix(Self.maxCharacters))
+    }
+
+    func clearText() {
+        text = ""
+    }
+
+    func summarize() {
+        // Summarization service integration point
+    }
+}

+ 4 - 0
gramora/Views/MainView.swift

@@ -52,6 +52,10 @@ struct MainView: View {
         switch viewModel.selectedDestination {
         case .grammarChecker:
             GrammarCheckerView()
+        case .summarizer:
+            SummarizerView()
+        case .paraphrasing:
+            ParaphrasingView()
         default:
             PlaceholderView(destination: viewModel.selectedDestination)
         }

+ 134 - 0
gramora/Views/ParaphrasingView.swift

@@ -0,0 +1,134 @@
+import SwiftUI
+
+struct ParaphrasingView: View {
+    @StateObject private var viewModel = ParaphrasingViewModel()
+
+    var body: some View {
+        GeometryReader { proxy in
+            let dynamicHorizontalPadding = min(max(proxy.size.width * 0.045, AppTheme.contentPadding), 52)
+            let dynamicTopPadding = min(max(proxy.size.height * 0.05, 24), 44)
+            let dynamicBottomPadding = min(max(proxy.size.height * 0.06, 28), 56)
+            let dynamicEditorHeight = min(max(proxy.size.height * 0.43, 260), 560)
+
+            ScrollView(.vertical, showsIndicators: false) {
+                VStack(alignment: .leading, spacing: 0) {
+                    header
+                        .padding(.bottom, 28)
+
+                    contentCard(editorHeight: dynamicEditorHeight)
+                        .padding(.bottom, 24)
+
+                    GradientButton(title: "Paraphrase") {
+                        viewModel.paraphrase()
+                    }
+                }
+                .frame(maxWidth: 1200, alignment: .topLeading)
+                .frame(maxWidth: .infinity, alignment: .topLeading)
+                .padding(.horizontal, dynamicHorizontalPadding)
+                .padding(.top, dynamicTopPadding)
+                .padding(.bottom, dynamicBottomPadding)
+            }
+        }
+        .onChange(of: viewModel.text) { newValue in
+            if newValue.count > ParaphrasingViewModel.maxCharacters {
+                viewModel.text = String(newValue.prefix(ParaphrasingViewModel.maxCharacters))
+            }
+        }
+    }
+
+    private var header: some View {
+        HStack(alignment: .top) {
+            VStack(alignment: .leading, spacing: 8) {
+                Text("Paraphrasing")
+                    .font(.system(size: 30, weight: .bold))
+                    .foregroundStyle(AppTheme.textPrimary)
+
+                HStack(spacing: 0) {
+                    Text("Say it ")
+                        .foregroundStyle(AppTheme.textSecondary)
+                    Text("differently")
+                        .fontWeight(.semibold)
+                        .foregroundStyle(AppTheme.teal)
+                    Text(". Keep it ")
+                        .foregroundStyle(AppTheme.textSecondary)
+                    Text("yours")
+                        .fontWeight(.semibold)
+                        .foregroundStyle(AppTheme.teal)
+                    Text(".")
+                        .foregroundStyle(AppTheme.textSecondary)
+                }
+                .font(.system(size: 15))
+            }
+
+            Spacer()
+
+            IconButton {
+                SettingsGearIcon()
+            } action: {}
+            .accessibilityLabel("Settings")
+        }
+    }
+
+    private func contentCard(editorHeight: CGFloat) -> some View {
+        VStack(alignment: .leading, spacing: 16) {
+            Image("YourContentLogo")
+                .resizable()
+                .aspectRatio(contentMode: .fit)
+                .frame(height: 52, alignment: .topLeading)
+                .frame(height: 46, alignment: .topLeading)
+                .clipped()
+                .accessibilityLabel("Your Content")
+
+            ZStack(alignment: .bottomTrailing) {
+                ThinCaretTextEditor(text: $viewModel.text)
+                    .font(.system(size: 14))
+                    .foregroundStyle(AppTheme.textPrimary)
+                    .frame(minHeight: editorHeight, idealHeight: editorHeight)
+                    .background(Color.white)
+                    .clipShape(RoundedRectangle(cornerRadius: AppTheme.inputCornerRadius))
+                    .overlay(
+                        RoundedRectangle(cornerRadius: AppTheme.inputCornerRadius)
+                            .stroke(AppTheme.inputBorder, lineWidth: 1)
+                    )
+                    .overlay(alignment: .topLeading) {
+                        if viewModel.text.isEmpty {
+                            Text("Type or paste your text here and press the paraphrase button...")
+                                .font(.system(size: 14))
+                                .foregroundStyle(AppTheme.textMuted)
+                                .padding(.leading, AppTheme.textEditorHorizontalInset)
+                                .padding(.top, AppTheme.textEditorVerticalInset)
+                                .allowsHitTesting(false)
+                        }
+                    }
+
+                Text(viewModel.characterCountLabel)
+                    .font(.system(size: 11))
+                    .foregroundStyle(AppTheme.textMuted)
+                    .padding(.trailing, 16)
+                    .padding(.bottom, 12)
+            }
+
+            HStack(spacing: 8) {
+                ToolbarButton(title: "Paste Text", iconName: "doc.on.clipboard") {
+                    viewModel.pasteText()
+                }
+
+                ToolbarButton(title: "Upload File", iconName: "square.and.arrow.up") {}
+
+                Spacer()
+
+                ClearButton {
+                    viewModel.clearText()
+                }
+            }
+        }
+        .padding(22)
+        .background(AppTheme.cardBackground)
+        .clipShape(RoundedRectangle(cornerRadius: AppTheme.cardCornerRadius))
+        .overlay(
+            RoundedRectangle(cornerRadius: AppTheme.cardCornerRadius)
+                .stroke(AppTheme.border, lineWidth: 1)
+        )
+        .shadow(color: AppTheme.cardShadow, radius: 16, y: 6)
+    }
+}

+ 134 - 0
gramora/Views/SummarizerView.swift

@@ -0,0 +1,134 @@
+import SwiftUI
+
+struct SummarizerView: View {
+    @StateObject private var viewModel = SummarizerViewModel()
+
+    var body: some View {
+        GeometryReader { proxy in
+            let dynamicHorizontalPadding = min(max(proxy.size.width * 0.045, AppTheme.contentPadding), 52)
+            let dynamicTopPadding = min(max(proxy.size.height * 0.05, 24), 44)
+            let dynamicBottomPadding = min(max(proxy.size.height * 0.06, 28), 56)
+            let dynamicEditorHeight = min(max(proxy.size.height * 0.43, 260), 560)
+
+            ScrollView(.vertical, showsIndicators: false) {
+                VStack(alignment: .leading, spacing: 0) {
+                    header
+                        .padding(.bottom, 28)
+
+                    contentCard(editorHeight: dynamicEditorHeight)
+                        .padding(.bottom, 24)
+
+                    GradientButton(title: "Summarize") {
+                        viewModel.summarize()
+                    }
+                }
+                .frame(maxWidth: 1200, alignment: .topLeading)
+                .frame(maxWidth: .infinity, alignment: .topLeading)
+                .padding(.horizontal, dynamicHorizontalPadding)
+                .padding(.top, dynamicTopPadding)
+                .padding(.bottom, dynamicBottomPadding)
+            }
+        }
+        .onChange(of: viewModel.text) { newValue in
+            if newValue.count > SummarizerViewModel.maxCharacters {
+                viewModel.text = String(newValue.prefix(SummarizerViewModel.maxCharacters))
+            }
+        }
+    }
+
+    private var header: some View {
+        HStack(alignment: .top) {
+            VStack(alignment: .leading, spacing: 8) {
+                Text("Summarizer")
+                    .font(.system(size: 30, weight: .bold))
+                    .foregroundStyle(AppTheme.textPrimary)
+
+                HStack(spacing: 0) {
+                    Text("Read ")
+                        .foregroundStyle(AppTheme.textSecondary)
+                    Text("less")
+                        .fontWeight(.semibold)
+                        .foregroundStyle(AppTheme.teal)
+                    Text(". Understand ")
+                        .foregroundStyle(AppTheme.textSecondary)
+                    Text("more")
+                        .fontWeight(.semibold)
+                        .foregroundStyle(AppTheme.teal)
+                    Text(".")
+                        .foregroundStyle(AppTheme.textSecondary)
+                }
+                .font(.system(size: 15))
+            }
+
+            Spacer()
+
+            IconButton {
+                SettingsGearIcon()
+            } action: {}
+            .accessibilityLabel("Settings")
+        }
+    }
+
+    private func contentCard(editorHeight: CGFloat) -> some View {
+        VStack(alignment: .leading, spacing: 16) {
+            Image("YourContentLogo")
+                .resizable()
+                .aspectRatio(contentMode: .fit)
+                .frame(height: 52, alignment: .topLeading)
+                .frame(height: 46, alignment: .topLeading)
+                .clipped()
+                .accessibilityLabel("Your Content")
+
+            ZStack(alignment: .bottomTrailing) {
+                ThinCaretTextEditor(text: $viewModel.text)
+                    .font(.system(size: 14))
+                    .foregroundStyle(AppTheme.textPrimary)
+                    .frame(minHeight: editorHeight, idealHeight: editorHeight)
+                    .background(Color.white)
+                    .clipShape(RoundedRectangle(cornerRadius: AppTheme.inputCornerRadius))
+                    .overlay(
+                        RoundedRectangle(cornerRadius: AppTheme.inputCornerRadius)
+                            .stroke(AppTheme.inputBorder, lineWidth: 1)
+                    )
+                    .overlay(alignment: .topLeading) {
+                        if viewModel.text.isEmpty {
+                            Text("Type or paste your text here and press the summarize button...")
+                                .font(.system(size: 14))
+                                .foregroundStyle(AppTheme.textMuted)
+                                .padding(.leading, AppTheme.textEditorHorizontalInset)
+                                .padding(.top, AppTheme.textEditorVerticalInset)
+                                .allowsHitTesting(false)
+                        }
+                    }
+
+                Text(viewModel.characterCountLabel)
+                    .font(.system(size: 11))
+                    .foregroundStyle(AppTheme.textMuted)
+                    .padding(.trailing, 16)
+                    .padding(.bottom, 12)
+            }
+
+            HStack(spacing: 8) {
+                ToolbarButton(title: "Paste Text", iconName: "doc.on.clipboard") {
+                    viewModel.pasteText()
+                }
+
+                ToolbarButton(title: "Upload File", iconName: "square.and.arrow.up") {}
+
+                Spacer()
+
+                ClearButton {
+                    viewModel.clearText()
+                }
+            }
+        }
+        .padding(22)
+        .background(AppTheme.cardBackground)
+        .clipShape(RoundedRectangle(cornerRadius: AppTheme.cardCornerRadius))
+        .overlay(
+            RoundedRectangle(cornerRadius: AppTheme.cardCornerRadius)
+                .stroke(AppTheme.border, lineWidth: 1)
+        )
+        .shadow(color: AppTheme.cardShadow, radius: 16, y: 6)
+    }
+}