feat: share wake gate via SwabbleKit
This commit is contained in:
@@ -2,6 +2,7 @@ import AVFAudio
|
||||
import Foundation
|
||||
import Observation
|
||||
import Speech
|
||||
import SwabbleKit
|
||||
|
||||
private func makeAudioTapEnqueueCallback(queue: AudioBufferQueue) -> @Sendable (AVAudioPCMBuffer, AVAudioTime) -> Void {
|
||||
{ buffer, _ in
|
||||
@@ -289,15 +290,18 @@ final class VoiceWakeManager: NSObject {
|
||||
private nonisolated func makeRecognitionResultHandler() -> @Sendable (SFSpeechRecognitionResult?, Error?) -> Void {
|
||||
{ [weak self] result, error in
|
||||
let transcript = result?.bestTranscription.formattedString
|
||||
let segments = result.flatMap { result in
|
||||
transcript.map { WakeWordSpeechSegments.from(transcription: result.bestTranscription, transcript: $0) }
|
||||
} ?? []
|
||||
let errorText = error?.localizedDescription
|
||||
|
||||
Task { @MainActor in
|
||||
self?.handleRecognitionCallback(transcript: transcript, errorText: errorText)
|
||||
self?.handleRecognitionCallback(transcript: transcript, segments: segments, errorText: errorText)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func handleRecognitionCallback(transcript: String?, errorText: String?) {
|
||||
private func handleRecognitionCallback(transcript: String?, segments: [WakeWordSegment], errorText: String?) {
|
||||
if let errorText {
|
||||
self.statusText = "Recognizer error: \(errorText)"
|
||||
self.isListening = false
|
||||
@@ -313,7 +317,7 @@ final class VoiceWakeManager: NSObject {
|
||||
}
|
||||
|
||||
guard let transcript else { return }
|
||||
guard let cmd = self.extractCommand(from: transcript) else { return }
|
||||
guard let cmd = self.extractCommand(from: transcript, segments: segments) else { return }
|
||||
|
||||
if cmd == self.lastDispatched { return }
|
||||
self.lastDispatched = cmd
|
||||
@@ -334,30 +338,18 @@ final class VoiceWakeManager: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
private func extractCommand(from transcript: String) -> String? {
|
||||
Self.extractCommand(from: transcript, triggers: self.activeTriggerWords)
|
||||
private func extractCommand(from transcript: String, segments: [WakeWordSegment]) -> String? {
|
||||
Self.extractCommand(from: transcript, segments: segments, triggers: self.activeTriggerWords)
|
||||
}
|
||||
|
||||
nonisolated static func extractCommand(from transcript: String, triggers: [String]) -> String? {
|
||||
var bestRange: Range<String.Index>?
|
||||
for trigger in triggers {
|
||||
let token = trigger.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !token.isEmpty else { continue }
|
||||
guard let range = transcript.range(of: token, options: [.caseInsensitive, .backwards]) else { continue }
|
||||
if let currentBest = bestRange {
|
||||
if range.lowerBound > currentBest.lowerBound {
|
||||
bestRange = range
|
||||
}
|
||||
} else {
|
||||
bestRange = range
|
||||
}
|
||||
}
|
||||
|
||||
guard let bestRange else { return nil }
|
||||
let after = transcript[bestRange.upperBound...]
|
||||
let trimmed = after.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
guard !trimmed.isEmpty else { return nil }
|
||||
return String(trimmed)
|
||||
nonisolated static func extractCommand(
|
||||
from transcript: String,
|
||||
segments: [WakeWordSegment],
|
||||
triggers: [String],
|
||||
minPostTriggerGap: TimeInterval = 0.45) -> String?
|
||||
{
|
||||
let config = WakeWordGateConfig(triggers: triggers, minPostTriggerGap: minPostTriggerGap)
|
||||
return WakeWordGate.match(transcript: transcript, segments: segments, config: config)?.command
|
||||
}
|
||||
|
||||
private static func configureAudioSession() throws {
|
||||
|
||||
@@ -54,3 +54,4 @@ Sources/Voice/VoiceWakePreferences.swift
|
||||
../shared/ClawdisKit/Sources/ClawdisKit/ScreenCommands.swift
|
||||
../shared/ClawdisKit/Sources/ClawdisKit/StoragePaths.swift
|
||||
../shared/ClawdisKit/Sources/ClawdisKit/SystemCommands.swift
|
||||
../../Swabble/Sources/SwabbleKit/WakeWordGate.swift
|
||||
|
||||
@@ -1,33 +1,90 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
import SwabbleKit
|
||||
@testable import Clawdis
|
||||
|
||||
@Suite struct VoiceWakeManagerExtractCommandTests {
|
||||
@Test func extractCommandReturnsNilWhenNoTriggerFound() {
|
||||
#expect(VoiceWakeManager.extractCommand(from: "hello world", triggers: ["clawd"]) == nil)
|
||||
let transcript = "hello world"
|
||||
let segments = makeSegments(
|
||||
transcript: transcript,
|
||||
words: [("hello", 0.0, 0.1), ("world", 0.2, 0.1)])
|
||||
#expect(VoiceWakeManager.extractCommand(from: transcript, segments: segments, triggers: ["clawd"]) == nil)
|
||||
}
|
||||
|
||||
@Test func extractCommandTrimsTokensAndResult() {
|
||||
let cmd = VoiceWakeManager.extractCommand(from: "hey clawd do thing ", triggers: [" clawd "])
|
||||
let transcript = "hey clawd do thing"
|
||||
let segments = makeSegments(
|
||||
transcript: transcript,
|
||||
words: [
|
||||
("hey", 0.0, 0.1),
|
||||
("clawd", 0.2, 0.1),
|
||||
("do", 0.9, 0.1),
|
||||
("thing", 1.1, 0.1),
|
||||
])
|
||||
let cmd = VoiceWakeManager.extractCommand(
|
||||
from: transcript,
|
||||
segments: segments,
|
||||
triggers: [" clawd "],
|
||||
minPostTriggerGap: 0.3)
|
||||
#expect(cmd == "do thing")
|
||||
}
|
||||
|
||||
@Test func extractCommandPicksLatestTriggerOccurrence() {
|
||||
let transcript = "clawd first\nthen something\nclaude second"
|
||||
let cmd = VoiceWakeManager.extractCommand(from: transcript, triggers: ["clawd", "claude"])
|
||||
#expect(cmd == "second")
|
||||
}
|
||||
|
||||
@Test func extractCommandIsCaseInsensitive() {
|
||||
let cmd = VoiceWakeManager.extractCommand(from: "HELLO CLAWD run it", triggers: ["clawd"])
|
||||
#expect(cmd == "run it")
|
||||
@Test func extractCommandReturnsNilWhenGapTooShort() {
|
||||
let transcript = "hey clawd do thing"
|
||||
let segments = makeSegments(
|
||||
transcript: transcript,
|
||||
words: [
|
||||
("hey", 0.0, 0.1),
|
||||
("clawd", 0.2, 0.1),
|
||||
("do", 0.35, 0.1),
|
||||
("thing", 0.5, 0.1),
|
||||
])
|
||||
let cmd = VoiceWakeManager.extractCommand(
|
||||
from: transcript,
|
||||
segments: segments,
|
||||
triggers: ["clawd"],
|
||||
minPostTriggerGap: 0.3)
|
||||
#expect(cmd == nil)
|
||||
}
|
||||
|
||||
@Test func extractCommandReturnsNilWhenNothingAfterTrigger() {
|
||||
#expect(VoiceWakeManager.extractCommand(from: "hey clawd \n", triggers: ["clawd"]) == nil)
|
||||
let transcript = "hey clawd"
|
||||
let segments = makeSegments(
|
||||
transcript: transcript,
|
||||
words: [("hey", 0.0, 0.1), ("clawd", 0.2, 0.1)])
|
||||
#expect(VoiceWakeManager.extractCommand(from: transcript, segments: segments, triggers: ["clawd"]) == nil)
|
||||
}
|
||||
|
||||
@Test func extractCommandIgnoresEmptyTriggers() {
|
||||
let cmd = VoiceWakeManager.extractCommand(from: "hey clawd do thing", triggers: ["", " ", "clawd"])
|
||||
let transcript = "hey clawd do thing"
|
||||
let segments = makeSegments(
|
||||
transcript: transcript,
|
||||
words: [
|
||||
("hey", 0.0, 0.1),
|
||||
("clawd", 0.2, 0.1),
|
||||
("do", 0.9, 0.1),
|
||||
("thing", 1.1, 0.1),
|
||||
])
|
||||
let cmd = VoiceWakeManager.extractCommand(
|
||||
from: transcript,
|
||||
segments: segments,
|
||||
triggers: ["", " ", "clawd"],
|
||||
minPostTriggerGap: 0.3)
|
||||
#expect(cmd == "do thing")
|
||||
}
|
||||
}
|
||||
|
||||
private func makeSegments(
|
||||
transcript: String,
|
||||
words: [(String, TimeInterval, TimeInterval)])
|
||||
-> [WakeWordSegment] {
|
||||
var searchStart = transcript.startIndex
|
||||
var output: [WakeWordSegment] = []
|
||||
for (word, start, duration) in words {
|
||||
let range = transcript.range(of: word, range: searchStart..<transcript.endIndex)
|
||||
output.append(WakeWordSegment(text: word, start: start, duration: duration, range: range))
|
||||
if let range { searchStart = range.upperBound }
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ options:
|
||||
packages:
|
||||
ClawdisKit:
|
||||
path: ../shared/ClawdisKit
|
||||
Swabble:
|
||||
path: ../../Swabble
|
||||
|
||||
schemes:
|
||||
Clawdis:
|
||||
@@ -29,6 +31,8 @@ targets:
|
||||
- package: ClawdisKit
|
||||
- package: ClawdisKit
|
||||
product: ClawdisChatUI
|
||||
- package: Swabble
|
||||
product: SwabbleKit
|
||||
- sdk: AppIntents.framework
|
||||
preBuildScripts:
|
||||
- name: SwiftFormat (lint)
|
||||
@@ -86,6 +90,8 @@ targets:
|
||||
- path: Tests
|
||||
dependencies:
|
||||
- target: Clawdis
|
||||
- package: Swabble
|
||||
product: SwabbleKit
|
||||
settings:
|
||||
base:
|
||||
PRODUCT_BUNDLE_IDENTIFIER: com.steipete.clawdis.ios.tests
|
||||
|
||||
@@ -17,6 +17,7 @@ let package = Package(
|
||||
.package(url: "https://github.com/swiftlang/swift-subprocess.git", from: "0.1.0"),
|
||||
.package(url: "https://github.com/sparkle-project/Sparkle", from: "2.8.1"),
|
||||
.package(path: "../shared/ClawdisKit"),
|
||||
.package(path: "../../Swabble"),
|
||||
.package(path: "../../Peekaboo/Core/PeekabooCore"),
|
||||
.package(path: "../../Peekaboo/Core/PeekabooAutomationKit"),
|
||||
],
|
||||
@@ -41,6 +42,7 @@ let package = Package(
|
||||
"ClawdisProtocol",
|
||||
.product(name: "ClawdisKit", package: "ClawdisKit"),
|
||||
.product(name: "ClawdisChatUI", package: "ClawdisKit"),
|
||||
.product(name: "SwabbleKit", package: "swabble"),
|
||||
.product(name: "MenuBarExtraAccess", package: "MenuBarExtraAccess"),
|
||||
.product(name: "Subprocess", package: "swift-subprocess"),
|
||||
.product(name: "Sparkle", package: "Sparkle"),
|
||||
@@ -56,7 +58,12 @@ let package = Package(
|
||||
]),
|
||||
.testTarget(
|
||||
name: "ClawdisIPCTests",
|
||||
dependencies: ["ClawdisIPC", "Clawdis", "ClawdisProtocol"],
|
||||
dependencies: [
|
||||
"ClawdisIPC",
|
||||
"Clawdis",
|
||||
"ClawdisProtocol",
|
||||
.product(name: "SwabbleKit", package: "swabble"),
|
||||
],
|
||||
swiftSettings: [
|
||||
.enableUpcomingFeature("StrictConcurrency"),
|
||||
.enableExperimentalFeature("SwiftTesting"),
|
||||
|
||||
@@ -2,6 +2,7 @@ import AVFoundation
|
||||
import Foundation
|
||||
import OSLog
|
||||
import Speech
|
||||
import SwabbleKit
|
||||
#if canImport(AppKit)
|
||||
import AppKit
|
||||
#endif
|
||||
@@ -35,6 +36,7 @@ actor VoiceWakeRuntime {
|
||||
private var currentConfig: RuntimeConfig?
|
||||
private var listeningState: ListeningState = .idle
|
||||
private var overlayToken: UUID?
|
||||
private var activeTriggerEndTime: TimeInterval?
|
||||
|
||||
// Tunables
|
||||
// Silence threshold once we've captured user speech (post-trigger).
|
||||
@@ -147,9 +149,13 @@ actor VoiceWakeRuntime {
|
||||
self.recognitionTask = recognizer.recognitionTask(with: request) { [weak self, generation] result, error in
|
||||
guard let self else { return }
|
||||
let transcript = result?.bestTranscription.formattedString
|
||||
let segments = result.flatMap { result in
|
||||
transcript.map { WakeWordSpeechSegments.from(transcription: result.bestTranscription, transcript: $0) }
|
||||
} ?? []
|
||||
let isFinal = result?.isFinal ?? false
|
||||
Task { await self.handleRecognition(
|
||||
transcript: transcript,
|
||||
segments: segments,
|
||||
isFinal: isFinal,
|
||||
error: error,
|
||||
config: config,
|
||||
@@ -184,6 +190,7 @@ actor VoiceWakeRuntime {
|
||||
self.audioEngine = nil
|
||||
self.currentConfig = nil
|
||||
self.listeningState = .idle
|
||||
self.activeTriggerEndTime = nil
|
||||
self.logger.debug("voicewake runtime stopped")
|
||||
DiagnosticsFileLog.shared.log(category: "voicewake.runtime", event: "stopped")
|
||||
|
||||
@@ -206,6 +213,7 @@ actor VoiceWakeRuntime {
|
||||
|
||||
private func handleRecognition(
|
||||
transcript: String?,
|
||||
segments: [WakeWordSegment],
|
||||
isFinal: Bool,
|
||||
error: Error?,
|
||||
config: RuntimeConfig,
|
||||
@@ -224,7 +232,11 @@ actor VoiceWakeRuntime {
|
||||
if !transcript.isEmpty {
|
||||
self.lastHeard = now
|
||||
if self.isCapturing {
|
||||
let trimmed = Self.trimmedAfterTrigger(transcript, triggers: config.triggers)
|
||||
let trimmed = Self.commandAfterTrigger(
|
||||
transcript: transcript,
|
||||
segments: segments,
|
||||
triggerEndTime: self.activeTriggerEndTime,
|
||||
triggers: config.triggers)
|
||||
self.capturedTranscript = trimmed
|
||||
self.updateHeardBeyondTrigger(withTrimmed: trimmed)
|
||||
if isFinal {
|
||||
@@ -252,37 +264,27 @@ actor VoiceWakeRuntime {
|
||||
|
||||
if self.isCapturing { return }
|
||||
|
||||
if Self.matches(text: transcript, triggers: config.triggers) {
|
||||
let gateConfig = WakeWordGateConfig(triggers: config.triggers)
|
||||
if let match = WakeWordGate.match(transcript: transcript, segments: segments, config: gateConfig) {
|
||||
if let cooldown = cooldownUntil, now < cooldown {
|
||||
return
|
||||
}
|
||||
await self.beginCapture(transcript: transcript, config: config)
|
||||
await self.beginCapture(command: match.command, triggerEndTime: match.triggerEndTime, config: config)
|
||||
}
|
||||
}
|
||||
|
||||
private static func matches(text: String, triggers: [String]) -> Bool {
|
||||
guard !text.isEmpty else { return false }
|
||||
let normalized = text.lowercased()
|
||||
for trigger in triggers {
|
||||
let t = trigger.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if t.isEmpty { continue }
|
||||
if normalized.contains(t) { return true }
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func beginCapture(transcript: String, config: RuntimeConfig) async {
|
||||
private func beginCapture(command: String, triggerEndTime: TimeInterval, config: RuntimeConfig) async {
|
||||
self.listeningState = .voiceWake
|
||||
self.isCapturing = true
|
||||
DiagnosticsFileLog.shared.log(category: "voicewake.runtime", event: "beginCapture")
|
||||
let trimmed = Self.trimmedAfterTrigger(transcript, triggers: config.triggers)
|
||||
self.capturedTranscript = trimmed
|
||||
self.capturedTranscript = command
|
||||
self.committedTranscript = ""
|
||||
self.volatileTranscript = trimmed
|
||||
self.volatileTranscript = command
|
||||
self.captureStartedAt = Date()
|
||||
self.cooldownUntil = nil
|
||||
self.heardBeyondTrigger = !trimmed.isEmpty
|
||||
self.heardBeyondTrigger = !command.isEmpty
|
||||
self.triggerChimePlayed = false
|
||||
self.activeTriggerEndTime = triggerEndTime
|
||||
|
||||
if config.triggerChime != .none, !self.triggerChimePlayed {
|
||||
self.triggerChimePlayed = true
|
||||
@@ -354,6 +356,7 @@ actor VoiceWakeRuntime {
|
||||
self.lastHeard = nil
|
||||
self.heardBeyondTrigger = false
|
||||
self.triggerChimePlayed = false
|
||||
self.activeTriggerEndTime = nil
|
||||
|
||||
await MainActor.run { AppStateStore.shared.stopVoiceEars() }
|
||||
if let token = self.overlayToken {
|
||||
@@ -467,6 +470,22 @@ actor VoiceWakeRuntime {
|
||||
return text
|
||||
}
|
||||
|
||||
private static func commandAfterTrigger(
|
||||
transcript: String,
|
||||
segments: [WakeWordSegment],
|
||||
triggerEndTime: TimeInterval?,
|
||||
triggers: [String]) -> String
|
||||
{
|
||||
guard let triggerEndTime else {
|
||||
return trimmedAfterTrigger(transcript, triggers: triggers)
|
||||
}
|
||||
let trimmed = WakeWordGate.commandText(
|
||||
transcript: transcript,
|
||||
segments: segments,
|
||||
triggerEndTime: triggerEndTime)
|
||||
return trimmed.isEmpty ? trimmedAfterTrigger(transcript, triggers: triggers) : trimmed
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
static func _testTrimmedAfterTrigger(_ text: String, triggers: [String]) -> String {
|
||||
self.trimmedAfterTrigger(text, triggers: triggers)
|
||||
@@ -481,9 +500,6 @@ actor VoiceWakeRuntime {
|
||||
.attribute(.foregroundColor, at: 0, effectiveRange: nil) as? NSColor ?? .clear
|
||||
}
|
||||
|
||||
static func _testMatches(text: String, triggers: [String]) -> Bool {
|
||||
self.matches(text: text, triggers: triggers)
|
||||
}
|
||||
#endif
|
||||
|
||||
private static func delta(after committed: String, current: String) -> String {
|
||||
|
||||
@@ -2,6 +2,7 @@ import AVFoundation
|
||||
import Foundation
|
||||
import OSLog
|
||||
import Speech
|
||||
import SwabbleKit
|
||||
|
||||
enum VoiceWakeTestState: Equatable {
|
||||
case idle
|
||||
@@ -93,14 +94,16 @@ final class VoiceWakeTester {
|
||||
self.recognitionTask = recognizer.recognitionTask(with: request) { [weak self] result, error in
|
||||
guard let self, !self.isStopping else { return }
|
||||
let text = result?.bestTranscription.formattedString ?? ""
|
||||
let matched = Self.matches(text: text, triggers: triggers)
|
||||
let segments = result.map { WakeWordSpeechSegments.from(transcription: $0.bestTranscription, transcript: text) } ?? []
|
||||
let gateConfig = WakeWordGateConfig(triggers: triggers)
|
||||
let match = WakeWordGate.match(transcript: text, segments: segments, config: gateConfig)
|
||||
let isFinal = result?.isFinal ?? false
|
||||
let errorMessage = error?.localizedDescription
|
||||
|
||||
Task { [weak self] in
|
||||
guard let self, !self.isStopping else { return }
|
||||
await self.handleResult(
|
||||
matched: matched,
|
||||
match: match,
|
||||
text: text,
|
||||
isFinal: isFinal,
|
||||
errorMessage: errorMessage,
|
||||
@@ -120,7 +123,7 @@ final class VoiceWakeTester {
|
||||
}
|
||||
|
||||
private func handleResult(
|
||||
matched: Bool,
|
||||
match: WakeWordGateMatch?,
|
||||
text: String,
|
||||
isFinal: Bool,
|
||||
errorMessage: String?,
|
||||
@@ -129,15 +132,15 @@ final class VoiceWakeTester {
|
||||
if !text.isEmpty {
|
||||
self.lastHeard = Date()
|
||||
}
|
||||
if matched, !text.isEmpty {
|
||||
if let match, !match.command.isEmpty {
|
||||
self.holdingAfterDetect = true
|
||||
self.detectedText = text
|
||||
self.logger.info("voice wake detected; forwarding (len=\(text.count))")
|
||||
self.detectedText = match.command
|
||||
self.logger.info("voice wake detected; forwarding (len=\(match.command.count))")
|
||||
await MainActor.run { AppStateStore.shared.triggerVoiceEars(ttl: nil) }
|
||||
Task.detached {
|
||||
await VoiceWakeForwarder.forward(transcript: text)
|
||||
await VoiceWakeForwarder.forward(transcript: match.command)
|
||||
}
|
||||
Task { @MainActor in onUpdate(.detected(text)) }
|
||||
Task { @MainActor in onUpdate(.detected(match.command)) }
|
||||
self.holdUntilSilence(onUpdate: onUpdate)
|
||||
return
|
||||
}
|
||||
@@ -187,15 +190,6 @@ final class VoiceWakeTester {
|
||||
_ = preferredMicID
|
||||
}
|
||||
|
||||
private static func matches(text: String, triggers: [String]) -> Bool {
|
||||
let lowered = text.lowercased()
|
||||
return triggers.contains { lowered.contains($0.lowercased()) }
|
||||
}
|
||||
|
||||
static func _testMatches(text: String, triggers: [String]) -> Bool {
|
||||
self.matches(text: text, triggers: triggers)
|
||||
}
|
||||
|
||||
private nonisolated static func ensurePermissions() async throws -> Bool {
|
||||
let speechStatus = SFSpeechRecognizer.authorizationStatus()
|
||||
if speechStatus == .notDetermined {
|
||||
|
||||
@@ -1,23 +1,9 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
import SwabbleKit
|
||||
@testable import Clawdis
|
||||
|
||||
@Suite struct VoiceWakeRuntimeTests {
|
||||
@Test func matchesIsCaseInsensitive() {
|
||||
let triggers = ["ClAwD", "buddy"]
|
||||
#expect(VoiceWakeRuntime._testMatches(text: "hey clawd are you there", triggers: triggers))
|
||||
#expect(!VoiceWakeRuntime._testMatches(text: "nothing to see", triggers: triggers))
|
||||
}
|
||||
|
||||
@Test func matchesIgnoresWhitespace() {
|
||||
let triggers = [" claude "]
|
||||
#expect(VoiceWakeRuntime._testMatches(text: "hello claude!", triggers: triggers))
|
||||
}
|
||||
|
||||
@Test func matchesSkipsEmptyTriggers() {
|
||||
let triggers = [" ", ""]
|
||||
#expect(!VoiceWakeRuntime._testMatches(text: "hello", triggers: triggers))
|
||||
}
|
||||
|
||||
@Test func trimsAfterTriggerKeepsPostSpeech() {
|
||||
let triggers = ["claude", "clawd"]
|
||||
let text = "hey Claude how are you"
|
||||
@@ -48,4 +34,46 @@ import Testing
|
||||
let text = "claude write a note"
|
||||
#expect(VoiceWakeRuntime._testHasContentAfterTrigger(text, triggers: triggers))
|
||||
}
|
||||
|
||||
@Test func gateRequiresGapBetweenTriggerAndCommand() {
|
||||
let transcript = "hey clawd do thing"
|
||||
let segments = makeSegments(
|
||||
transcript: transcript,
|
||||
words: [
|
||||
("hey", 0.0, 0.1),
|
||||
("clawd", 0.2, 0.1),
|
||||
("do", 0.35, 0.1),
|
||||
("thing", 0.5, 0.1),
|
||||
])
|
||||
let config = WakeWordGateConfig(triggers: ["clawd"], minPostTriggerGap: 0.3)
|
||||
#expect(WakeWordGate.match(transcript: transcript, segments: segments, config: config) == nil)
|
||||
}
|
||||
|
||||
@Test func gateAcceptsGapAndExtractsCommand() {
|
||||
let transcript = "hey clawd do thing"
|
||||
let segments = makeSegments(
|
||||
transcript: transcript,
|
||||
words: [
|
||||
("hey", 0.0, 0.1),
|
||||
("clawd", 0.2, 0.1),
|
||||
("do", 0.9, 0.1),
|
||||
("thing", 1.1, 0.1),
|
||||
])
|
||||
let config = WakeWordGateConfig(triggers: ["clawd"], minPostTriggerGap: 0.3)
|
||||
#expect(WakeWordGate.match(transcript: transcript, segments: segments, config: config)?.command == "do thing")
|
||||
}
|
||||
}
|
||||
|
||||
private func makeSegments(
|
||||
transcript: String,
|
||||
words: [(String, TimeInterval, TimeInterval)])
|
||||
-> [WakeWordSegment] {
|
||||
var searchStart = transcript.startIndex
|
||||
var output: [WakeWordSegment] = []
|
||||
for (word, start, duration) in words {
|
||||
let range = transcript.range(of: word, range: searchStart..<transcript.endIndex)
|
||||
output.append(WakeWordSegment(text: word, start: start, duration: duration, range: range))
|
||||
if let range { searchStart = range.upperBound }
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
@@ -1,15 +1,47 @@
|
||||
import Foundation
|
||||
import Testing
|
||||
@testable import Clawdis
|
||||
import SwabbleKit
|
||||
|
||||
struct VoiceWakeTesterTests {
|
||||
@Test func matchesIsCaseInsensitiveAndSubstring() {
|
||||
let triggers = ["Claude", "wake word"]
|
||||
#expect(VoiceWakeTester._testMatches(text: "hey claude are you there", triggers: triggers))
|
||||
#expect(VoiceWakeTester._testMatches(text: "this has wake word inside", triggers: triggers))
|
||||
@Test func matchRespectsGapRequirement() {
|
||||
let transcript = "hey claude do thing"
|
||||
let segments = makeSegments(
|
||||
transcript: transcript,
|
||||
words: [
|
||||
("hey", 0.0, 0.1),
|
||||
("claude", 0.2, 0.1),
|
||||
("do", 0.35, 0.1),
|
||||
("thing", 0.5, 0.1),
|
||||
])
|
||||
let config = WakeWordGateConfig(triggers: ["claude"], minPostTriggerGap: 0.3)
|
||||
#expect(WakeWordGate.match(transcript: transcript, segments: segments, config: config) == nil)
|
||||
}
|
||||
|
||||
@Test func matchesReturnsFalseWhenNoTrigger() {
|
||||
let triggers = ["claude"]
|
||||
#expect(!VoiceWakeTester._testMatches(text: "random text", triggers: triggers))
|
||||
@Test func matchReturnsCommandAfterGap() {
|
||||
let transcript = "hey claude do thing"
|
||||
let segments = makeSegments(
|
||||
transcript: transcript,
|
||||
words: [
|
||||
("hey", 0.0, 0.1),
|
||||
("claude", 0.2, 0.1),
|
||||
("do", 0.8, 0.1),
|
||||
("thing", 1.0, 0.1),
|
||||
])
|
||||
let config = WakeWordGateConfig(triggers: ["claude"], minPostTriggerGap: 0.3)
|
||||
#expect(WakeWordGate.match(transcript: transcript, segments: segments, config: config)?.command == "do thing")
|
||||
}
|
||||
}
|
||||
|
||||
private func makeSegments(
|
||||
transcript: String,
|
||||
words: [(String, TimeInterval, TimeInterval)])
|
||||
-> [WakeWordSegment] {
|
||||
var searchStart = transcript.startIndex
|
||||
var output: [WakeWordSegment] = []
|
||||
for (word, start, duration) in words {
|
||||
let range = transcript.range(of: word, range: searchStart..<transcript.endIndex)
|
||||
output.append(WakeWordSegment(text: word, start: start, duration: duration, range: range))
|
||||
if let range { searchStart = range.upperBound }
|
||||
}
|
||||
return output
|
||||
}
|
||||
|
||||
1
apps/shared/ClawdisKit/.build/.lock
Normal file
1
apps/shared/ClawdisKit/.build/.lock
Normal file
@@ -0,0 +1 @@
|
||||
50345
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1,311 @@
|
||||
// Generated by Apple Swift version 6.2.3 (swiftlang-6.2.3.3.21 clang-1700.6.3.2)
|
||||
#ifndef CLAWDISCHATUI_SWIFT_H
|
||||
#define CLAWDISCHATUI_SWIFT_H
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wgcc-compat"
|
||||
|
||||
#if !defined(__has_include)
|
||||
# define __has_include(x) 0
|
||||
#endif
|
||||
#if !defined(__has_attribute)
|
||||
# define __has_attribute(x) 0
|
||||
#endif
|
||||
#if !defined(__has_feature)
|
||||
# define __has_feature(x) 0
|
||||
#endif
|
||||
#if !defined(__has_warning)
|
||||
# define __has_warning(x) 0
|
||||
#endif
|
||||
|
||||
#if __has_include(<swift/objc-prologue.h>)
|
||||
# include <swift/objc-prologue.h>
|
||||
#endif
|
||||
|
||||
#pragma clang diagnostic ignored "-Wauto-import"
|
||||
#if defined(__OBJC__)
|
||||
#include <Foundation/Foundation.h>
|
||||
#endif
|
||||
#if defined(__cplusplus)
|
||||
#include <cstdint>
|
||||
#include <cstddef>
|
||||
#include <cstdbool>
|
||||
#include <cstring>
|
||||
#include <stdlib.h>
|
||||
#include <new>
|
||||
#include <type_traits>
|
||||
#else
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#endif
|
||||
#if defined(__cplusplus)
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wnon-modular-include-in-framework-module"
|
||||
#if defined(__arm64e__) && __has_include(<ptrauth.h>)
|
||||
# include <ptrauth.h>
|
||||
#else
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wreserved-macro-identifier"
|
||||
# ifndef __ptrauth_swift_value_witness_function_pointer
|
||||
# define __ptrauth_swift_value_witness_function_pointer(x)
|
||||
# endif
|
||||
# ifndef __ptrauth_swift_class_method_pointer
|
||||
# define __ptrauth_swift_class_method_pointer(x)
|
||||
# endif
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
|
||||
#if !defined(SWIFT_TYPEDEFS)
|
||||
# define SWIFT_TYPEDEFS 1
|
||||
# if __has_include(<uchar.h>)
|
||||
# include <uchar.h>
|
||||
# elif !defined(__cplusplus)
|
||||
typedef unsigned char char8_t;
|
||||
typedef uint_least16_t char16_t;
|
||||
typedef uint_least32_t char32_t;
|
||||
# endif
|
||||
typedef float swift_float2 __attribute__((__ext_vector_type__(2)));
|
||||
typedef float swift_float3 __attribute__((__ext_vector_type__(3)));
|
||||
typedef float swift_float4 __attribute__((__ext_vector_type__(4)));
|
||||
typedef double swift_double2 __attribute__((__ext_vector_type__(2)));
|
||||
typedef double swift_double3 __attribute__((__ext_vector_type__(3)));
|
||||
typedef double swift_double4 __attribute__((__ext_vector_type__(4)));
|
||||
typedef int swift_int2 __attribute__((__ext_vector_type__(2)));
|
||||
typedef int swift_int3 __attribute__((__ext_vector_type__(3)));
|
||||
typedef int swift_int4 __attribute__((__ext_vector_type__(4)));
|
||||
typedef unsigned int swift_uint2 __attribute__((__ext_vector_type__(2)));
|
||||
typedef unsigned int swift_uint3 __attribute__((__ext_vector_type__(3)));
|
||||
typedef unsigned int swift_uint4 __attribute__((__ext_vector_type__(4)));
|
||||
#endif
|
||||
|
||||
#if !defined(SWIFT_PASTE)
|
||||
# define SWIFT_PASTE_HELPER(x, y) x##y
|
||||
# define SWIFT_PASTE(x, y) SWIFT_PASTE_HELPER(x, y)
|
||||
#endif
|
||||
#if !defined(SWIFT_METATYPE)
|
||||
# define SWIFT_METATYPE(X) Class
|
||||
#endif
|
||||
#if !defined(SWIFT_CLASS_PROPERTY)
|
||||
# if __has_feature(objc_class_property)
|
||||
# define SWIFT_CLASS_PROPERTY(...) __VA_ARGS__
|
||||
# else
|
||||
# define SWIFT_CLASS_PROPERTY(...)
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(SWIFT_RUNTIME_NAME)
|
||||
# if __has_attribute(objc_runtime_name)
|
||||
# define SWIFT_RUNTIME_NAME(X) __attribute__((objc_runtime_name(X)))
|
||||
# else
|
||||
# define SWIFT_RUNTIME_NAME(X)
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(SWIFT_COMPILE_NAME)
|
||||
# if __has_attribute(swift_name)
|
||||
# define SWIFT_COMPILE_NAME(X) __attribute__((swift_name(X)))
|
||||
# else
|
||||
# define SWIFT_COMPILE_NAME(X)
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(SWIFT_METHOD_FAMILY)
|
||||
# if __has_attribute(objc_method_family)
|
||||
# define SWIFT_METHOD_FAMILY(X) __attribute__((objc_method_family(X)))
|
||||
# else
|
||||
# define SWIFT_METHOD_FAMILY(X)
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(SWIFT_NOESCAPE)
|
||||
# if __has_attribute(noescape)
|
||||
# define SWIFT_NOESCAPE __attribute__((noescape))
|
||||
# else
|
||||
# define SWIFT_NOESCAPE
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(SWIFT_RELEASES_ARGUMENT)
|
||||
# if __has_attribute(ns_consumed)
|
||||
# define SWIFT_RELEASES_ARGUMENT __attribute__((ns_consumed))
|
||||
# else
|
||||
# define SWIFT_RELEASES_ARGUMENT
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(SWIFT_WARN_UNUSED_RESULT)
|
||||
# if __has_attribute(warn_unused_result)
|
||||
# define SWIFT_WARN_UNUSED_RESULT __attribute__((warn_unused_result))
|
||||
# else
|
||||
# define SWIFT_WARN_UNUSED_RESULT
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(SWIFT_NORETURN)
|
||||
# if __has_attribute(noreturn)
|
||||
# define SWIFT_NORETURN __attribute__((noreturn))
|
||||
# else
|
||||
# define SWIFT_NORETURN
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(SWIFT_CLASS_EXTRA)
|
||||
# define SWIFT_CLASS_EXTRA
|
||||
#endif
|
||||
#if !defined(SWIFT_PROTOCOL_EXTRA)
|
||||
# define SWIFT_PROTOCOL_EXTRA
|
||||
#endif
|
||||
#if !defined(SWIFT_ENUM_EXTRA)
|
||||
# define SWIFT_ENUM_EXTRA
|
||||
#endif
|
||||
#if !defined(SWIFT_CLASS)
|
||||
# if __has_attribute(objc_subclassing_restricted)
|
||||
# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_CLASS_EXTRA
|
||||
# define SWIFT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_subclassing_restricted)) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
|
||||
# else
|
||||
# define SWIFT_CLASS(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
|
||||
# define SWIFT_CLASS_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_CLASS_EXTRA
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(SWIFT_RESILIENT_CLASS)
|
||||
# if __has_attribute(objc_class_stub)
|
||||
# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME) __attribute__((objc_class_stub))
|
||||
# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) __attribute__((objc_class_stub)) SWIFT_CLASS_NAMED(SWIFT_NAME)
|
||||
# else
|
||||
# define SWIFT_RESILIENT_CLASS(SWIFT_NAME) SWIFT_CLASS(SWIFT_NAME)
|
||||
# define SWIFT_RESILIENT_CLASS_NAMED(SWIFT_NAME) SWIFT_CLASS_NAMED(SWIFT_NAME)
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(SWIFT_PROTOCOL)
|
||||
# define SWIFT_PROTOCOL(SWIFT_NAME) SWIFT_RUNTIME_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
|
||||
# define SWIFT_PROTOCOL_NAMED(SWIFT_NAME) SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_PROTOCOL_EXTRA
|
||||
#endif
|
||||
#if !defined(SWIFT_EXTENSION)
|
||||
# define SWIFT_EXTENSION(M) SWIFT_PASTE(M##_Swift_, __LINE__)
|
||||
#endif
|
||||
#if !defined(OBJC_DESIGNATED_INITIALIZER)
|
||||
# if __has_attribute(objc_designated_initializer)
|
||||
# define OBJC_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
|
||||
# else
|
||||
# define OBJC_DESIGNATED_INITIALIZER
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(SWIFT_ENUM_ATTR)
|
||||
# if __has_attribute(enum_extensibility)
|
||||
# define SWIFT_ENUM_ATTR(_extensibility) __attribute__((enum_extensibility(_extensibility)))
|
||||
# else
|
||||
# define SWIFT_ENUM_ATTR(_extensibility)
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(SWIFT_ENUM)
|
||||
# define SWIFT_ENUM(_type, _name, _extensibility) enum _name : _type _name; enum SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type
|
||||
# if __has_feature(generalized_swift_name)
|
||||
# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) enum _name : _type _name SWIFT_COMPILE_NAME(SWIFT_NAME); enum SWIFT_COMPILE_NAME(SWIFT_NAME) SWIFT_ENUM_ATTR(_extensibility) SWIFT_ENUM_EXTRA _name : _type
|
||||
# else
|
||||
# define SWIFT_ENUM_NAMED(_type, _name, SWIFT_NAME, _extensibility) SWIFT_ENUM(_type, _name, _extensibility)
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(SWIFT_UNAVAILABLE)
|
||||
# define SWIFT_UNAVAILABLE __attribute__((unavailable))
|
||||
#endif
|
||||
#if !defined(SWIFT_UNAVAILABLE_MSG)
|
||||
# define SWIFT_UNAVAILABLE_MSG(msg) __attribute__((unavailable(msg)))
|
||||
#endif
|
||||
#if !defined(SWIFT_AVAILABILITY)
|
||||
# define SWIFT_AVAILABILITY(plat, ...) __attribute__((availability(plat, __VA_ARGS__)))
|
||||
#endif
|
||||
#if !defined(SWIFT_WEAK_IMPORT)
|
||||
# define SWIFT_WEAK_IMPORT __attribute__((weak_import))
|
||||
#endif
|
||||
#if !defined(SWIFT_DEPRECATED)
|
||||
# define SWIFT_DEPRECATED __attribute__((deprecated))
|
||||
#endif
|
||||
#if !defined(SWIFT_DEPRECATED_MSG)
|
||||
# define SWIFT_DEPRECATED_MSG(...) __attribute__((deprecated(__VA_ARGS__)))
|
||||
#endif
|
||||
#if !defined(SWIFT_DEPRECATED_OBJC)
|
||||
# if __has_feature(attribute_diagnose_if_objc)
|
||||
# define SWIFT_DEPRECATED_OBJC(Msg) __attribute__((diagnose_if(1, Msg, "warning")))
|
||||
# else
|
||||
# define SWIFT_DEPRECATED_OBJC(Msg) SWIFT_DEPRECATED_MSG(Msg)
|
||||
# endif
|
||||
#endif
|
||||
#if defined(__OBJC__)
|
||||
#if !defined(IBSegueAction)
|
||||
# define IBSegueAction
|
||||
#endif
|
||||
#endif
|
||||
#if !defined(SWIFT_EXTERN)
|
||||
# if defined(__cplusplus)
|
||||
# define SWIFT_EXTERN extern "C"
|
||||
# else
|
||||
# define SWIFT_EXTERN extern
|
||||
# endif
|
||||
#endif
|
||||
#if !defined(SWIFT_CALL)
|
||||
# define SWIFT_CALL __attribute__((swiftcall))
|
||||
#endif
|
||||
#if !defined(SWIFT_INDIRECT_RESULT)
|
||||
# define SWIFT_INDIRECT_RESULT __attribute__((swift_indirect_result))
|
||||
#endif
|
||||
#if !defined(SWIFT_CONTEXT)
|
||||
# define SWIFT_CONTEXT __attribute__((swift_context))
|
||||
#endif
|
||||
#if !defined(SWIFT_ERROR_RESULT)
|
||||
# define SWIFT_ERROR_RESULT __attribute__((swift_error_result))
|
||||
#endif
|
||||
#if defined(__cplusplus)
|
||||
# define SWIFT_NOEXCEPT noexcept
|
||||
#else
|
||||
# define SWIFT_NOEXCEPT
|
||||
#endif
|
||||
#if !defined(SWIFT_C_INLINE_THUNK)
|
||||
# if __has_attribute(always_inline)
|
||||
# if __has_attribute(nodebug)
|
||||
# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline)) __attribute__((nodebug))
|
||||
# else
|
||||
# define SWIFT_C_INLINE_THUNK inline __attribute__((always_inline))
|
||||
# endif
|
||||
# else
|
||||
# define SWIFT_C_INLINE_THUNK inline
|
||||
# endif
|
||||
#endif
|
||||
#if defined(_WIN32)
|
||||
#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL)
|
||||
# define SWIFT_IMPORT_STDLIB_SYMBOL __declspec(dllimport)
|
||||
#endif
|
||||
#else
|
||||
#if !defined(SWIFT_IMPORT_STDLIB_SYMBOL)
|
||||
# define SWIFT_IMPORT_STDLIB_SYMBOL
|
||||
#endif
|
||||
#endif
|
||||
#if defined(__OBJC__)
|
||||
#if __has_feature(objc_modules)
|
||||
#if __has_warning("-Watimport-in-framework-header")
|
||||
#pragma clang diagnostic ignored "-Watimport-in-framework-header"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
#pragma clang diagnostic ignored "-Wproperty-attribute-mismatch"
|
||||
#pragma clang diagnostic ignored "-Wduplicate-method-arg"
|
||||
#if __has_warning("-Wpragma-clang-attribute")
|
||||
# pragma clang diagnostic ignored "-Wpragma-clang-attribute"
|
||||
#endif
|
||||
#pragma clang diagnostic ignored "-Wunknown-pragmas"
|
||||
#pragma clang diagnostic ignored "-Wnullability"
|
||||
#pragma clang diagnostic ignored "-Wdollar-in-identifier-extension"
|
||||
#pragma clang diagnostic ignored "-Wunsafe-buffer-usage"
|
||||
|
||||
#if __has_attribute(external_source_symbol)
|
||||
# pragma push_macro("any")
|
||||
# undef any
|
||||
# pragma clang attribute push(__attribute__((external_source_symbol(language="Swift", defined_in="ClawdisChatUI",generated_declaration))), apply_to=any(function,enum,objc_interface,objc_category,objc_protocol))
|
||||
# pragma pop_macro("any")
|
||||
#endif
|
||||
|
||||
#if defined(__OBJC__)
|
||||
|
||||
#endif
|
||||
#if __has_attribute(external_source_symbol)
|
||||
# pragma clang attribute pop
|
||||
#endif
|
||||
#if defined(__cplusplus)
|
||||
#endif
|
||||
#pragma clang diagnostic pop
|
||||
#endif
|
||||
@@ -0,0 +1,3 @@
|
||||
module ClawdisChatUI {
|
||||
header "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/include/ClawdisChatUI-Swift.h"
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"": {
|
||||
"swift-dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/master.swiftdeps"
|
||||
},
|
||||
"/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatComposer.swift": {
|
||||
"dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatComposer.d",
|
||||
"object": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatComposer.swift.o",
|
||||
"swiftmodule": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatComposer~partial.swiftmodule",
|
||||
"swift-dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatComposer.swiftdeps",
|
||||
"diagnostics": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatComposer.dia"
|
||||
},
|
||||
"/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatMarkdownSplitter.swift": {
|
||||
"dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatMarkdownSplitter.d",
|
||||
"object": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatMarkdownSplitter.swift.o",
|
||||
"swiftmodule": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatMarkdownSplitter~partial.swiftmodule",
|
||||
"swift-dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatMarkdownSplitter.swiftdeps",
|
||||
"diagnostics": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatMarkdownSplitter.dia"
|
||||
},
|
||||
"/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatMessageViews.swift": {
|
||||
"dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatMessageViews.d",
|
||||
"object": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatMessageViews.swift.o",
|
||||
"swiftmodule": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatMessageViews~partial.swiftmodule",
|
||||
"swift-dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatMessageViews.swiftdeps",
|
||||
"diagnostics": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatMessageViews.dia"
|
||||
},
|
||||
"/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatModels.swift": {
|
||||
"dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatModels.d",
|
||||
"object": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatModels.swift.o",
|
||||
"swiftmodule": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatModels~partial.swiftmodule",
|
||||
"swift-dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatModels.swiftdeps",
|
||||
"diagnostics": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatModels.dia"
|
||||
},
|
||||
"/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatPayloadDecoding.swift": {
|
||||
"dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatPayloadDecoding.d",
|
||||
"object": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatPayloadDecoding.swift.o",
|
||||
"swiftmodule": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatPayloadDecoding~partial.swiftmodule",
|
||||
"swift-dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatPayloadDecoding.swiftdeps",
|
||||
"diagnostics": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatPayloadDecoding.dia"
|
||||
},
|
||||
"/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatSessions.swift": {
|
||||
"dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatSessions.d",
|
||||
"object": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatSessions.swift.o",
|
||||
"swiftmodule": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatSessions~partial.swiftmodule",
|
||||
"swift-dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatSessions.swiftdeps",
|
||||
"diagnostics": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatSessions.dia"
|
||||
},
|
||||
"/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatSheets.swift": {
|
||||
"dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatSheets.d",
|
||||
"object": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatSheets.swift.o",
|
||||
"swiftmodule": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatSheets~partial.swiftmodule",
|
||||
"swift-dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatSheets.swiftdeps",
|
||||
"diagnostics": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatSheets.dia"
|
||||
},
|
||||
"/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatTheme.swift": {
|
||||
"dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatTheme.d",
|
||||
"object": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatTheme.swift.o",
|
||||
"swiftmodule": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatTheme~partial.swiftmodule",
|
||||
"swift-dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatTheme.swiftdeps",
|
||||
"diagnostics": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatTheme.dia"
|
||||
},
|
||||
"/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatTransport.swift": {
|
||||
"dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatTransport.d",
|
||||
"object": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatTransport.swift.o",
|
||||
"swiftmodule": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatTransport~partial.swiftmodule",
|
||||
"swift-dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatTransport.swiftdeps",
|
||||
"diagnostics": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatTransport.dia"
|
||||
},
|
||||
"/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatView.swift": {
|
||||
"dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatView.d",
|
||||
"object": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatView.swift.o",
|
||||
"swiftmodule": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatView~partial.swiftmodule",
|
||||
"swift-dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatView.swiftdeps",
|
||||
"diagnostics": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatView.dia"
|
||||
},
|
||||
"/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatViewModel.swift": {
|
||||
"dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatViewModel.d",
|
||||
"object": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatViewModel.swift.o",
|
||||
"swiftmodule": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatViewModel~partial.swiftmodule",
|
||||
"swift-dependencies": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatViewModel.swiftdeps",
|
||||
"diagnostics": "/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/.build/arm64-apple-macosx/debug/ClawdisChatUI.build/ChatViewModel.dia"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatComposer.swift
|
||||
/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatMarkdownSplitter.swift
|
||||
/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatMessageViews.swift
|
||||
/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatModels.swift
|
||||
/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatPayloadDecoding.swift
|
||||
/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatSessions.swift
|
||||
/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatSheets.swift
|
||||
/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatTheme.swift
|
||||
/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatTransport.swift
|
||||
/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatView.swift
|
||||
/Users/steipete/Projects/clawdis/apps/shared/ClawdisKit/Sources/ClawdisChatUI/ChatViewModel.swift
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user