|
|
|
@ -196,43 +196,64 @@ public final class VoiceCommandExecutor { |
|
|
|
} |
|
|
|
|
|
|
|
public func isSlashCommand(text: String) -> Bool { |
|
|
|
let lower = text.lowercased() |
|
|
|
|
|
|
|
// Check for starting prefix |
|
|
|
for prefix in ["اسلش", "اصلش", "slash", "slsh", "/", "\\"] { |
|
|
|
if lower.hasPrefix(prefix) { return true } |
|
|
|
return getSlashWordPosition(text: text) != nil |
|
|
|
} |
|
|
|
|
|
|
|
// Check for slash trigger inside speech (e.g. after filler words like "امم، اسلش ...") |
|
|
|
if lower.contains("اسلش") || lower.contains("اصلش") || lower.contains("slash") || lower.contains("slsh") { |
|
|
|
return true |
|
|
|
} |
|
|
|
public func getSlashWordPosition(text: String) -> Int? { |
|
|
|
let cleanText = text.trimmingCharacters(in: .whitespacesAndNewlines) |
|
|
|
guard !cleanText.isEmpty else { return nil } |
|
|
|
|
|
|
|
return false |
|
|
|
// Starting with slash symbol, e.g. "/settings" |
|
|
|
if cleanText.hasPrefix("/") || cleanText.hasPrefix("\\") { |
|
|
|
return 0 |
|
|
|
} |
|
|
|
|
|
|
|
public func extractCommandBody(rawText: String) -> String { |
|
|
|
var text = rawText.trimmingCharacters(in: .whitespacesAndNewlines) |
|
|
|
let lower = text.lowercased() |
|
|
|
let punct = CharacterSet(charactersIn: ".,،؛:!؟?\"'«»()[]{}—-_") |
|
|
|
let rawWords = cleanText.components(separatedBy: .whitespacesAndNewlines).filter { !$0.isEmpty } |
|
|
|
guard !rawWords.isEmpty else { return nil } |
|
|
|
|
|
|
|
let slashTriggers = ["اسلش", "اصلش", "slash", "slsh", "/", "\\"] |
|
|
|
|
|
|
|
let triggers = ["اسلش", "اصلش", "slash", "slsh", "//", "/", "\\"] |
|
|
|
var foundRange: Range<String.Index>? = nil |
|
|
|
// Word 1: Direct slash command (e.g. "اسلش آنتیگراویتی") |
|
|
|
let firstWordClean = rawWords[0].trimmingCharacters(in: punct).lowercased() |
|
|
|
if slashTriggers.contains(firstWordClean) || firstWordClean.hasPrefix("/") || firstWordClean.hasPrefix("\\") { |
|
|
|
return 0 |
|
|
|
} |
|
|
|
|
|
|
|
for trig in triggers { |
|
|
|
if let r = lower.range(of: trig) { |
|
|
|
if foundRange == nil || r.lowerBound < foundRange!.lowerBound { |
|
|
|
foundRange = r |
|
|
|
// Word 2: Slash after a single filler word (e.g. "امم اسلش فایرفاکس" or "خب، اسلش چپ") |
|
|
|
if rawWords.count >= 2 { |
|
|
|
let secondWordClean = rawWords[1].trimmingCharacters(in: punct).lowercased() |
|
|
|
if slashTriggers.contains(secondWordClean) || secondWordClean.hasPrefix("/") || secondWordClean.hasPrefix("\\") { |
|
|
|
return 1 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// STRICT RULE: If "slash" occurs at word 3 or later (middle of sentence), it is NOT a command! |
|
|
|
return nil |
|
|
|
} |
|
|
|
|
|
|
|
if let r = foundRange { |
|
|
|
text = String(text[r.upperBound...]) |
|
|
|
public func extractCommandBody(rawText: String) -> String { |
|
|
|
guard let slashIndex = getSlashWordPosition(text: rawText) else { return "" } |
|
|
|
|
|
|
|
let rawWords = rawText.trimmingCharacters(in: .whitespacesAndNewlines) |
|
|
|
.components(separatedBy: .whitespacesAndNewlines) |
|
|
|
.filter { !$0.isEmpty } |
|
|
|
|
|
|
|
guard rawWords.count > slashIndex + 1 else { |
|
|
|
// Case where command is attached to first symbol, e.g. "/settings" |
|
|
|
if slashIndex == 0, let first = rawWords.first, first.hasPrefix("/") || first.hasPrefix("\\") { |
|
|
|
let bodyFromFirst = String(first.dropFirst()).trimmingCharacters(in: .whitespacesAndNewlines) |
|
|
|
let rest = rawWords.dropFirst().joined(separator: " ") |
|
|
|
return ([bodyFromFirst, rest].filter { !$0.isEmpty }).joined(separator: " ") |
|
|
|
} |
|
|
|
return "" |
|
|
|
} |
|
|
|
|
|
|
|
let bodyWords = rawWords[(slashIndex + 1)...] |
|
|
|
var body = bodyWords.joined(separator: " ") |
|
|
|
let leadingPunct = CharacterSet(charactersIn: " :.,،؛!؟?-_—\"'«»()[]{}") |
|
|
|
text = text.trimmingCharacters(in: leadingPunct) |
|
|
|
return text |
|
|
|
body = body.trimmingCharacters(in: leadingPunct) |
|
|
|
return body |
|
|
|
} |
|
|
|
|
|
|
|
// MARK: - Main Execution Dispatcher |
|
|
|
|