Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions Fluid.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
7CFA1D0B2F500000C0DEF001 /* TypingServiceTransientPasteboardTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7CFA1D0B2F500000C0DEF002 /* TypingServiceTransientPasteboardTests.swift */; };
803000000000000000000002 /* MediaPlaybackServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 803000000000000000000001 /* MediaPlaybackServiceTests.swift */; };
803000000000000000000003 /* MediaRemoteAdapter in Frameworks */ = {isa = PBXBuildFile; productRef = 803000000000000000000004 /* MediaRemoteAdapter */; };
FEED67600000000000000002 /* PasteDeliveryCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = FEED67600000000000000001 /* PasteDeliveryCoordinatorTests.swift */; };
7CDB0A2F2F3C4D5600FB7CAD /* dictation_fixture.wav in Resources */ = {isa = PBXBuildFile; fileRef = 7CDB0A2B2F3C4D5600FB7CAD /* dictation_fixture.wav */; };
7CDB0A302F3C4D5600FB7CAD /* XCTest.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CDB0A2C2F3C4D5600FB7CAD /* XCTest.framework */; };
7CE006BD2E80EBE600DDCCD6 /* AppUpdater in Frameworks */ = {isa = PBXBuildFile; productRef = 7CE006BC2E80EBE600DDCCD6 /* AppUpdater */; };
Expand Down Expand Up @@ -66,6 +67,7 @@
F1BD00000000000000000001 /* StripThinkingTagsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StripThinkingTagsTests.swift; sourceTree = "<group>"; };
C0DE63600000000000000001 /* AudioEngineRetirementDrainTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioEngineRetirementDrainTests.swift; sourceTree = "<group>"; };
DA7100010000000000000001 /* DirectAudioReliabilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DirectAudioReliabilityTests.swift; sourceTree = "<group>"; };
FEED67600000000000000001 /* PasteDeliveryCoordinatorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteDeliveryCoordinatorTests.swift; sourceTree = "<group>"; };
7C078D8F2E3B339200FB7CAC /* FluidVoice Debug.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "FluidVoice Debug.app"; sourceTree = BUILT_PRODUCTS_DIR; };
7C91B0022F42AA0100C0DEF0 /* HotkeyShortcutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HotkeyShortcutTests.swift; sourceTree = "<group>"; };
7CDB0A202F3C4D5600FB7CAD /* FluidDictationIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FluidDictationIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
Expand Down Expand Up @@ -155,6 +157,7 @@
DA7100010000000000000001 /* DirectAudioReliabilityTests.swift */,
7CFA1D0B2F500000C0DEF002 /* TypingServiceTransientPasteboardTests.swift */,
803000000000000000000001 /* MediaPlaybackServiceTests.swift */,
FEED67600000000000000001 /* PasteDeliveryCoordinatorTests.swift */,
);
path = FluidDictationIntegrationTests;
sourceTree = "<group>";
Expand Down Expand Up @@ -324,6 +327,7 @@
DA7100020000000000000002 /* DirectAudioReliabilityTests.swift in Sources */,
7CFA1D0B2F500000C0DEF001 /* TypingServiceTransientPasteboardTests.swift in Sources */,
803000000000000000000002 /* MediaPlaybackServiceTests.swift in Sources */,
FEED67600000000000000002 /* PasteDeliveryCoordinatorTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
275 changes: 164 additions & 111 deletions Sources/Fluid/ContentView.swift

Large diffs are not rendered by default.

20 changes: 14 additions & 6 deletions Sources/Fluid/Persistence/SettingsStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ final class SettingsStore: ObservableObject {
self.repairForcedOnboardingResetIfNeeded()
self.migrateOverlayBottomOffsetTo50IfNeeded()
self.migratePrivateAIContextDefaultTo4KIfNeeded()
Self.migrateTextInsertionModeToReliablePasteIfNeeded(defaults: self.defaults)
self.refreshLaunchAtStartupStatus(clearError: true, logMismatch: false)
}

Expand Down Expand Up @@ -5263,6 +5264,7 @@ private extension SettingsStore {
static let enableAIStreaming = "EnableAIStreaming"
static let copyTranscriptionToClipboard = "CopyTranscriptionToClipboard"
static let textInsertionMode = "TextInsertionMode"
static let reliablePasteMigrationV1 = "TextInsertionModeMigratedToReliablePasteV1"
static let autoUpdateCheckEnabled = "AutoUpdateCheckEnabled"
static let betaReleasesEnabled = "BetaReleasesEnabled"
static let lastUpdateCheckDate = "LastUpdateCheckDate"
Expand Down Expand Up @@ -5392,9 +5394,15 @@ private extension SettingsStore {
}

extension SettingsStore {
static func migrateTextInsertionModeToReliablePasteIfNeeded(defaults: UserDefaults) {
guard !defaults.bool(forKey: Keys.reliablePasteMigrationV1) else { return }
defaults.set(TextInsertionMode.reliablePaste.rawValue, forKey: Keys.textInsertionMode)
defaults.set(true, forKey: Keys.reliablePasteMigrationV1)
Comment thread
greptile-apps[bot] marked this conversation as resolved.
}

enum TextInsertionMode: String, CaseIterable, Identifiable, Codable {
case standard
case reliablePaste
case standard

var id: String {
self.rawValue
Expand All @@ -5403,18 +5411,18 @@ extension SettingsStore {
var displayName: String {
switch self {
case .standard:
return "Clipboard Free Insert"
return "Direct Paste"
case .reliablePaste:
return "Clipboard Paste"
return "Clipboard Paste (Recommended)"
}
}

var description: String {
switch self {
case .standard:
return "Fastest path. Inserts text without changing the clipboard, with paste fallback if direct insertion is unavailable."
return "Posts text directly without changing the clipboard. Some editors may reject or truncate it."
case .reliablePaste:
return "Compatibility path. Uses a temporary clipboard paste and restores your previous clipboard after insertion."
return "Fast, compatible insertion using a temporary clipboard entry that is restored after paste."
}
}
}
Expand All @@ -5424,7 +5432,7 @@ extension SettingsStore {
guard let raw = self.defaults.string(forKey: Keys.textInsertionMode),
let mode = TextInsertionMode(rawValue: raw)
else {
return .standard
return .reliablePaste
}
return mode
}
Expand Down
34 changes: 25 additions & 9 deletions Sources/Fluid/Services/ASRService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4785,20 +4785,34 @@ final class ASRService: ObservableObject {

private let typingService = TypingService() // Reuse instance to avoid conflicts

func typeTextToActiveField(_ text: String) {
self.typeTextToActiveField(text, preferredTargetPID: nil, textReadyAt: nil)
@MainActor
func typeTextToActiveField(_ text: String) async -> TextDeliveryResult {
await self.typeTextToActiveField(text, preferredTargetPID: nil, textReadyAt: nil)
}

func typeTextToActiveField(_ text: String, preferredTargetPID: pid_t?, textReadyAt: TimeInterval? = nil) {
self.typeOutputPlanToActiveField(.plain(text), preferredTargetPID: preferredTargetPID, textReadyAt: textReadyAt)
@MainActor
func typeTextToActiveField(
_ text: String,
preferredTargetPID: pid_t?,
textReadyAt: TimeInterval? = nil,
preserveTranscriptOnClipboard: Bool = false
) async -> TextDeliveryResult {
await self.typeOutputPlanToActiveField(
.plain(text),
preferredTargetPID: preferredTargetPID,
textReadyAt: textReadyAt,
preserveTranscriptOnClipboard: preserveTranscriptOnClipboard
)
}

@MainActor
func typeOutputPlanToActiveField(
_ plan: DictationLiteralOutputPlan,
preferredTargetPID: pid_t?,
textReadyAt: TimeInterval? = nil,
tracksDictionaryCorrections: Bool = false
) {
tracksDictionaryCorrections: Bool = false,
preserveTranscriptOnClipboard: Bool = false
) async -> TextDeliveryResult {
let requestedAt = ProcessInfo.processInfo.systemUptime
let textReadyAge = textReadyAt.map { Int(((requestedAt - $0) * 1000).rounded()) }
let text = plan.plainText
Expand All @@ -4807,21 +4821,23 @@ final class ASRService: ObservableObject {
message: "asr_type_request chars=\(text.count) preferredPID=\(preferredTargetPID.map { String($0) } ?? "nil") textReadyAgeMs=\(textReadyAge.map { String($0) } ?? "nil")",
source: "TypingBenchmark"
)
self.typingService.typeOutputPlanInstantly(
let result = await self.typingService.typeOutputPlanInstantly(
plan,
preferredTargetPID: preferredTargetPID,
textReadyAt: textReadyAt,
tracksDictionaryCorrections: tracksDictionaryCorrections
tracksDictionaryCorrections: tracksDictionaryCorrections,
preserveTranscriptOnClipboard: preserveTranscriptOnClipboard
)
let dispatchedAt = ProcessInfo.processInfo.systemUptime
let textReadyToDispatchMs = textReadyAt.map {
String(Int(((dispatchedAt - $0) * 1000).rounded()))
} ?? "nil"
DebugLogger.shared.benchmark(
"TYPING_BENCH",
message: "asr_type_dispatched chars=\(text.count) preferredPID=\(preferredTargetPID.map { String($0) } ?? "nil") textReadyToDispatchMs=\(textReadyToDispatchMs)",
message: "asr_type_dispatched chars=\(text.count) preferredPID=\(preferredTargetPID.map { String($0) } ?? "nil") result=\(String(describing: result)) textReadyToDispatchMs=\(textReadyToDispatchMs)",
source: "TypingBenchmark"
)
return result
}

/// Removes filler sounds from transcribed text
Expand Down
Loading
Loading