Skip to content

Conversation

@plyght
Copy link
Contributor

@plyght plyght commented Apr 23, 2025

@kitlangton
This pull request introduces significant enhancements and fixes across multiple areas of the codebase, including the addition of a new AI enhancement feature, improvements to pasteboard handling, and updates to existing functionalities. Below is a breakdown of the most important changes grouped by theme:

New Feature: AI Enhancement

  • Added a new AIEnhancementClient in Hex/Clients/AIEnhancementClient.swift, which provides functionality for enhancing transcribed text using local AI models like Ollama. This includes methods for checking model availability, retrieving available models, and performing text enhancement with detailed options.
  • Introduced a new aiEnhancement tab in the app's UI by updating AppFeature and AppView to include the AI enhancement feature. This includes a new button and navigation logic. [1] [2] [3]

Improvements to Pasteboard Handling

  • Updated savePasteboardState in PasteboardClientLive to limit the number of pasteboard items saved (to 5) and restrict the size of saved data (to 1MB per item) to reduce memory usage.
  • Enhanced the pasteWithClipboard method to conditionally save the pasteboard state only when necessary, added delays for better system processing, and ensured proper restoration of the pasteboard state. [1] [2]

Updates to Transcription Functionality

  • Modified the transcription process in TranscriptionClientLive to respect a new disableAutoCapitalization setting from hex_settings.json. If this setting is enabled, transcribed text will remain in lowercase.

Build Configuration Changes

  • Changed the CODE_SIGN_IDENTITY for macOS builds in Hex.xcodeproj/project.pbxproj to "-" to simplify code signing during development. [1] [2]

Minor Fixes

  • Updated RecordingClientLive to use let instead of var for the deviceNamePtr allocation to ensure immutability.

Summary by CodeRabbit

  • New Features

    • Introduced AI enhancement for transcription, allowing improved grammar, formatting, and error correction using local AI models.
    • Added a dedicated AI Enhancement settings page with controls for enabling AI, model selection, temperature adjustment, and prompt customization.
    • Integrated Ollama service detection and model management, including connection status and error handling.
    • Added toggle to disable automatic capitalization in transcriptions.
  • Enhancements

    • Updated transcription flow to optionally process results with AI enhancement based on user preferences.
    • Improved pasteboard management for more reliable clipboard operations using temporary pasteboard backups.
    • Reduced redundant audio meter updates with adaptive throttling to optimize performance.
    • Added thread safety and resource management improvements in key event handling and paste fallback mechanisms.
  • User Interface

    • Extended navigation and settings views to include AI enhancement controls and status indicators.
    • Updated transcription indicator to visually reflect AI enhancement activity with new animations and colors.
    • Added descriptive toggle for disabling automatic capitalization in transcription settings.
  • Localization

    • Added new localization keys for AI enhancement features and related UI elements.
  • Performance

    • Implemented caching for settings loading to reduce file I/O.

@plyght plyght marked this pull request as ready for review April 28, 2025 00:39
@coderabbitai
Copy link

coderabbitai bot commented Apr 28, 2025

Walkthrough

This update introduces AI-powered transcription enhancement to the Hex application, primarily by integrating local large language models (LLMs) through Ollama. New client logic and UI components are added to enable users to configure and use AI enhancement, including model selection, prompt customization, and response style adjustment. The transcription flow is updated to optionally pass results through the AI enhancement pipeline, with state and error handling for the asynchronous process. Settings and state management are extended to support these features, and new localization keys are added for the updated UI. Pasteboard and recording logic receive minor improvements and optimizations.

Changes

File(s) Change Summary
Hex/Clients/AIEnhancementClient.swift New client for AI-powered text enhancement using local LLMs via Ollama. Provides methods for enhancement, model listing, and service availability checks. Includes robust error handling, progress reporting, and dependency injection support.
Hex/Features/Settings/AIEnhancementFeature.swift, Hex/Features/Settings/AIEnhancementView.swift New feature and view for AI enhancement settings. Implements state, actions, reducer, and a SwiftUI form for configuring AI model, prompt, and temperature. Handles Ollama availability checks, model fetching, error handling, and user interactions.
Hex/Features/App/AppFeature.swift, Hex/Features/Settings/SettingsFeature.swift, Hex/Features/Settings/SettingsView.swift App and settings integration: Adds AI enhancement tab in app navigation, wires up new feature in settings reducer, and introduces a toggle for disabling automatic capitalization in transcriptions.
Hex/Features/Transcription/TranscriptionFeature.swift, Hex/Features/Transcription/TranscriptionIndicatorView.swift Transcription flow updates: Adds AI enhancement step after transcription with new state, actions, cancellation handling, and UI indicator support for enhancement progress. Enhances audio metering with rate limiting and threshold checks.
Hex/Clients/PasteboardClient.swift Refactors pasteboard backup/restore logic to use temporary NSPasteboard instances instead of manual dictionary copying. Improves fallback paste mechanism with thread-safe key code retrieval and autoreleasepool for event posting. Cleans up deprecated code paths.
Hex/Clients/RecordingClient.swift Adds adaptive throttling to audio meter update emissions to reduce redundant updates during inactivity; minor pointer allocation changed from var to let for clarity.
Hex/Clients/TranscriptionClient.swift Updates transcription method signature to accept optional settings parameter. Applies auto-capitalization control based on user setting to transcription output.
Hex/Models/HexSettings.swift Adds new properties for AI enhancement and auto-capitalization settings. Implements caching for settings loaded from disk to reduce file I/O.
Localizable.xcstrings Adds numerous new localization keys related to AI enhancement features, model management, transcription settings, and UI elements without translations.
Hex.xcodeproj/project.pbxproj Enables dead code stripping in all build configurations for project and targets.
Hex/Clients/KeyEventMonitorClient.swift Introduces SafeSauce enum to ensure thread-safe calls to Sauce library on main thread, preventing dispatch queue assertion failures. Updates KeyEvent initializer to use thread-safe key retrieval.

Sequence Diagram(s)

AI Enhancement Flow in Transcription

sequenceDiagram
    participant User
    participant AppView
    participant TranscriptionFeature
    participant AIEnhancementClient
    participant Ollama

    User->>AppView: Starts transcription
    AppView->>TranscriptionFeature: Begin recording/transcribing
    TranscriptionFeature->>TranscriptionFeature: Process audio, produce transcript
    TranscriptionFeature->>TranscriptionFeature: Check if AI enhancement enabled
    alt Enhancement enabled
        TranscriptionFeature->>AIEnhancementClient: enhance(transcript, model, options, progress)
        AIEnhancementClient->>Ollama: POST /api/generate
        Ollama-->>AIEnhancementClient: Enhanced text
        AIEnhancementClient-->>TranscriptionFeature: Return enhanced text
        TranscriptionFeature->>TranscriptionFeature: Store enhanced transcript
    else Enhancement disabled
        TranscriptionFeature->>TranscriptionFeature: Store original transcript
    end
    TranscriptionFeature-->>AppView: Update UI with final transcript
Loading

AI Enhancement Settings Initialization

sequenceDiagram
    participant User
    participant AIEnhancementView
    participant AIEnhancementFeature
    participant AIEnhancementClient
    participant Ollama

    User->>AIEnhancementView: Opens AI Enhancement settings
    AIEnhancementView->>AIEnhancementFeature: .task action
    AIEnhancementFeature->>AIEnhancementClient: isOllamaAvailable()
    AIEnhancementClient->>Ollama: GET /api/tags
    Ollama-->>AIEnhancementClient: Model list or error
    AIEnhancementClient-->>AIEnhancementFeature: Availability and models
    AIEnhancementFeature-->>AIEnhancementView: Update models, status
Loading

Poem

🐇✨
In Hex’s warren, clever and bright,
AI now helps your words take flight.
With models picked and prompts in tow,
Transcripts shine where bunnies go.
Settings cached and toggles new,
Pasteboards swift, enhancements too—
Hop along, your text refined,
By rabbit code and AI combined!

Tip

⚡️ Faster reviews with caching
  • CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.

Enjoy the performance boost—your workflow just got faster.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🔭 Outside diff range comments (1)
Localizable.xcstrings (1)

38-670: 🛠️ Refactor suggestion

New localization keys added without comments or translations.

The file has been updated with numerous new localization keys related to the AI enhancement feature, but they lack comments explaining their context and translations for other supported languages like German.

For consistency with existing localization keys in the file, consider adding:

  1. Comments explaining where and how each string is used
  2. Translations for the supported languages (particularly German, which appears to be supported)

This will ensure a consistent experience for users across all supported languages.

🧹 Nitpick comments (14)
Hex/Models/HexSettings.swift (1)

126-160: Efficient caching implementation for HexSettings.

The caching mechanism for HexSettings is a good performance optimization to reduce disk I/O. Setting a 5-second expiration is a reasonable balance between performance and freshness.

However, there's a minor issue in the variable declaration:

-private var cachedSettings: HexSettings? = nil
+private var cachedSettings: HexSettings?

Since Swift initializes optionals to nil by default, the explicit nil initialization is redundant.

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 127-127: Initializing an optional variable with nil is redundant

(redundant_optional_initialization)

Hex/Features/Settings/AIEnhancementFeature.swift (2)

21-21: Remove redundant optional initialization

errorMessage is declared as an optional, which is nil by default. Initialising it explicitly adds noise and triggers the SwiftLint warning you saw.

-var errorMessage: String? = nil
+var errorMessage: String?
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 21-21: Initializing an optional variable with nil is redundant

(redundant_optional_initialization)


24-26: Consider hoisting defaultAIModel to a static let

Because defaultAIModel never varies per‐instance, a static constant is marginally cheaper and communicates intent more clearly:

-// Computed property for convenient access to the default model
-var defaultAIModel: String {
-    "gemma3"
-}
+static let defaultAIModel = "gemma3"

You can then reference it with State.defaultAIModel.
This is purely stylistic, feel free to ignore if you prefer the current form.

Hex/Features/Transcription/TranscriptionFeature.swift (2)

60-61: Unused aiEnhancement cancel ID

CancelID.aiEnhancement is declared but never used in a .cancellable(id:) call.
If you intentionally removed cancellation to avoid premature termination, consider deleting the enum case altogether (or re-introduce the ID in .run {} via .cancellable(id: CancelID.aiEnhancement)).

This keeps the enum in sync with real usage and avoids confusion for future maintainers.


51-54: aiEnhancementError action is never dispatched

The catch-block in enhanceWithAI sends .aiEnhancementResult(result) instead of .aiEnhancementError. Either:

  1. Remove the unused case to simplify the reducer, or
  2. Emit the dedicated error action to handle/report enhancement failures separately.

Aligning intent and implementation prevents dead code paths.

Hex/Clients/PasteboardClient.swift (2)

60-64: Remove now-unused static tracking properties

savedChangeCount and savedPasteboardName are written in savePasteboardState but never read elsewhere after the refactor. They can be deleted to reduce clutter:

-// Stores the previous pasteboard owner change count
-private static var savedChangeCount: Int = 0
-// Stores the previous pasteboard contents name for tracking
-private static var savedPasteboardName: String?

94-109: Return value of writeObjects should be checked

NSPasteboard.writeObjects(_:) returns Bool indicating success.
Silently ignoring a failure may leave the pasteboard empty, causing data loss on restore.

-if let items = backupPasteboard.pasteboardItems {
-    backupPasteboard.writeObjects(items)
+if let items = backupPasteboard.pasteboardItems,
+   !items.isEmpty {
+    let ok = backupPasteboard.writeObjects(items)
+    if !ok {
+        print("⚠️  Failed to write items to backup pasteboard")
+    }
 }
Hex/Clients/AIEnhancementClient.swift (7)

1-11: Header block includes an unusual creator attribution.

The file header indicates it was created by "Claude AI" which is unusual for source code files. Consider changing this to reflect the actual developer or your team name for consistency with other files in the project.


12-14: Consider using OllamaKit directly as suggested by the comment.

The commented-out code suggests a future enhancement to use OllamaKit directly. This could provide better integration with Ollama and potentially simplify the code by leveraging an official or community-maintained client library rather than implementing the API integration manually.

Would you like me to research if OllamaKit exists and provide implementation guidelines for integrating it?


96-101: Replace magic number with a named constant.

The check text.count > 5 uses a hardcoded value. This would be clearer as a named constant.

+    // Minimum text length required for enhancement
+    private let minimumTextLengthForEnhancement = 5
+    
     /// Enhances text using a local AI model
     func enhance(text: String, model: String, options: EnhancementOptions, progressCallback: @escaping (Progress) -> Void) async throws -> String {
         // Skip if the text is empty or too short
-        guard !text.isEmpty, text.count > 5 else {
+        guard !text.isEmpty, text.count > minimumTextLengthForEnhancement else {
             print("[AIEnhancementClientLive] Text too short for enhancement, returning original")
             return text
         }

115-116: Use localized error messages instead of hardcoded strings.

Error messages are hardcoded in English. Since your app supports localization (as seen in the Localizable.xcstrings file), consider using localized strings for error messages.

-                throw NSError(domain: "AIEnhancementClient", code: -5, 
-                              userInfo: [NSLocalizedDescriptionKey: "Ollama is not available. Please ensure it's running."])
+                throw NSError(domain: "AIEnhancementClient", code: -5, 
+                              userInfo: [NSLocalizedDescriptionKey: NSLocalizedString("Ollama is not available. Please ensure it's running.", comment: "Error when Ollama service is unavailable")])

214-252: Consider making temperature and token limits part of the EnhancementOptions validation.

The limits for temperature and token count are enforced in the enhancement method rather than in the EnhancementOptions struct itself.

Consider validating these parameters in the EnhancementOptions initializer instead, ensuring that invalid values cannot be created in the first place:

struct EnhancementOptions {
    /// The prompt to send to the AI model for text enhancement
    var prompt: String
    
    /// Temperature controls randomness: lower values (0.1-0.3) are more precise,
    /// higher values (0.7-1.0) give more creative/varied results
    var temperature: Double
    
    /// Maximum number of tokens to generate in the response
    var maxTokens: Int
    
+    // Valid ranges for parameters
+    private static let minTemperature = 0.1
+    private static let maxTemperature = 1.0
+    private static let minTokens = 100
+    private static let maxTokens = 2000
+    
    /// Default prompt for enhancing transcribed text with clear instructions
    static let defaultPrompt = """
    // [existing prompt]
    """
    
    /// Default enhancement options for transcribed text
    static let `default` = EnhancementOptions(
        prompt: defaultPrompt,
        temperature: 0.3,
        maxTokens: 1000
    )
    
    /// Custom initialization with sensible defaults
    init(prompt: String = defaultPrompt, temperature: Double = 0.3, maxTokens: Int = 1000) {
        self.prompt = prompt
-        self.temperature = temperature
-        self.maxTokens = maxTokens
+        self.temperature = max(Self.minTemperature, min(Self.maxTemperature, temperature))
+        self.maxTokens = max(Self.minTokens, min(Self.maxTokens, maxTokens))
    }
}

Then in the enhanceWithOllama method:

-    // Build request parameters with appropriate defaults
-    let temperature = max(0.1, min(1.0, options.temperature)) // Ensure valid range
-    let maxTokens = max(100, min(2000, options.maxTokens))   // Reasonable limits
     
     let requestDict: [String: Any] = [
         "model": model,
         "prompt": fullPrompt,
-        "temperature": temperature,
-        "max_tokens": maxTokens,
+        "temperature": options.temperature,
+        "max_tokens": options.maxTokens,
         "stream": false,
         "system": "You are an AI that improves transcribed text while preserving meaning."
     ]

139-141: Consider making the Ollama API endpoint configurable.

The Ollama API endpoint is hardcoded in multiple places. Consider making it configurable, either through a configuration file or an environment variable, to support different setups.

class AIEnhancementClientLive {
+    // MARK: - Configuration
+    
+    private let ollamaBaseURL: String
+    
+    init(ollamaBaseURL: String = "http://localhost:11434") {
+        self.ollamaBaseURL = ollamaBaseURL
+    }
    
    // MARK: - Public Methods
    
    // ... existing code ...
    
    /// Checks if Ollama is available on the system
    func isOllamaAvailable() async -> Bool {
        // Simple check - try to connect to Ollama's API endpoint
        do {
-            var request = URLRequest(url: URL(string: "http://localhost:11434/api/version")!)
+            var request = URLRequest(url: URL(string: "\(ollamaBaseURL)/api/version")!)
            request.timeoutInterval = 5.0 // Longer timeout for more reliability

And similarly update the other hardcoded URLs.

Also applies to: 179-180, 225-229


99-131: Consider using a proper logging framework instead of print statements.

The code uses print statements for logging. Consider using a proper logging framework like os.log or a third-party solution that supports different log levels and better formatting.

Here's how you might use os.log instead:

+import os

class AIEnhancementClientLive {
+    // MARK: - Logging
+    
+    private let logger = Logger(subsystem: "com.yourcompany.Hex", category: "AIEnhancement")
    
    // MARK: - Public Methods
    
    /// Enhances text using a local AI model
    func enhance(text: String, model: String, options: EnhancementOptions, progressCallback: @escaping (Progress) -> Void) async throws -> String {
        // Skip if the text is empty or too short
        guard !text.isEmpty, text.count > 5 else {
-            print("[AIEnhancementClientLive] Text too short for enhancement, returning original")
+            logger.debug("Text too short for enhancement, returning original")
            return text
        }
        
        let progress = Progress(totalUnitCount: 100)
        progressCallback(progress)
        
-        print("[AIEnhancementClientLive] Starting text enhancement with model: \(model)")
-        print("[AIEnhancementClientLive] Text to enhance (\(text.count) chars): \"\(text.prefix(50))...\"")
+        logger.debug("Starting text enhancement with model: \(model)")
+        logger.debug("Text to enhance (\(text.count) chars): \"\(text.prefix(50))...\"")
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9a4c38b and 9e51c96.

📒 Files selected for processing (13)
  • Hex/Clients/AIEnhancementClient.swift (1 hunks)
  • Hex/Clients/PasteboardClient.swift (2 hunks)
  • Hex/Clients/RecordingClient.swift (2 hunks)
  • Hex/Clients/TranscriptionClient.swift (1 hunks)
  • Hex/Features/App/AppFeature.swift (3 hunks)
  • Hex/Features/Settings/AIEnhancementFeature.swift (1 hunks)
  • Hex/Features/Settings/AIEnhancementView.swift (1 hunks)
  • Hex/Features/Settings/SettingsFeature.swift (5 hunks)
  • Hex/Features/Settings/SettingsView.swift (1 hunks)
  • Hex/Features/Transcription/TranscriptionFeature.swift (6 hunks)
  • Hex/Features/Transcription/TranscriptionIndicatorView.swift (8 hunks)
  • Hex/Models/HexSettings.swift (5 hunks)
  • Localizable.xcstrings (17 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
Hex/Features/Settings/AIEnhancementView.swift (1)
Hex/Clients/AIEnhancementClient.swift (1)
  • isOllamaAvailable (136-159)
Hex/Clients/AIEnhancementClient.swift (1)
Hex/Clients/TranscriptionClient.swift (1)
  • getAvailableModels (198-200)
Hex/Features/Transcription/TranscriptionFeature.swift (1)
Hex/Clients/AIEnhancementClient.swift (1)
  • enhance (96-133)
🪛 SwiftLint (0.57.0)
Hex/Models/HexSettings.swift

[Warning] 127-127: Initializing an optional variable with nil is redundant

(redundant_optional_initialization)

Hex/Features/Settings/AIEnhancementFeature.swift

[Warning] 21-21: Initializing an optional variable with nil is redundant

(redundant_optional_initialization)

🔇 Additional comments (41)
Hex/Models/HexSettings.swift (6)

20-25: The AI enhancement settings are well organized and properly integrated.

The new settings for disabling auto-capitalization and AI enhancement options are properly added to the struct with appropriate defaults. The naming is clear and descriptive.


42-46: Good practice adding corresponding CodingKeys entries.

All the new properties have corresponding CodingKeys, which ensures proper JSON coding/decoding consistency.


62-67: Well-structured initializer parameters.

New parameters added to the initializer with appropriate default values that match the property defaults.


82-86: Proper initializer implementation.

All new properties are correctly initialized from the parameters.


116-122: Proper decoder handling for new properties.

The custom decoder correctly handles all new properties with appropriate default values if they are missing from the encoded data.


168-168: Good implementation of caching in the SharedReaderKey.

The update to use the cached settings is a nice optimization and maintains the same functionality.

Hex/Features/Settings/SettingsView.swift (1)

221-226: Well-integrated UI control for the new auto-capitalization setting.

The toggle is properly bound to the HexSettings property and includes clear descriptive text and an appropriate icon. It's consistently styled with other toggles in the General section.

Hex/Features/App/AppFeature.swift (3)

18-18: Good addition of the AI enhancement tab.

The new aiEnhancement case is properly added to the ActiveTab enum.


84-90: Well-structured UI integration for the AI enhancement feature.

The new AI Enhancement button is implemented consistently with other navigation items, using the same pattern for button styling and tagging.


110-112: Good integration of AIEnhancementView in the detail view.

The AIEnhancementView is properly scoped to the settings.aiEnhancement state and action, following the established pattern.

Hex/Clients/RecordingClient.swift (2)

384-384: Good improvement: Using let for immutable allocation.

The change from var to let for deviceNamePtr is good practice since it's only initialized once and never modified after allocation. This ensures immutability and prevents accidental modifications.


562-583: Nice optimization to throttle meter updates.

This implementation adds intelligent throttling for meter updates, which:

  • Reduces UI updates for imperceptible changes (< 0.05 for average, < 0.1 for peak)
  • Only forces updates every ~500ms (5 updates at 100ms intervals)
  • Improves performance and reduces resource usage while maintaining responsiveness
+  var lastMeter = Meter(averagePower: 0, peakPower: 0)
+  var updateCount = 0
+
+  // Only emit if there's a significant change, or every ~5 updates (500ms)
+  let significantChange = abs(currentMeter.averagePower - lastMeter.averagePower) > 0.05 ||
+                         abs(currentMeter.peakPower - lastMeter.peakPower) > 0.1
+  
+  if significantChange || updateCount >= 5 {
+    meterContinuation.yield(currentMeter)
+    lastMeter = currentMeter
+    updateCount = 0
+  } else {
+    updateCount += 1
+  }
Hex/Features/Settings/SettingsFeature.swift (5)

39-40: Well-structured integration of AIEnhancementFeature.

The AI enhancement feature is properly added as a property in the State struct, following the existing pattern for feature composition.


67-68: Consistent action enum extension.

The new action case for AI enhancement follows the established pattern in this reducer.


83-85: Good use of Scope for feature composition.

The AIEnhancementFeature is correctly scoped within the main reducer, ensuring proper separation of concerns.


112-123: Performance improvement for device refresh.

Two smart optimizations:

  1. Extended refresh interval from 120 to 180 seconds to reduce resource usage
  2. Added conditional refresh that only runs when app is active AND settings panel is visible

This will improve battery life and reduce unnecessary background processing.


298-300: Consistent handling of sub-feature actions.

The .aiEnhancement action handler follows the established pattern for delegating to the scoped sub-reducer.

Hex/Features/Transcription/TranscriptionIndicatorView.swift (6)

17-17: Well-integrated new enhancing status.

The new enhancing case is correctly added to the Status enum with a dedicated green color to visually distinguish it from other statuses.

Also applies to: 24-24


33-33: Consistent styling for enhancing status.

The enhancing status is properly handled in all styling computations (background, stroke, and inner shadow), maintaining visual consistency with existing states.

Also applies to: 44-44, 55-55


68-68: Complete visual integration of enhancing state.

The enhancement state is thoroughly implemented in:

  • Shadow effects with appropriate opacity levels
  • Glow effects using the green color
  • Animation effects with a dedicated counter variable

This provides a cohesive visual experience for the new state.

Also applies to: 108-117, 128-131


133-147: Optimized animation with consolidated task.

Excellent optimization to use a single animation task for both transcribing and enhancing states instead of separate tasks. The code:

  1. Only runs animation when needed (status check)
  2. Updates the correct counter based on current status
  3. Maintains the same timing for animations (0.3s)

This reduces resource usage while providing the same visual feedback.


150-151: Clear tooltip behavior for distinct states.

Good decision to explicitly limit the "Model prewarming..." tooltip to only appear for the prewarming state, keeping the UI clean during enhancement.


178-178: Complete preview with all states.

Adding the enhancing status to the preview ensures developers can test and verify all possible visual states.

Hex/Features/Settings/AIEnhancementView.swift (7)

11-54: Well-structured AIEnhancementView with conditional sections.

The view is well-organized with:

  • Logical section grouping
  • Conditional rendering based on feature enablement and Ollama availability
  • Appropriate task initialization for data loading
  • Clear section headers and explanatory footers

The form style and binding to the store follow SwiftUI best practices.


59-119: Informative connection status view for better user experience.

The connection status view provides:

  • Clear visual alert with appropriate icon and styling
  • Detailed setup instructions with bullet points
  • Actionable buttons for downloading Ollama and checking connection
  • Proper spacing and visual hierarchy

This helps users understand what's needed to make the feature work.


122-156: Good activation toggle with status feedback.

The toggle implementation:

  • Properly uses withLock for thread-safe settings updates
  • Triggers Ollama availability check when enabled
  • Shows connection status indicator when connected
  • Has clear explanatory text

I appreciate the visual indicator (green dot) when connected.


159-258: Complete model selection UI with all possible states.

The model selection section handles all states gracefully:

  • Loading state with progress indicator
  • Error state with message
  • Empty state with helpful link
  • Normal state with proper picker

The refresh button and explanatory footer provide good UX.


261-316: Well-designed temperature control with clear visual cues.

The temperature slider implementation:

  • Shows precise numeric value
  • Uses clear label indicators for "Precision" vs "Creativity"
  • Has appropriate range (0-1) and step (0.05)
  • Updates settings thread-safely with withLock
  • Includes explanatory text about the impact of different values

319-400: Versatile prompt configuration with expandable editing.

The prompt section offers a good balance of simplicity and power:

  • Collapsed view shows preview with limited lines
  • Expandable view provides full editing capability
  • Reset button to restore defaults
  • Monospaced font in editor for better code/prompt editing
  • Different footer text based on expanded state

The animation for expanding/collapsing is a nice touch.


402-413: Reusable bullet point helper for consistent formatting.

Good extraction of the bullet point rendering into a helper function for consistent styling and reuse throughout the view.

Hex/Clients/AIEnhancementClient.swift (6)

18-27: Good use of dependency injection pattern with TCA.

The AIEnhancementClient structure effectively uses the @DependencyClient macro for dependency injection, providing clear method signatures with sensible defaults. This follows the TCA pattern well and enables easy testing through dependency substitution.


30-70: Well-designed options struct with clear documentation.

The EnhancementOptions struct is well-designed with:

  • Clear documentation for each property
  • Appropriate default values
  • A detailed default prompt with specific instructions
  • Clean initialization with sensible defaults

This makes the API both easy to use with defaults and flexible for custom configurations.


136-159: Good implementation of Ollama availability check.

The isOllamaAvailable method is well-implemented with:

  • Appropriate timeout settings
  • Clear logging
  • Proper error handling that defaults to false when errors occur
  • Status code validation
  • Useful debug information

This should provide reliable detection of the Ollama service.


162-209: Well-structured model fetching implementation.

The getAvailableModels method provides a comprehensive implementation:

  • Clean nested structure for JSON decoding
  • Proper error handling at each step
  • Appropriate timeout settings
  • Result sorting for better UX
  • Detailed error messages with error propagation

This will provide reliable model listing functionality.


92-134: Well-structured error handling and progress reporting in the enhancement logic.

The enhance method contains robust error handling and progress reporting:

  • Checks for Ollama availability before proceeding
  • Propagates errors appropriately
  • Reports progress at key points in the process
  • Logs useful diagnostic information
  • Returns the original text when enhancement fails or isn't possible

This helps ensure a good user experience even when things go wrong.


214-329: Comprehensive implementation of text enhancement via Ollama API.

The enhanceWithOllama method provides a thorough implementation:

  • Input validation
  • Well-constructed prompt format
  • Parameter validation
  • Proper HTTP request setup
  • Comprehensive error handling with descriptive messages
  • Progress reporting at multiple stages
  • Response parsing and cleanup
  • Fallback to original text when needed

The implementation should provide reliable enhancement functionality with good error recovery.

Localizable.xcstrings (5)

203-207: New auto-capitalization feature strings.

The addition of "Disable auto-capitalization" and "Disable automatic capitalization in transcriptions" strings aligns with the PR objective mentioning the new disableAutoCapitalization setting. This provides the necessary UI text for the feature mentioned in the PR summary.


38-40: AI Enhancement feature localization keys added.

The addition of "AI Enhancement" and related description strings properly supports the new AI enhancement feature mentioned in the PR objectives. These strings provide the necessary UI text for the feature described in the PR summary.

Also applies to: 259-261, 672-673


422-432: Ollama status indicator strings added.

The Ollama connection status strings ("Ollama Connected", "Ollama Not Connected", "Ollama Status") provide clear feedback to users about the connection state for the AI enhancement feature, which is important for user experience when integrating with an external service.


152-153: Response style configuration strings added.

The strings related to "Creativity", "Precision", and "Response Style" along with explanatory text provide user-friendly controls for configuring the AI enhancement's behavior, allowing users to tailor the enhancement process to their needs.

Also applies to: 367-368, 492-493, 526-527


262-263: Enhancement prompt customization strings added.

The strings for "Enhancement Prompt", prompt editing instructions, and "Reset to Default" provide users with the ability to customize how the AI enhances their transcriptions, offering flexibility while maintaining the option to revert to defaults.

Also applies to: 371-372, 523-524, 655-656

Comment on lines +8 to +54
import ComposableArchitecture
import SwiftUI

struct AIEnhancementView: View {
@Bindable var store: StoreOf<AIEnhancementFeature>
@State private var showExpandedPrompt = false
@State private var isHoveringModelSelect = false

var body: some View {
Form {
// Activation Section (Always show this first)
Section {
activationToggle
} header: {
Text("AI Enhancement")
} footer: {
Text("Enable AI to improve transcription quality by fixing grammar, formatting, and errors.")
.foregroundColor(.secondary.opacity(0.7))
.font(.caption)
}

// Only show other settings if AI enhancement is enabled
if store.hexSettings.useAIEnhancement {
// Connection Status Section (only if AI enhancement is enabled)
if !store.isOllamaAvailable {
Section {
connectionStatusView
} header: {
Text("Ollama Status")
}
}

// Model Selection Section
modelSelectionSection

// Temperature Control Section
temperatureSection

// Prompt Configuration Section
promptSection
}
}
.formStyle(.grouped)
.task {
await store.send(.task).finish()
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify ability to detect Ollama timeout issues.

The current implementation relies on the AIEnhancementClient's availability check, but there might be edge cases where Ollama appears available but becomes unresponsive during actual usage.


🏁 Script executed:

#!/bin/bash
# Check the timeout handling in AIEnhancementClient's isOllamaAvailable method

# Look for timeout configuration in AIEnhancementClient
rg -A 2 "timeoutInterval" --glob "**/*EnhancementClient*"

# Check error handling for connection issues 
rg -A 5 "try await" --glob "**/*EnhancementClient*"

Length of output: 3778


🏁 Script executed:

#!/bin/bash
# Extract the isOllamaAvailable implementation
rg -A20 "func isOllamaAvailable" --glob "Hex/Clients/AIEnhancementClient.swift"

echo "--------------------------------------------------"

# Extract the generation call (enhanceWithOllama) implementation
rg -A20 "func enhanceWithOllama" --glob "Hex/Clients/AIEnhancementClient.swift"

Length of output: 4135


Handle Ollama timeouts and connectivity failures during enhancement

Although isOllamaAvailable() uses a 5 s timeout and returns false on error, the actual generation call in enhanceWithOllama(…) (60 s timeout) simply propagates any thrown error. This means the UI may think Ollama is available but then hang or crash on a timeout. We need to:

• Add explicit error handling around the URLSession call in enhanceWithOllama to catch timeouts and other network errors, then translate them into a user‐facing state (e.g. mark Ollama unavailable, show an error banner).
• In your feature reducer (or wherever .enhance is called), catch enhancement errors and dispatch an action to recheck availability or present an alert.

Example diff in AIEnhancementClient.swift:

     // generation call
-    let (data, response) = try await URLSession.shared.data(for: request)
+    let (data, response): (Data, URLResponse)
+    do {
+      (data, response) = try await URLSession.shared.data(for: request)
+    } catch {
+      // Treat timeouts and connectivity issues as “Ollama unavailable”
+      print("[AIEnhancementClientLive] Generation failed: \(error.localizedDescription)")
+      throw NSError(domain: "AIEnhancementClient",
+                    code: -1001, // NSURLErrorTimedOut or similar
+                    userInfo: [NSLocalizedDescriptionKey: "Ollama is unresponsive"])
+    }

And in your reducer:

  • Catch the thrown error from enhance, send a new .ollamaBecameUnavailable action.
  • In response, re-run isOllamaAvailable() and display an alert if still down.

This ensures the user never hits a silent timeout and always sees a clear “Ollama unavailable” state.

Committable suggestion skipped: line range outside the PR's diff.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
Hex/Features/Transcription/TranscriptionFeature.swift (1)

396-435: ⚠️ Potential issue

Compile-time error – missing argument labels when calling enhance

AIEnhancementClient.enhance expects external labels text:model:options:, but the call is missing them.

-let enhancedText = try await aiEnhancement.enhance(result, model, options) { _ in }
+let enhancedText = try await aiEnhancement.enhance(
+    text: result,
+    model: model,
+    options: options
+) { _ in }
🧹 Nitpick comments (2)
Hex/Features/Transcription/TranscriptionFeature.swift (2)

433-435: Consider documenting the cancellation decision more clearly.

The code avoids making the AI enhancement cancellable due to potential issues. This reasoning should be better documented for future maintainers to understand the rationale.

// Don't make this cancellable to avoid premature cancellation
// This may have been causing the issue with the enhancement being cancelled
+// Note: AI enhancement operations may take longer than typical network operations.
+// Making this cancellable was causing the enhancement to be prematurely cancelled when
+// another action was dispatched or when the user interacted with the UI.

534-540: Commented-out code needs explanation.

The decision not to cancel AI enhancement during the cancel flow should be better documented.

// Don't cancel AI enhancement as it might cause issues
-// .cancel(id: CancelID.aiEnhancement),
+// AI enhancement is intentionally not cancelled here because:
+// 1. It might be near completion when cancelled, wasting processing
+// 2. It can cause race conditions with result handling
+// .cancel(id: CancelID.aiEnhancement),
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9e51c96 and 27fef1e.

📒 Files selected for processing (6)
  • Hex.xcodeproj/project.pbxproj (6 hunks)
  • Hex/Clients/AIEnhancementClient.swift (1 hunks)
  • Hex/Clients/RecordingClient.swift (2 hunks)
  • Hex/Clients/TranscriptionClient.swift (4 hunks)
  • Hex/Features/Settings/SettingsFeature.swift (5 hunks)
  • Hex/Features/Transcription/TranscriptionFeature.swift (8 hunks)
✅ Files skipped from review due to trivial changes (1)
  • Hex.xcodeproj/project.pbxproj
🚧 Files skipped from review as they are similar to previous changes (4)
  • Hex/Clients/RecordingClient.swift
  • Hex/Features/Settings/SettingsFeature.swift
  • Hex/Clients/TranscriptionClient.swift
  • Hex/Clients/AIEnhancementClient.swift
🔇 Additional comments (8)
Hex/Features/Transcription/TranscriptionFeature.swift (8)

22-22: State tracking addition looks good.

Adding the isEnhancing state flag properly tracks when AI enhancement is active, maintaining consistency with other state flags in the struct.


50-55: Well-structured action cases for AI enhancement flow.

The new action cases properly handle all aspects of the AI enhancement flow, including state management, results, errors, and Ollama availability.


62-62: Cancel ID for AI enhancement added correctly.

This addition follows the established pattern for other cancellable operations in the codebase.


70-70: Dependency injection properly implemented.

The aiEnhancement dependency is correctly injected following the established pattern.


120-154: Well-implemented AI enhancement action handlers.

The implementation checks for Ollama connectivity issues specifically and triggers availability rechecks when needed. Good error handling and logging.


357-391: Good conditional flow for AI enhancement.

The transcription result handler now intelligently routes the result through AI enhancement when enabled, with proper state management.


438-460: AI enhancement result handler looks good.

The handler properly resets state and finalizes the transcript similarly to the original flow.


582-583: Good UI state prioritization for enhancing status.

The status logic correctly prioritizes showing the enhancing status before transcribing or recording states.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🔭 Outside diff range comments (1)
Hex/Features/Transcription/TranscriptionFeature.swift (1)

158-164: 🛠️ Refactor suggestion

Update cancel guard condition to include isEnhancing

The cancel handler has a guard condition that only runs if isRecording or isTranscribing are true, but it doesn't check isEnhancing. This might lead to inconsistencies if cancel is triggered during enhancement.

Update the guard condition to include all relevant states:

case .cancel:
  // Only cancel if we're in the middle of recording or transcribing
- guard state.isRecording || state.isTranscribing else {
+ guard state.isRecording || state.isTranscribing || state.isEnhancing else {
    return .none
  }
  return handleCancel(&state)
♻️ Duplicate comments (1)
Hex/Features/Transcription/TranscriptionFeature.swift (1)

423-425: ⚠️ Potential issue

Missing argument labels in enhance method call

The AIEnhancementClient.enhance method likely requires argument labels (text:model:options:), but they're omitted here.

The call should include the external parameter labels to prevent compilation errors:

-let enhancedText = try await aiEnhancement.enhance(result, model, options) { progress in
+let enhancedText = try await aiEnhancement.enhance(
+  text: result,
+  model: model,
+  options: options
+) { progress in
  // Optional: Could update UI with progress information here if needed
}
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 423-423: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🧹 Nitpick comments (1)
Hex/Features/Transcription/TranscriptionFeature.swift (1)

423-425: Unused closure parameters flagged by static analysis

There are two instances of unused parameters in closures that should be replaced with _ as recommended by SwiftLint.

Apply these changes:

// In recheckOllamaAvailability handler
-return .run { send in
+return .run { _ in

// In enhanceWithAI method
-let enhancedText = try await aiEnhancement.enhance(...) { progress in
+let enhancedText = try await aiEnhancement.enhance(...) { _ in

Also applies to: 147-148

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 423-423: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 27fef1e and 25679ae.

📒 Files selected for processing (1)
  • Hex/Features/Transcription/TranscriptionFeature.swift (8 hunks)
🧰 Additional context used
🪛 SwiftLint (0.57.0)
Hex/Features/Transcription/TranscriptionFeature.swift

[Warning] 147-147: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


[Warning] 423-423: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🔇 Additional comments (1)
Hex/Features/Transcription/TranscriptionFeature.swift (1)

435-437: Uncancellable AI enhancement operation

The AI enhancement effect is deliberately not made cancellable (with a comment explaining it avoids premature cancellation), but this creates inconsistency with the cancel handler which resets the isEnhancing state flag without actually cancelling the operation.

This could lead to a situation where the UI shows enhancement is not happening (after cancel), but the enhancement is still running in the background. Consider the implications:

  1. What happens if the enhancement completes after cancellation?
  2. Could this lead to unexpected state or race conditions?

You might need additional safeguards in handleAIEnhancement to check if the operation was cancelled before applying the result.

Comment on lines 147 to 154
return .run { send in
let isAvailable = await aiEnhancement.isOllamaAvailable()
if !isAvailable {
// Could dispatch to a UI state to show an alert or notification
print("[TranscriptionFeature] Ollama is not available. AI enhancement is disabled.")
// Here you would typically update UI state to show an alert
}
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Incomplete handling of Ollama unavailability

The rechecking logic detects Ollama unavailability but only logs it without updating any UI state or taking corrective action. Users won't be notified of the issue.

Consider updating to actually dispatch an action that updates the UI when Ollama is unavailable:

case .recheckOllamaAvailability:
  // Recheck if Ollama is available and update UI accordingly
  return .run { send in
    let isAvailable = await aiEnhancement.isOllamaAvailable()
    if !isAvailable {
      // Could dispatch to a UI state to show an alert or notification
      print("[TranscriptionFeature] Ollama is not available. AI enhancement is disabled.")
      // Here you would typically update UI state to show an alert
+     await send(.someActionToShowOllamaUnavailableAlert)
    }
  }

🏁 Script executed:

#!/bin/bash
# Verify if there's any UI state or user notification mechanism implemented elsewhere

# Look for UI alerts or notifications related to Ollama availability
rg -A 3 -B 3 "Ollama.*not available|ollamaUnavailable" --glob "*.swift"

Length of output: 2932


Dispatch a UI action in TranscriptionFeature for Ollama unavailability

The .recheckOllamaAvailability branch only logs the error but doesn’t surface it to the user. Your Settings view already shows a message when store.isOllamaAvailable is false—do the same in Transcription:

• File: Hex/Features/Transcription/TranscriptionFeature.swift (Lines 147–154)
• Add a new action in TranscriptionAction, e.g. .ollamaUnavailable
• Update the reducer:

case .recheckOllamaAvailability:
  return .run { send in
    let isAvailable = await aiEnhancement.isOllamaAvailable()
    if !isAvailable {
      print("[TranscriptionFeature] Ollama is not available. AI enhancement is disabled.")
+     await send(.ollamaUnavailable)
    }
  }

• Handle .ollamaUnavailable in the reducer to set a UI flag (e.g. state.showOllamaAlert = true)
• In TranscriptionView, bind an Alert to state.showOllamaAlert, similar to AIEnhancementView.swift

This ensures users receive an immediate notification when Ollama can’t be reached.

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 147-147: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

@0ni-x4
Copy link

0ni-x4 commented May 13, 2025

i want this!!

@plyght
Copy link
Contributor Author

plyght commented May 13, 2025

@0ni-x4 motivated me to finish this. will get it going!!

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
Hex/Features/Transcription/TranscriptionFeature.swift (4)

154-162: Missing UI feedback for Ollama unavailability.

The rechecking logic detects Ollama unavailability but only logs it without updating any UI state or taking corrective action. Users won't be notified of the issue.

#!/bin/bash
# Verify if there's any UI state update or user notification mechanism implemented elsewhere

# Look for UI alerts or notifications related to Ollama availability
rg -A 3 -B 3 "Ollama.*not available|ollamaUnavailable" --glob "*.swift"
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 155-155: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


579-581: Potential UI state inconsistency with commented cancellation code.

The cancel handler resets the isEnhancing state but explicitly avoids cancelling the AI enhancement effect. This creates a visual/UI inconsistency where the UI indicates cancellation but the operation continues in the background.

Consider these options:

  1. Make the AI enhancement cancellable and actually cancel it
  2. Add state tracking to ignore enhancement results if cancel was requested
  3. Document this behavior clearly for future maintainers
// Don't cancel AI enhancement as it might cause issues
// .cancel(id: CancelID.aiEnhancement),
+ // TODO: This creates a potential inconsistency - the UI shows cancellation
+ // but enhancement continues in background. Consider implementing a safer
+ // cancellation approach or state tracking to ignore late results.

138-147: ⚠️ Potential issue

Incomplete error handling in AI enhancement.

The comment states "For other errors, just use the original transcription," but the code returns .none which doesn't actually restore or use the original transcription result. When a non-Ollama error occurs, the transcription might be lost.

case let .aiEnhancementError(error):
  // Check if this is an Ollama connectivity error
  let nsError = error as NSError
  if nsError.domain == "AIEnhancementClient" && (nsError.code == -1001 || nsError.localizedDescription.contains("Ollama")) {
    print("AI Enhancement error due to Ollama connectivity: \(error)")
    return .send(.ollamaBecameUnavailable)
  } else {
    // For other errors, just use the original transcription
    print("AI Enhancement error: \(error)")
-   return .none
+   // Pass the original transcription result through
+   return .send(.transcriptionResult(result))
  }

433-475: ⚠️ Potential issue

Incorrect parameter passing in AI enhancement call.

The enhance method is called without parameter labels, but AIEnhancementClient.enhance requires external labels (text:, model:, options:).

-          let enhancedText = try await aiEnhancement.enhance(result, model, options) { progress in
+          let enhancedText = try await aiEnhancement.enhance(
+            text: result,
+            model: model,
+            options: options
+          ) { progress in
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 461-461: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🧹 Nitpick comments (1)
Hex/Features/Transcription/TranscriptionFeature.swift (1)

461-461: Unused parameter in closure.

The progress parameter in the closure is unused. Replace it with _ to avoid the SwiftLint warning.

-          let enhancedText = try await aiEnhancement.enhance(result, model, options) { progress in
+          let enhancedText = try await aiEnhancement.enhance(result, model, options) { _ in
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 461-461: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25679ae and e2ce7ca.

📒 Files selected for processing (5)
  • Hex/Clients/KeyEventMonitorClient.swift (3 hunks)
  • Hex/Clients/PasteboardClient.swift (2 hunks)
  • Hex/Clients/RecordingClient.swift (2 hunks)
  • Hex/Features/Transcription/TranscriptionFeature.swift (10 hunks)
  • Hex/Features/Transcription/TranscriptionIndicatorView.swift (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • Hex/Features/Transcription/TranscriptionIndicatorView.swift
🧰 Additional context used
🧬 Code Graph Analysis (1)
Hex/Features/Transcription/TranscriptionFeature.swift (3)
Hex/Clients/AIEnhancementClient.swift (2)
  • isOllamaAvailable (136-159)
  • enhance (96-133)
Hex/Clients/RecordingClient.swift (1)
  • observeAudioLevel (621-623)
Hex/Clients/TranscriptionClient.swift (1)
  • transcribe (206-247)
🪛 SwiftLint (0.57.0)
Hex/Features/Transcription/TranscriptionFeature.swift

[Warning] 155-155: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


[Warning] 185-185: Initializing an optional variable with nil is redundant

(redundant_optional_initialization)


[Warning] 461-461: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🔇 Additional comments (19)
Hex/Clients/KeyEventMonitorClient.swift (2)

11-36: Well-designed thread safety implementation for Sauce library.

This new SafeSauce enum provides an excellent thread-safe wrapper around Sauce library calls, effectively preventing _dispatch_assert_queue_fail errors. The implementation correctly handles the case when code is already on the main thread versus when it needs to dispatch.


47-47: Good use of the thread-safe wrapper.

The KeyEvent initializer now correctly uses the thread-safe wrapper to prevent potential crashes from background thread access.

Hex/Clients/RecordingClient.swift (2)

384-384: Appropriate use of let for immutable pointer allocation.

Changed from var to let which is correct since this pointer doesn't need to be mutable after allocation.


562-612: Excellent adaptive throttling for audio metering.

The implementation adds sophisticated adaptive throttling that:

  1. Adjusts sampling intervals based on activity level (80-150ms)
  2. Only emits updates when there are significant changes
  3. Has smart fallbacks to ensure UI responsiveness

This will reduce CPU usage and improve battery life while maintaining responsive UI feedback.

Hex/Clients/PasteboardClient.swift (6)

68-92: Efficient pasteboard backup implementation.

This new approach using NSPasteboard's native capabilities is more efficient than manually copying data items. The use of a unique temporary pasteboard is elegant and avoids potential data loss issues.


95-109: Clean pasteboard restoration with proper cleanup.

The restoration process correctly handles the pasteboard state and cleans up the temporary pasteboard to avoid memory leaks.


112-115: Good practice keeping the legacy method with a warning.

Maintaining backward compatibility while clearly marking this method as deprecated will help with future code maintenance.


164-166: Smart conditional pasteboard backup.

Only saving the pasteboard state when clipboard retention is disabled is a good optimization that avoids unnecessary work.


180-211: Thread-safe key code retrieval and efficient event posting.

The thread safety check and main thread dispatching for key code retrieval aligns with the improvements in KeyEventMonitorClient. Using autoreleasepool for the event posting sequence ensures proper resource management.


222-229: Appropriate delay and cleanup handling.

Adding a delay before restoration gives the paste operation time to complete, and wrapping the restoration in an autoreleasepool helps with memory management.

Hex/Features/Transcription/TranscriptionFeature.swift (9)

22-22: Good addition of enhancing state tracking.

Adding the isEnhancing state variable allows proper UI feedback during the AI enhancement process.


50-56: Well-defined actions for AI enhancement flow.

The new actions clearly separate different aspects of the AI enhancement process, promoting a clean state management approach.


70-70: Added AI enhancement dependency.

Correctly added the dependency to access AI enhancement functionality.


89-97: Optimized audio level update logic.

The conditional update based on significant changes is a good optimization that reduces unnecessary state updates and UI refreshes.


183-214: Excellent audio meter update optimization.

The rate limiting and significance threshold for audio meter updates will reduce UI updates and improve performance, especially during quieter periods.

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 185-185: Initializing an optional variable with nil is redundant

(redundant_optional_initialization)


375-375: Good integration of HexSettings with transcription client.

Passing the settings to the transcription client enables features like disabling auto-capitalization.


394-429: Well-structured conditional AI enhancement.

The transcription result handler now correctly branches based on user settings, either proceeding to AI enhancement or finalizing the transcription directly. The code properly extracts necessary settings values.


477-500: Complete AI enhancement result handling.

The implementation correctly updates all relevant state variables and proceeds to finalize the recording with the enhanced transcript.


622-623: Good UI status prioritization.

Prioritizing the .enhancing state in the UI status logic ensures users get appropriate feedback during the AI enhancement process.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
Hex/Features/Transcription/TranscriptionFeature.swift (1)

466-509: 🛠️ Refactor suggestion

Fix argument labels in AIEnhancement.enhance call.

The code bypasses Swift's argument labels by directly accessing the enhance method. This is error-prone and less readable.

- // Access the raw value directly to avoid argument label issues
- let enhanceMethod = aiEnhancement.enhance
- let enhancedText = try await enhanceMethod(result, model, options) { progress in
+ // Use proper argument labels for better readability and type safety
+ let enhancedText = try await aiEnhancement.enhance(
+   text: result, 
+   model: model, 
+   options: options
+ ) { progress in
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 495-495: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

🧹 Nitpick comments (2)
Hex/Features/Transcription/TranscriptionFeature.swift (2)

234-242: Enhance meter update with for-where pattern.

Consider using a Swift for-where pattern to simplify the conditional meter updates.

- for await meter in await recording.observeAudioLevel() {
-   // Check if we should send this update
-   if await rateLimiter.shouldUpdate(meter: meter) {
-     // The Effect.run captures its function as @Sendable, so we're already on an appropriate context
-     // for sending actions. ComposableArchitecture handles dispatching to the main thread as needed.
-       await send(.audioLevelUpdated(meter))
-   }
+ for await meter in await recording.observeAudioLevel() where await rateLimiter.shouldUpdate(meter: meter) {
+   // The Effect.run captures its function as @Sendable, so we're already on an appropriate context
+   // for sending actions. ComposableArchitecture handles dispatching to the main thread as needed.
+     await send(.audioLevelUpdated(meter))
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 239-239: where clauses are preferred over a single if inside a for

(for_where)


495-495: Replace unused closure parameter with underscore.

The progress parameter in the closure is unused and should be replaced with an underscore.

- let enhancedText = try await enhanceMethod(result, model, options) { progress in
+ let enhancedText = try await enhanceMethod(result, model, options) { _ in
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 495-495: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e2ce7ca and dd4356f.

📒 Files selected for processing (2)
  • Hex.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved (2 hunks)
  • Hex/Features/Transcription/TranscriptionFeature.swift (10 hunks)
✅ Files skipped from review due to trivial changes (1)
  • Hex.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved
🧰 Additional context used
🪛 SwiftLint (0.57.0)
Hex/Features/Transcription/TranscriptionFeature.swift

[Warning] 239-239: where clauses are preferred over a single if inside a for

(for_where)


[Warning] 201-201: Initializing an optional variable with nil is redundant

(redundant_optional_initialization)


[Warning] 495-495: Unused parameter in a closure should be replaced with _

(unused_closure_parameter)


[Warning] 618-618: TODOs should be resolved (Consider implementing a safer ...)

(todo)

🔇 Additional comments (16)
Hex/Features/Transcription/TranscriptionFeature.swift (16)

22-23: State management for tracking AI enhancement progress looks good.

The new isEnhancing state properly tracks when AI enhancement is active, providing a clear separation between transcription and enhancement states.


27-27: Well-designed fallback mechanism for transcription.

Using pendingTranscription as a fallback storage is an excellent approach to ensure the original transcription is preserved in case of AI enhancement failures.


51-56: Comprehensive action enumeration for AI enhancement flow.

The action cases cover all necessary states for the AI enhancement flow including state changes, results, error handling, and Ollama availability checks.


63-63: LGTM: New cancellation ID added for AI enhancement.

Properly defines a cancellation ID for the AI enhancement operation.


71-71: LGTM: Dependency injection for AI enhancement client.

Correctly uses dependency injection for the AI enhancement client following TCA patterns.


90-98: Optimized meter updates improve UI performance.

The optimization to only update the meter when there's a significant change or during active recording reduces unnecessary UI updates.


131-133: Clean state management for AI enhancement status.

Simple and direct state management for the enhancing state.


135-136: Proper handling of AI enhancement results.

Correctly delegates to a helper method to handle successful AI enhancement results.


161-178: Properly implemented Ollama availability rechecking.

Correctly handles the case when Ollama becomes unavailable, showing an error to the user.


198-232: Well-implemented actor for meter rate limiting.

The MeterRateLimiter actor is a good implementation for Swift concurrency. It safely manages state and implements an adaptive threshold for meter updates.

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 201-201: Initializing an optional variable with nil is redundant

(redundant_optional_initialization)


404-404: LGTM: Updated transcription call with settings parameter.

Properly passes the settings to respect configuration options like disableAutoCapitalization.


424-460: Well-designed enhancement decision flow.

Good conditional logic to determine whether AI enhancement should be applied based on user settings.


512-535: LGTM: Proper AI enhancement result handling.

The handler correctly updates all relevant state properties and proceeds with storing the transcript.


609-620: Addressed cancellation concerns with detailed comment.

The code intentionally avoids canceling AI enhancement operations to prevent Ollama API issues. The comment clearly explains the trade-off and plans for future improvements.

🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 618-618: TODOs should be resolved (Consider implementing a safer ...)

(todo)


662-673: LGTM: Updated status logic with enhancement state.

The status logic correctly prioritizes showing the enhancing state when active.


138-159:

Details

✅ Verification successful

Consider improving error recovery flow to avoid false positives.

The error handling correctly identifies Ollama connectivity issues, but there's a potential edge case where pendingTranscription might be nil, which would result in empty text being processed.


🏁 Script executed:

#!/bin/bash
# Check if there are cases where pendingTranscription could be nil when error handling is triggered

# Look for places where pendingTranscription is set to nil
rg "pendingTranscription = nil" --type swift

# Look for places where aiEnhancementError action is sent without setting pendingTranscription
rg "send\(\.aiEnhancementError" --type swift -A 10 -B 10

Length of output: 2321


To confirm where and how pendingTranscription is set, let’s list all usages in the reducer around that property:


🏁 Script executed:

#!/bin/bash
rg "pendingTranscription" -n Hex/Features/Transcription/TranscriptionFeature.swift -A 5 -B 5

Length of output: 2204


Error recovery flow is safe—no pendingTranscription nil scenario

  • state.pendingTranscription is set at line 429 before the AI enhancement call.
  • It’s only cleared on success at line 519, so in the .aiEnhancementError case it will always be non-nil.
  • The ?? "" fallback further guards against any unexpected nil, preventing crashes.

No further changes needed.

@kitlangton
Copy link
Owner

Let me know when you're ready for a final review :D This looks like it's gonna be great!

@lkeude96
Copy link

lkeude96 commented Jun 2, 2025

Super pumped for this @plyght this will unlock lot 🙌

@plyght
Copy link
Contributor Author

plyght commented Jun 3, 2025

@lkeude96 appreciate it! sorry it's taking a while to finish... there's some performance issues :/

@kitlangton actually would be great if you could take a look and help me diagnose why the spinner wheel is super laggy!

@VipinReddy
Copy link

I understand, I might be asking for more, is there an option to add another bigger model that supports my 48 gig MacBook Pro M4 other than Ollama

@plyght
Copy link
Contributor Author

plyght commented Jun 3, 2025

@VipinReddy all https://ollama.com models will be supported for the text enhancement.

@devtanna
Copy link

Super excited about this!

@shirishpothi
Copy link

Cmon, really looking forward to this!

@plyght
Copy link
Contributor Author

plyght commented Nov 17, 2025

@kitlangton would love your help on this to fix the performance of the bubble when transcribing, etc. I've seen that you'd like to reduce redundancy and too many options in settings. please let me know what you'd like me to fix, change, etc here, before merging. I will fix the merge conflicts!

@supnim
Copy link

supnim commented Dec 16, 2025

buzzin for this!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants